Nuxt 性能优化
记录 Nuxt 应用的性能优化策略,涵盖图片优化、代码分割、缓存策略、字体加载等内容。
概述
性能优化是前端开发中的重要环节。对于 Nuxt 应用而言,由于涉及服务端渲染、静态生成、客户端水合等多个环节,性能优化需要从多个维度入手。本文记录了一些实践中的优化策略。
性能指标
Core Web Vitals
Google 定义的 Core Web Vitals 是衡量网页用户体验的核心指标:
| 指标 | 全称 | 含义 | 良好标准 |
|---|---|---|---|
| LCP | Largest Contentful Paint | 最大内容绘制时间 | ≤ 2.5s |
| INP | Interaction to Next Paint | 交互到下次绘制 | ≤ 200ms |
| CLS | Cumulative Layout Shift | 累积布局偏移 | ≤ 0.1 |
测量工具
- Lighthouse:Chrome DevTools 内置,全面评估性能
- PageSpeed Insights:https://pagespeed.web.dev
- WebPageTest:https://www.webpagetest.org
- Chrome DevTools Performance:运行时性能分析
图片优化
图片通常是网页中最大的资源,优化图片对性能提升最为显著。
@nuxt/image 模块
// nuxt.config.ts
image: {
format: ['webp', 'avif', 'jpg'],
quality: 80,
screens: {
xs: 320,
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
xxl: 1536
}
}响应式图片
使用 NuxtImg 组件自动生成响应式图片:
<template>
<NuxtImg
:src="imageUrl"
:alt="alt"
format="webp"
quality="80"
loading="lazy"
sizes="sm:100vw md:50vw lg:800px"
/>
</template>生成的 HTML 会包含 srcset 和 sizes 属性,浏览器会根据设备选择最合适的图片。
首屏图片优先加载
<template>
<!-- 首屏图片:优先加载 -->
<NuxtImg
:src="heroImage"
:alt="heroAlt"
loading="eager"
fetchpriority="high"
format="webp"
quality="90"
/>
<!-- 非首屏图片:懒加载 -->
<NuxtImg
v-for="img in gallery"
:key="img.id"
:src="img.url"
loading="lazy"
format="webp"
/>
</template>轮播图优化
对于轮播图组件,首张图片优先加载,其余图片懒加载:
<template>
<div class="hero-slider">
<div
v-for="(slide, index) in slides"
:key="index"
class="slide"
>
<NuxtImg
:src="slide.image"
:alt="slide.alt"
:loading="index === 0 ? 'eager' : 'lazy'"
:fetchpriority="index === 0 ? 'high' : 'auto'"
format="webp"
quality="90"
/>
</div>
</div>
</template>IPX 配置
@nuxt/image 使用 IPX 进行服务端图片处理。在生产环境中,需要排除 IPX 路由的预渲染:
// nuxt.config.ts
nitro: {
prerender: {
ignore: ['/_ipx/**']
}
}IPX 路由是动态的,依赖请求参数生成图片,预渲染时访问这些路由会导致构建失败。
代码分割
路由级代码分割
Nuxt 默认按页面分割代码,每个页面组件会生成独立的 chunk:
pages/
├── index.vue → index.[hash].js
├── about.vue → about.[hash].js
└── news/
├── index.vue → news-index.[hash].js
└── [slug].vue → news-slug.[hash].js组件懒加载
对于大型组件或非首屏组件,使用懒加载:
<template>
<div>
<!-- 首屏组件:直接导入 -->
<AppHeader />
<!-- 非首屏组件:懒加载 -->
<LazyModal v-if="showModal" />
<LazyGallery v-if="showGallery" />
</div>
</template>Lazy 前缀会让 Nuxt 自动将组件分离为独立的 chunk,在需要时才加载。
条件加载模块
某些模块体积较大但使用频率低,可以条件加载:
// nuxt.config.ts
const enableFeature = process.env.ENABLE_FEATURE === 'true'
export default defineNuxtConfig({
modules: [
enableFeature ? 'heavy-module' : undefined
].filter(Boolean)
})Bundle 分析
使用 rollup-plugin-visualizer 分析打包产物:
// nuxt.config.ts
import { visualizer } from 'rollup-plugin-visualizer'
export default defineNuxtConfig({
vite: {
plugins: process.env.ANALYZE === 'true'
? [
visualizer({
filename: 'stats.html',
gzipSize: true,
brotliSize: true,
open: true
})
]
: []
}
})运行 ANALYZE=true npm run build 会生成可视化报告,帮助识别大型依赖。
缓存策略
ISR(增量静态再生)
// nuxt.config.ts
routeRules: {
// 静态页面:预渲染
'/': { prerender: true },
'/about': { prerender: true },
// 动态内容:ISR
'/news': { swr: 3600 }, // 1 小时
'/news/**': { swr: 3600 },
'/api/news': { swr: 1800 }, // 30 分钟
}SWR(Stale-While-Revalidate)的工作流程:
- 请求到达时检查缓存是否存在
- 如果存在,立即返回缓存内容
- 检查缓存是否过期
- 如果过期,在返回旧缓存的同时,后台重新渲染
- 渲染完成后更新缓存
注意:开发时应禁用缓存,否则修改内容后可能看不到更新。
routeRules: {
'/news': process.env.NODE_ENV === 'development'
? {}
: { swr: 3600 }
}API 响应缓存
对于服务端 API,同样可以应用缓存:
routeRules: {
'/api/news': { swr: 1800 }, // 30 分钟
'/api/cars': { swr: 7200 }, // 2 小时
'/api/events': { swr: 3600 } // 1 小时
}内存缓存
对于频繁访问的数据,可以在服务端实现内存缓存:
// server/utils/cache.ts
interface CacheItem<T> {
data: T
timestamp: number
}
const cache = new Map<string, CacheItem<any>>()
export function useCache<T>(
key: string,
ttl: number,
fetcher: () => Promise<T>
): Promise<T> {
const cached = cache.get(key)
const now = Date.now()
if (cached && (now - cached.timestamp) < ttl) {
return Promise.resolve(cached.data)
}
return fetcher().then(data => {
cache.set(key, { data, timestamp: now })
return data
})
}字体优化
字体加载策略
// nuxt.config.ts
fonts: {
providers: {
google: false // 禁用 Google Fonts(国内网络问题)
},
families: [
{
name: 'Inter',
src: '/fonts/Inter/Inter-VariableFont_opsz-wght.ttf'
},
{
name: 'Outfit',
src: '/fonts/Outfit/Outfit-VariableFont_wght.ttf'
}
]
}字体预加载
对于首屏渲染必需的字体,使用 <link rel="preload"> 预加载:
// nuxt.config.ts
app: {
head: {
link: [
{
rel: 'preload',
href: '/fonts/Inter/Inter-VariableFont_opsz-wght.ttf',
as: 'font',
type: 'font/ttf',
crossorigin: 'anonymous'
}
]
}
}font-display 策略
在 CSS 中设置 font-display 控制字体加载行为:
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter/Inter-VariableFont_opsz-wght.ttf') format('truetype');
font-display: swap;
}font-display: swap 会立即显示后备字体,字体加载后平滑过渡,不阻塞文本渲染。
CSS 优化
CSS 变量
使用 CSS 变量实现主题切换,避免重复加载样式表:
/* variables.css */
:root {
--primary-color: #3b82f6;
--text-color: #1f2937;
--bg-color: #ffffff;
}
[data-theme="dark"] {
--primary-color: #60a5fa;
--text-color: #f3f4f6;
--bg-color: #111827;
}Tailwind CSS
// nuxt.config.ts
vite: {
css: {
devSourcemap: false // 消除 Tailwind CSS v4 sourcemap 警告
}
}Tailwind CSS 文档:https://tailwindcss.com
JavaScript 优化
减少客户端 JavaScript
Nuxt 的 SSR 特性天然减少了客户端 JavaScript 的执行:首屏 HTML 在服务端渲染,客户端仅需水合交互逻辑。
避免不必要的水合
对于纯静态内容,可以使用 <ClientOnly> 组件:
<template>
<ClientOnly>
<HeavyInteractiveComponent />
</ClientOnly>
</template>延迟加载第三方脚本
// nuxt.config.ts
app: {
head: {
script: [
{
src: 'https://analytics.example.com/script.js',
defer: true,
async: true
}
]
}
}预渲染优化
并发控制
当使用 Nuxt Content v3 时,预渲染需要控制并发:
// nuxt.config.ts
nitro: {
prerender: {
concurrency: 1, // 串行处理,避免 SQLite 冲突
failOnError: true,
crawlLinks: true
}
}构建内存优化
大型项目构建时可能遇到内存不足:
NODE_OPTIONS='--max-old-space-size=6144' npm run build将 Node.js 堆内存上限设为 6GB,避免 Nitro 打包阶段 OOM。
jemalloc
sharp/libvips 在某些环境下可能因内存分配器问题崩溃,使用 jemalloc 可以解决:
# 安装 jemalloc
sudo apt-get install -y libjemalloc2
# 构建时使用
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 npm run build运行时性能
虚拟列表
对于长列表,使用虚拟列表只渲染可见项:
<template>
<VirtualScroller :items="items" :item-height="80">
<template #default="{ item }">
<ItemCard :item="item" />
</template>
</VirtualScroller>
</template>防抖与节流
对于频繁触发的事件,使用防抖或节流:
// composables/useDebounce.ts
export function useDebounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
): T {
let timeoutId: ReturnType<typeof setTimeout>
return ((...args: Parameters<T>) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => fn(...args), delay)
}) as T
}动画性能
使用 CSS 动画和 transform 属性,避免触发重排:
/* 推荐:使用 transform */
.card {
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
}性能监控
真实用户监控(RUM)
使用 web-vitals 库收集真实用户数据:
// plugins/web-vitals.ts
import { onLCP, onINP, onCLS } from 'web-vitals'
export default defineNuxtPlugin(() => {
onLCP(console.log)
onINP(console.log)
onCLS(console.log)
})web-vitals 库:https://github.com/GoogleChrome/web-vitals
优化清单
首屏优化
- 首屏图片使用
loading="eager"和fetchpriority="high" - 关键字体预加载
- 延迟非关键 JavaScript
- 使用 SSR 或预渲染
资源优化
- 图片使用 WebP/AVIF 格式
- 响应式图片(srcset)
- 图片懒加载
- 代码分割和懒加载
- 第三方脚本异步加载
缓存优化
- 静态页面预渲染
- 动态页面使用 ISR
- API 响应缓存
- CDN 部署
运行时优化
- 长列表虚拟滚动
- 事件防抖节流
- 动画使用 transform
- 避免强制同步布局
小结
性能优化是一个持续的过程,需要从多个维度入手。测量先行,使用 Lighthouse、WebPageTest 等工具建立基准。图片优化通常是最容易获得显著收益的优化点。代码分割可以减少首屏 JavaScript 体积。合理使用预渲染和 ISR 缓存策略,关注交互响应和动画流畅度。
参考资料:
- 上一篇:Powered By Typecho
- 下一篇:Nuxt 4 核心特性与开发实践