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

第 20 課:效能優化

捆綁分析,搖樹。程式碼分割,元件延遲載入。有效負載優化、快取策略。 Vue DevTools 效能選項卡。燈塔,核心網路生命。

💻 程式設計 — 第 20 課 第 20 課:效能優化

Vue.js 和 Nuxt:從基礎到高級

第 6 部分:測試、部署和生產

亞洲開發網

1. 捆綁分析

// 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. 延遲載入組件

<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. 影像優化

<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. 負載優化

// 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. 快取策略

// 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. 核心網路生命線

公制好最佳技術
液晶聚合物< 2.5秒預先載入英雄圖片、SSR、字體顯示:交換
內啡肽<200毫秒反跳輸入、網路工作人員、虛擬化列表
CLS< 0.1設定img、骨架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. 虛擬滾動

<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>

下一篇: 部署和託管 — Vercel、Cloudflare、Docker、CI/CD。