Nuxt4 性能调优实战 Checklist
性能优化不是一次性工作,而是持续的过程。本章提供一份可执行的 Checklist——从构建产物分析到 Core Web Vitals 达标验证,每一项都给出具体操作步骤、配置代码和预期效果。按优先级排序:先做投入小收益大的(图片、字体、缓存),再做需要架构调整的(渲染策略、代码拆分)。
1. 构建产物分析
1.1 使用 nuxi analyze
npx nuxi analyze运行后自动打开浏览器,展示 bundle 的 treemap 可视化。重点关注:
- 超过 50KB 的 chunk:是否可以拆分或懒加载
- 重复依赖:同一个库是否被打包了多次
- 未使用的代码:某些库只用了一个函数,却打包了整个库
1.2 查看 SSR payload 大小
// 临时添加到 app.vue,查看 payload 大小
onMounted(() => {
const payload = document.querySelector('#__NUXT_DATA__')
if (payload) {
console.log(`Payload size: ${(payload.textContent?.length ?? 0 / 1024).toFixed(1)} KB`)
}
})目标:payload 控制在 50KB 以下。超过 100KB 需要用 transform 或 pick 裁剪。
1.3 Lighthouse 基线测试
# 安装 Lighthouse CLI
npm install -g lighthouse
# 测试生产构建
npx nuxi build && npx nuxi preview &
lighthouse http://localhost:3000 --output=json --output-path=./lighthouse-baseline.json记录基线分数,后续每次优化后对比。
2. Bundle 优化
2.1 Tree Shaking 检查
// ❌ 导入整个库
import _ from 'lodash'
_.debounce(fn, 300)
// ✅ 按需导入
import { debounce } from 'lodash-es'
debounce(fn, 300)
// ✅ 更好:使用 VueUse 替代
import { useDebounceFn } from '@vueuse/core'
const debouncedFn = useDebounceFn(fn, 300)2.2 手动 chunk 拆分
// nuxt.config.ts
export default defineNuxtConfig({
vite: {
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor-ui': ['@nuxt/ui'],
'vendor-chart': ['chart.js', 'echarts'],
'vendor-editor': ['tiptap', 'prosemirror'],
},
},
},
},
},
})效果:大型第三方库独立成 chunk,首屏不需要的库不会阻塞加载。
2.3 组件懒加载
<template>
<!-- Lazy 前缀:Nuxt 自动转为异步组件 -->
<LazyVideoPlayer v-if="showPlayer" :src="videoUrl" />
<LazyRichEditor v-if="isEditing" v-model="content" />
<LazyAdminChart v-if="isAdmin" :data="chartData" />
</template>规则:首屏不可见的组件都加 Lazy 前缀。
2.4 外部化大型依赖
// nuxt.config.ts — 把大型库从 bundle 中排除,用 CDN 加载
export default defineNuxtConfig({
app: {
head: {
script: [
{ src: 'https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js', defer: true },
],
},
},
vite: {
build: {
rollupOptions: {
external: ['echarts'],
},
},
},
})3. 渲染策略
3.1 routeRules 混合渲染
export default defineNuxtConfig({
routeRules: {
// 静态页面:构建时预渲染
'/': { prerender: true },
'/about': { prerender: true },
'/pricing': { prerender: true },
// ISR(增量静态再生):缓存 10 分钟
'/video/**': { swr: 600 },
'/category/**': { swr: 3600 },
// SPA:管理后台不需要 SSR
'/admin/**': { ssr: false },
// API 缓存
'/api/tags/popular': { swr: 300 },
'/api/config': { swr: 86400 },
},
})效果:
| 策略 | TTFB | SEO | 适用场景 |
|---|---|---|---|
prerender | 最快 | ✅ | 不变的静态页 |
swr | 快 | ✅ | 低频变化的内容页 |
| SSR(默认) | 中等 | ✅ | 实时数据页 |
ssr: false | 慢 | ❌ | 管理后台 |
3.2 Nitro 预渲染配置
export default defineNuxtConfig({
nitro: {
prerender: {
crawlLinks: true, // 自动爬取页面中的链接
routes: ['/sitemap.xml'],
failOnError: false, // 某个页面失败不阻断构建
},
},
})4. 缓存配置
4.1 API 路由缓存
// server/api/videos/popular.get.ts
export default defineCachedEventHandler(async () => {
const videos = await db.select()
.from(videosTable)
.orderBy(desc(videosTable.viewCount))
.limit(20)
return videos
}, {
maxAge: 60 * 5, // 缓存 5 分钟
swr: true, // 过期后返回旧数据,后台刷新
name: 'popular-videos',
getKey: () => 'popular-videos',
})4.2 客户端数据缓存
// useFetch 配合 getCachedData
const { data } = await useAsyncData('video-list', () => $fetch('/api/videos'), {
getCachedData: (key, nuxtApp) => {
return nuxtApp.payload.data[key] || nuxtApp.static.data[key]
},
})4.3 HTTP 缓存头
// server/api/static-config.get.ts
export default defineEventHandler((event) => {
// 设置强缓存
setResponseHeaders(event, {
'Cache-Control': 'public, max-age=3600, s-maxage=86400',
'CDN-Cache-Control': 'max-age=86400',
})
return { version: '1.0', features: [] }
})4.4 Nitro 存储缓存
// nuxt.config.ts — 配置 Redis 作为缓存存储
export default defineNuxtConfig({
nitro: {
storage: {
cache: {
driver: 'redis',
host: process.env.REDIS_HOST || 'localhost',
port: 6379,
db: 0,
},
},
},
})5. 图片与字体优化
5.1 NuxtImg 自动优化
<template>
<!-- 自动生成 WebP/AVIF、响应式 srcset -->
<NuxtImg
src="/images/hero.jpg"
width="1200"
height="630"
format="webp"
quality="80"
loading="lazy"
sizes="sm:100vw md:50vw lg:33vw"
placeholder
/>
</template>// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/image'],
image: {
quality: 80,
format: ['webp', 'avif'],
screens: {
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
},
},
})5.2 图片懒加载策略
<template>
<!-- 首屏图片:eager 加载 -->
<NuxtImg src="/hero.jpg" loading="eager" fetchpriority="high" />
<!-- 非首屏图片:lazy 加载 -->
<NuxtImg
v-for="video in videos"
:key="video.id"
:src="video.thumbnail"
loading="lazy"
/>
</template>5.3 字体优化
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/fonts'],
fonts: {
defaults: {
weights: [400, 500, 700],
styles: ['normal'],
subsets: ['latin', 'chinese-simplified'],
},
},
})@nuxt/fonts 自动:
- 下载字体到本地(避免第三方请求)
- 生成
font-display: swap(避免 FOIT) - 按需加载字符子集
5.4 关键 CSS 内联
export default defineNuxtConfig({
experimental: {
inlineSSRStyles: true, // 首屏关键 CSS 内联到 HTML
},
})效果:减少一次 CSS 请求,FCP 提升 100-300ms。
6. 第三方脚本延迟加载
6.1 useScript(Nuxt Scripts)
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/scripts'],
})<script setup lang="ts">
// Google Analytics — 延迟加载,不阻塞首屏
const { proxy } = useScript('https://www.googletagmanager.com/gtag/js?id=G-XXXXX', {
trigger: 'idle', // 浏览器空闲时加载
})
</script>6.2 手动延迟加载
<script setup lang="ts">
// 用户交互后才加载(如聊天组件)
const loadChat = ref(false)
function initChat() {
loadChat.value = true
}
</script>
<template>
<button @click="initChat">打开客服</button>
<LazyLiveChatWidget v-if="loadChat" />
</template>6.3 DNS 预解析
export default defineNuxtConfig({
app: {
head: {
link: [
{ rel: 'dns-prefetch', href: 'https://fonts.googleapis.com' },
{ rel: 'dns-prefetch', href: 'https://cdn.example.com' },
{ rel: 'preconnect', href: 'https://api.example.com' },
],
},
},
})7. Core Web Vitals 达标验证
7.1 指标目标
| 指标 | 好 | 需改进 | 差 |
|---|---|---|---|
| LCP (Largest Contentful Paint) | < 2.5s | 2.5-4s | > 4s |
| INP (Interaction to Next Paint) | < 200ms | 200-500ms | > 500ms |
| CLS (Cumulative Layout Shift) | < 0.1 | 0.1-0.25 | > 0.25 |
7.2 LCP 优化清单
- [ ] 首屏大图使用 fetchpriority="high" + loading="eager"
- [ ] 首屏关键 CSS 内联(inlineSSRStyles)
- [ ] 字体使用 font-display: swap
- [ ] 服务端响应时间 < 200ms(TTFB)
- [ ] 避免 render-blocking 的第三方脚本7.3 CLS 优化清单
- [ ] 图片/视频设置明确的 width 和 height
- [ ] 字体加载不导致文字跳动(@nuxt/fonts 自动处理)
- [ ] 动态内容区域预留空间(skeleton / min-height)
- [ ] 广告/嵌入内容使用固定容器7.4 INP 优化清单
- [ ] 事件处理器中的耗时操作使用 requestAnimationFrame 或 Web Worker
- [ ] 长列表使用虚拟滚动(vue-virtual-scroller)
- [ ] 避免在 JS 主线程中做大量 DOM 操作
- [ ] 使用 CSS 动画替代 JS 动画7.5 Lighthouse CI 自动化
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install -g @lhci/cli
- run: pnpm install
- run: pnpm build
- name: Run Lighthouse
run: |
lhci autorun --config=lighthouserc.json
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}// lighthouserc.json
{
"ci": {
"collect": {
"startServerCommand": "node .output/server/index.mjs",
"url": ["http://localhost:3000/", "http://localhost:3000/video/1"],
"numberOfRuns": 3
},
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }],
"categories:accessibility": ["warn", { "minScore": 0.9 }],
"categories:seo": ["warn", { "minScore": 0.9 }]
}
},
"upload": {
"target": "temporary-public-storage"
}
}
}效果:每个 PR 自动运行 Lighthouse,Performance < 90 分自动阻断合并。
本章小结
性能优化 Checklist 七步走:
- 分析:
nuxi analyze看 bundle、检查 payload 大小、Lighthouse 基线 - Bundle:Tree Shaking、手动 chunk 拆分、Lazy 前缀懒加载、CDN 外部化
- 渲染:routeRules 混合渲染(prerender + SWR + SSR + SPA)
- 缓存:defineCachedEventHandler 服务端缓存、getCachedData 客户端缓存、HTTP 缓存头、Redis 存储
- 资源:NuxtImg 自动 WebP/AVIF、字体本地化 + swap、关键 CSS 内联
- 脚本:useScript idle 加载、用户交互触发加载、DNS 预解析
- 验证:LCP < 2.5s、CLS < 0.1、INP < 200ms、Lighthouse CI 自动化守门