Chuyển đến nội dung chính

Lesson 20: Performance Optimization

Bundle analysis, tree shaking. Code splitting, component lazy loading. Payload optimization, caching strategies. Vue DevTools performance tab. Lighthouse, Core Web Vitals.

💻 Programming — Lesson 20 Lesson 20: Performance Optimization

Vue.js & Nuxt: From Basics to Advanced

Part 6: Testing, Deployment & Production

xdev.asia

1. Bundle Analysis

// nuxt.config.ts
export default defineNuxtConfig({
  build: {
    analyze: true, // Bật webpack-bundle-analyzer
  },
  vite: {
    build: {
      rollupOptions: {
        output: {
          manualChunks: {
            'vendor-ui': ['@headlessui/vue', '@heroicons/vue'],
            'vendor-charts': ['chart.js', 'vue-chartjs'],
          },
        },
      },
    },
  },
})
# Phân tích bundle
npx nuxi analyze

# Kiểm tra bundle size
npx nuxi build --analyze

2. Lazy Loading Components

<script setup>
// Lazy load component — tự động code-split
const HeavyChart = defineAsyncComponent(() =>
  import('~/components/HeavyChart.vue')
)

// Nuxt auto-import: prefix "Lazy"
// <LazyHeavyChart /> sẽ tự code-split
</script>

<template>
  <div>
    <!-- Chỉ load khi visible -->
    <LazyHeavyChart v-if="showChart" />

    <!-- Lazy load với Suspense -->
    <Suspense>
      <template #default>
        <HeavyChart :data="chartData" />
      </template>
      <template #fallback>
        <div class="skeleton h-64"></div>
      </template>
    </Suspense>
  </div>
</template>

3. Image Optimization

<template>
  <!-- Nuxt Image — tự động optimize -->
  <NuxtImg
    src="/images/hero.jpg"
    width="800"
    height="400"
    format="webp"
    quality="80"
    loading="lazy"
    placeholder
    sizes="sm:100vw md:50vw lg:800px"
  />

  <!-- NuxtPicture — responsive format -->
  <NuxtPicture
    src="/images/banner.jpg"
    format="avif,webp"
    :img-attrs="{ class: 'rounded-lg' }"
  />
</template>

4. Payload Optimization

// Giảm payload size khi SSR
export default defineNuxtConfig({
  experimental: {
    payloadExtraction: true, // Tách payload riêng file
  },
})

// composables/useOptimizedFetch.ts
export function useOptimizedFetch<T>(url: string) {
  return useFetch<T>(url, {
    // Chỉ pick fields cần thiết
    transform: (data: any[]) => data.map(({ id, title, slug }) => ({
      id, title, slug,
    })),
    // Cache key để deduplicate
    key: url,
  })
}

5. Caching Strategies

// server/api/products.get.ts — Route caching
export default defineCachedEventHandler(async (event) => {
  const products = await db.select().from(productsTable)
  return products
}, {
  maxAge: 60 * 10,      // Cache 10 phút
  staleMaxAge: 60 * 60, // Stale 1 giờ
  swr: true,            // Stale-while-revalidate
  name: 'products-list',
  getKey: (event) => getQuery(event).category as string || 'all',
})

// nuxt.config.ts — ISR
export default defineNuxtConfig({
  routeRules: {
    '/': { isr: 60 },
    '/blog/**': { isr: 3600 },
    '/api/**': { cors: true, headers: { 'cache-control': 'max-age=60' } },
    '/admin/**': { ssr: false },
  },
})

6. Core Web Vitals

MetricGoodOptimal technique
LCP< 2.5sPreload hero image, SSR, font-display: swap
INP< 200msDebounce input, web workers, virtualize lists
CLS< 0.1Set width/height on img, skeleton UI
// plugins/web-vitals.client.ts
import { onCLS, onINP, onLCP } from 'web-vitals'

export default defineNuxtPlugin(() => {
  onLCP(sendToAnalytics)
  onINP(sendToAnalytics)
  onCLS(sendToAnalytics)
})

function sendToAnalytics(metric: any) {
  navigator.sendBeacon('/api/analytics/vitals', JSON.stringify({
    name: metric.name,
    value: metric.value,
    id: metric.id,
  }))
}

7. Virtual Scrolling

<script setup>
import { useVirtualList } from '@vueuse/core'

const allItems = ref(Array.from({ length: 10000 }, (_, i) => ({
  id: i,
  title: `Item ${i}`,
})))

const { list, containerProps, wrapperProps } = useVirtualList(allItems, {
  itemHeight: 48,
  overscan: 10,
})
</script>

<template>
  <div v-bind="containerProps" class="h-96 overflow-auto">
    <div v-bind="wrapperProps">
      <div v-for="{ data } in list" :key="data.id" class="h-12 px-4 flex items-center">
        {{ data.title }}
      </div>
    </div>
  </div>
</template>

Next article: Deployment & Hosting — Vercel, Cloudflare, Docker, CI/CD.