Nuxt: 性能优化
最后更新:2026-08-26
MegaShop 上线了,但 Lighthouse 评分只有 45 分——首屏加载 4.5 秒,图片未优化,JS bundle 太大。Charlie 需要 Lighthouse 90+ 分,首屏 LCP < 2s,才能在竞争中胜出。
1. 你将学到
- 代码分割与 Tree Shaking:Vite 构建优化与 bundle 分析
- 图片优化:@nuxt/image + WebP + 懒加载 + 响应式 srcset
- 组件懒加载与异步:Lazy 前缀 + Suspense
- 缓存策略:HTTP 缓存头 + Nitro KV + CDN
- MegaShop Lighthouse 90+ 优化实战
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) 性能优化决策树
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——关键渲染路径必须精简,首屏只加载必要资源。
📖 小节
- 代码分割:Lazy 组件 + dynamic import + manualChunks,首屏只加载必要代码
- 图片优化:@nuxt/image 自动 WebP/AVIF + 响应式 srcset + 懒加载
- 缓存策略:浏览器 → CDN → Nitro ISR → 动态渲染,多层缓存
- Bundle 分析用 rollup-plugin-visualizer 找到体积瓶颈
- MegaShop 优化后:JS 180KB(gzip) + LCP 1.3s + Lighthouse 92 分
📝 作业
- 基础题(难度⭐):安装 @nuxt/image,将所有 img 标签替换为 NuxtImg,验证自动 WebP 转换
- 进阶题(难度⭐⭐):用 rollup-plugin-visualizer 分析 bundle,找到最大的依赖并配置 manualChunks 拆分
- 挑战题(难度⭐⭐⭐):实现 Lighthouse 90+ 分:图片优化 + 代码分割 + 缓存策略,用 Lighthouse CI 自动审计
---|