Nuxt: 性能优化

最后更新:2026-08-26

MegaShop 上线了,但 Lighthouse 评分只有 45 分——首屏加载 4.5 秒,图片未优化,JS bundle 太大。Charlie 需要 Lighthouse 90+ 分,首屏 LCP < 2s,才能在竞争中胜出。

1. 你将学到


2. 一个架构师的真实故事

(1) 痛点:Lighthouse 45 分,首屏 4.5 秒

Alice 在手机上打开 MegaShop,4.5 秒才看到商品。Charlie 用 Lighthouse 审计发现:JS bundle 2.8MB、图片全是 PNG 未压缩、无缓存头、首屏加载了 50 个组件的代码。

(2) Nuxt 3 性能优化的解法

Nuxt 3 内置多种性能优化能力:

TYPESCRIPT
// Image optimization with @nuxt/image
<NuxtImg src="/products/123.webp" width="400" height="400" loading="lazy" />

(3) 收益:Lighthouse 92 分,LCP 1.3 秒

优化后 JS bundle 降到 180KB(gzip),图片自动 WebP + 响应式,首屏 LCP 1.3 秒,Lighthouse 92 分。


3. 代码分割与 Tree Shaking

(1) 性能优化决策树

100%
flowchart TB
    A[Performance Issue] --> B{What type?}
    B -->|Slow first paint| C[Code Splitting]
    B -->|Large images| D[Image Optimization]
    B -->|Slow subsequent loads| E[Caching Strategy]
    B -->|JS too large| F[Tree Shaking]

    C --> C1[Lazy components]
    C --> C2[Dynamic imports]
    D --> D1[@nuxt/image module]
    D --> D2[WebP conversion]
    E --> E1[HTTP Cache Headers]
    E --> E2[Nitro KV Cache]
    F --> F1[Check bundle with rollup-plugin-visualizer]

(2) Bundle 分析

BASH
npm install -D rollup-plugin-visualizer
TYPESCRIPT
// nuxt.config.ts
import { visualizer } from 'rollup-plugin-visualizer'

export default defineNuxtConfig({
  vite: {
    plugins: [
      visualizer({ filename: 'bundle-stats.html', open: true })
    ],
    build: {
      rollupOptions: {
        output: {
          manualChunks: {
            'vendor': ['vue', 'vue-router'],
            'pinia': ['pinia']
          }
        }
      }
    }
  }
})

▶ 示例:Lazy 组件代码分割

VUE
<template>
  <div>
    <!-- Core: loaded immediately -->
    <AppHeader />
    <ProductHero :product="featured" />

    <!-- Below fold: lazy loaded -->
    <LazyProductGrid :products="all" />
    <LazyProductReviews :product-id="id" />

    <!-- Conditional: loaded only when visible -->
    <LazyNewsletterSignup v-if="showNewsletter" />
  </div>
</template>

输出:

TEXT 📖 仅展示
// 执行成功

(3) 优化前后对比

指标 优化前 优化后 改善
JS Bundle (gzip) 2.8 MB 180 KB -94%
首屏组件数 50 8 -84%
首屏 JS 下载 2.8 MB 85 KB -97%
LCP 4.5s 1.3s -71%

4. 图片优化

▶ 示例:安装 @nuxt/image

BASH
npm install @nuxt/image

输出:

TEXT 📖 仅展示
# 命令执行成功
TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/image'],
  image: {
    quality: 80,
    format: ['webp', 'avif'],
    screens: {
      xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280, xxl: 1536
    }
  }
})

▶ 示例:NuxtImg 响应式图片

VUE
<template>
  <div>
    <!-- Auto WebP/AVIF + responsive srcset -->
    <NuxtImg
      src="/images/product-123.jpg"
      width="800"
      height="600"
      format="webp"
      loading="lazy"
      sizes="sm:100vw md:50vw lg:33vw"
      :modifiers="{ quality: 80 }"
      alt="Premium Headphones"
    />

    <!-- Placeholder with blur-up -->
    <NuxtImg
      src="/images/hero.jpg"
      placeholder
      width="1920"
      height="1080"
      format="webp"
      alt="MegaShop Hero"
    />
  </div>
</template>

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例:NuxtPicture 多格式

VUE
<template>
  <!-- NuxtPicture: auto-select best format per browser -->
  <NuxtPicture
    src="/images/banner.jpg"
    width="1200"
    height="400"
    format="avif,webp"
    loading="lazy"
    alt="Sale Banner"
  />
</template>

输出:

TEXT 📖 仅展示
// 执行成功

(1) 图片格式对比

格式 压缩率 浏览器支持 适用场景
AVIF 🟢 最高(50% vs JPG) Chrome/Edge/Firefox 现代浏览器
WebP 🟢 高(30% vs JPG) 全主流 通用首选
JPEG 🟡 基准 全部 兼容回退
PNG 🔴 大 全部 透明图

5. 缓存策略

▶ 示例:HTTP 缓存头

TYPESCRIPT
// nuxt.config.ts - Cache headers per route
export default defineNuxtConfig({
  routeRules: {
    // Static assets: 1 year immutable cache
    '/_nuxt/**': {
      headers: { 'cache-control': 'public, max-age=31536000, immutable' }
    },
    // Images: 1 day cache
    '/images/**': {
      headers: { 'cache-control': 'public, max-age=86400, stale-while-revalidate=604800' }
    },
    // Product pages: ISR cache
    '/products/**': {
      swr: 86400,
      headers: { 'cache-control': 'public, s-maxage=86400, stale-while-revalidate=3600' }
    }
  }
})

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例:Nitro KV 缓存 API

TYPESCRIPT
// server/api/products/[id].get.ts - Cached handler
export default cachedEventHandler(
  async (event) => {
    const id = getRouterParam(event, 'id')
    const product = await fetchProductFromDB(Number(id))
    return product
  },
  {
    maxAge: 60 * 60,        // Cache for 1 hour
    swr: true,               // Serve stale while revalidating
    getKey: (event) => `product:${getRouterParam(event, 'id')}`,
    varies: ['Accept-Language'] // Different cache per language
  }
)

输出:

TEXT 📖 仅展示
// 执行成功

(1) 缓存层级对比

层级 位置 速度 控制方式
浏览器缓存 用户设备 ⚡⚡⚡ 最快 Cache-Control 头
CDN 边缘缓存 最近节点 ⚡⚡ 快 CDN 配置 + Vary
Nitro KV 缓存 服务器内存 ⚡ 快 cachedEventHandler
ISR 页面缓存 Nitro 存储 ⚡ 快 routeRules swr

6. 综合示例:MegaShop 性能优化配置

TYPESCRIPT
// nuxt.config.ts - Full performance optimization
export default defineNuxtConfig({
  ssr: true,

  modules: ['@nuxt/image', '@nuxtjs/tailwindcss'],

  // Image optimization
  image: {
    quality: 80,
    format: ['webp', 'avif'],
    screens: { xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280 }
  },

  // Vite build optimization
  vite: {
    build: {
      cssCodeSplit: true,
      rollupOptions: {
        output: {
          manualChunks(id) {
            if (id.includes('node_modules')) {
              if (id.includes('pinia')) return 'vendor-pinia'
              if (id.includes('vue')) return 'vendor-vue'
              return 'vendor-libs'
            }
          }
        }
      }
    }
  },

  // Route-level caching strategy
  routeRules: {
    '/': { prerender: true },
    '/products': { swr: 3600 },
    '/products/**': { swr: 86400 },
    '/admin/**': { ssr: false },
    '/_nuxt/**': { headers: { 'cache-control': 'public, max-age=31536000, immutable' } },
    '/images/**': { headers: { 'cache-control': 'public, max-age=86400' } }
  },

  // App performance hints
  experimental: {
    payloadExtraction: true // Reduce payload size
  }
})
VUE
<!-- pages/index.vue - Optimized homepage -->
<template>
  <div>
    <AppHeader />

    <!-- Above fold: eager load -->
    <section class="hero">
      <NuxtImg src="/images/hero.jpg" width="1200" height="400" format="webp"
        priority alt="MegaShop" />
      <h1>MegaShop</h1>
    </section>

    <!-- Below fold: lazy load -->
    <LazyProductGrid :products="featured" />

    <!-- Conditional: only load when in viewport -->
    <LazyNewsletterSignup v-if="showNewsletter" />
  </div>
</template>

<script setup lang="ts">
const { data: featured } = await useFetch('/api/products/featured')
const showNewsletter = ref(false)
</script>

❓ 常见问题

Q Lighthouse 分数低怎么办?
A 按优先级处理:1) 图片优化(WebP/压缩/懒加载)→ 2) JS bundle 分析(Lazy 组件/Tree Shaking)→ 3) 缓存策略(HTTP 头/ISR)→ 4) 字体优化。
Q @nuxt/image 支持外部图片 URL 吗?
A 支持。配置 image.domains 添加外部域名:image: { domains: ['cdn.example.com'] }。NuxtImg 会自动优化外部图片。
Q NuxtImg 和普通 img 有什么区别?
A NuxtImg 自动生成 srcset、转换 WebP/AVIF、按设备宽高裁剪、懒加载。img 需要手动处理所有优化。
Q ISR 缓存和 HTTP 缓存哪个更优先?
A ISR 是服务端缓存(Nitro 存储),HTTP 缓存是浏览器/CDN 层。请求链路:浏览器缓存 → CDN 缓存 → Nitro ISR → 动态渲染。各层独立。
Q manualChunks 配置有什么坑?
A 不要把服务端代码分到客户端 chunk。Nuxt 3 自动处理 server/client 分离。manualChunks 只需处理第三方库的拆分。
Q Lighthouse 90+ 分难达到吗?
A SSR + 图片优化 + 缓存策略就能到 90+。最难的是 LCP < 2.5s——关键渲染路径必须精简,首屏只加载必要资源。

📖 小节


📝 作业

  1. 基础题(难度⭐):安装 @nuxt/image,将所有 img 标签替换为 NuxtImg,验证自动 WebP 转换
  2. 进阶题(难度⭐⭐):用 rollup-plugin-visualizer 分析 bundle,找到最大的依赖并配置 manualChunks 拆分
  3. 挑战题(难度⭐⭐⭐):实现 Lighthouse 90+ 分:图片优化 + 代码分割 + 缓存策略,用 Lighthouse CI 自动审计

---|

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏