Nuxt 性能优化指南
深度解析 Nuxt 框架性能优化指南,从基础配置到高级优化策略,涵盖 SSR、SSG、ISR、资源优化、缓存策略、性能监控等核心技术,构建高性能 Web 应用
概述
在现代 Web 开发中,性能优化已成为决定应用成功的关键因素。据 Google 研究显示,页面加载时间超过 3 秒将导致 53% 的用户流失,而优化后的应用可以显著提升转化率和用户体验。Nuxt 框架基于 Vue.js 构建,提供了完整的性能优化工具链,从服务端渲染到客户端优化,从构建时优化到运行时性能调优。
本指南将深入探讨 Nuxt 性能优化的全链路实践,帮助开发者掌握从理论到实践的完整优化体系。
🎯 核心目标
- 掌握 Nuxt 性能优化的全链路方法论和最佳实践
- 深入理解渲染模式、缓存策略和资源优化的技术原理
- 学会构建高效的监控体系和性能诊断方法
- 掌握企业级应用的性能优化策略和架构设计
💡 技术架构体系
- Nitro 引擎: 统一的服务端运行时和优化机制
- Vite/Webpack: 现代化构建工具和打包优化
- Vue 3: 组合式 API 和响应式系统优化
- 边缘计算: CDN 和 Edge Runtime 性能提升
- 智能缓存: 多层次缓存策略和缓存失效机制
性能优化理念: Nuxt 的性能优化基于 "渐进式增强" 和 "按需加载" 原则,通过智能的代码分割、缓存策略和渲染模式,实现最佳的首屏加载时间和交互响应性能。
第一部分:性能优化基础理论
Web 性能关键指标深入解析
现代 Web 性能评估基于 Core Web Vitals 和其他关键指标,了解这些指标对于制定有效的优化策略至关重要。
Core Web Vitals 指标详解
Largest Contentful Paint (LCP)
LCP(最大内容绘制)是衡量用户感知加载速度的核心指标,用于记录视窗内最大内容元素(如图像、视频、文本块)的渲染时间点。
关键要素解析:
- 内容类型:包括但不限于:
- 图片/视频
- 页面主标题(H1)
- 关键信息横幅
- 首屏产品卡片
- 测量标准:
- 元素必须可见且在视窗内
- 内容需为实际渲染内容(非背景或占位符)
- 元素尺寸需占视窗面积 30% 以上
技术原理:
- 浏览器渲染引擎会持续追踪内容元素的尺寸和位置变化
- 当布局稳定后(无后续 DOM 变更影响),记录最大元素的渲染时间
- 测量时间点包含网络请求、资源解码、渲染管线执行等全链路耗时
优化关注点:
- 消除渲染阻塞资源(Render-blocking Resources)
- 优化关键渲染路径(Critical Rendering Path)
- 确保文本内容使用正确的字体加载策略(FOIT/FOUT)
- 优先保障首屏内容的 CSS 交付优化(Critical CSS)
优化目标
- 目标值: ≤ 2.5 秒
- 技术原理: 衡量页面主要内容的渲染时间
- 优化策略: 服务端渲染、图像优化、关键资源预加载
// LCP 优化示例:关键资源预加载
export default defineNuxtConfig({
app: {
head: {
link: [
{
rel: 'preload',
href: '/images/hero-image.webp',
as: 'image',
fetchpriority: 'high'
}
]
}
}
})
First Input Delay (FID) / Interaction to Next Paint (INP)
- 目标值: FID ≤ 100ms, INP ≤ 200ms
- 技术原理: 衡量用户交互响应速度
- 优化策略: 代码分割、主线程优化、Web Workers
// 主线程优化:使用 Web Workers 处理计算密集型任务
// composables/useWebWorker.ts
export const useWebWorker = () => {
const processData = async (data: any) => {
if (process.client) {
const worker = new Worker('/workers/data-processor.js')
return new Promise((resolve) => {
worker.postMessage(data)
worker.onmessage = (e) => {
resolve(e.data)
worker.terminate()
}
})
}
}
return { processData }
}
Cumulative Layout Shift (CLS)
- 目标值: ≤ 0.1
- 技术原理: 衡量视觉稳定性
- 优化策略: 尺寸预定义、骨架屏、字体优化
<!-- CLS 优化:预定义图片尺寸 -->
<template>
<div>
<NuxtImg
src="/images/product.jpg"
width="400"
height="300"
:style="{ aspectRatio: '4/3' }"
loading="lazy"
placeholder
/>
</div>
</template>
Nuxt 性能优化技术栈分析
Nitro 引擎核心机制
Nitro 是 Nuxt 3 的服务端引擎,提供了统一的服务端运行时环境和优化机制:
// nitro.config.ts - Nitro 优化配置
export default defineNitroConfig({
// 预渲染配置
prerender: {
routes: ['/sitemap.xml'],
crawlLinks: true
},
// 压缩配置
compression: true,
// 实验性功能
experimental: {
wasm: true
},
// 路由规则
routeRules: {
'/': { prerender: true },
'/api/**': { headers: { 'cache-control': 's-maxage=60' } },
'/admin/**': { ssr: false },
'/blog/**': { isr: true }
}
})
渲染模式性能对比分析
渲染模式 | 首屏时间 | SEO 友好 | 服务器负载 | 缓存策略 | 适用场景 |
---|---|---|---|---|---|
SSR | 快 | 优秀 | 高 | 动态 | 内容网站、电商 |
SSG | 最快 | 优秀 | 低 | 静态 | 博客、文档 |
SPA | 慢 | 差 | 低 | 客户端 | 管理后台 |
ISR | 快 | 优秀 | 中 | 智能 | 大型网站 |
混合渲染 | 优化 | 灵活 | 可控 | 分层 | 企业应用 |
第二部分:构建时性能优化
代码分割和懒加载策略
自动代码分割机制
Nuxt 基于路由自动进行代码分割,但我们可以进一步优化:
// nuxt.config.ts - 高级代码分割配置
export default defineNuxtConfig({
build: {
splitChunks: {
layouts: true,
pages: true,
commons: true
}
},
// Vite 优化配置
vite: {
build: {
rollupOptions: {
output: {
manualChunks(id) {
// 第三方库分离
if (id.includes('node_modules')) {
if (id.includes('vue') || id.includes('@vue')) {
return 'vue-vendor'
}
if (id.includes('lodash')) {
return 'lodash'
}
return 'vendor'
}
}
}
}
}
}
})
组件级懒加载最佳实践
<!-- pages/products/index.vue -->
<template>
<div>
<ProductHeader />
<!-- 延迟加载非关键组件 -->
<ClientOnly>
<LazyProductRecommendations />
<template #fallback>
<div class="skeleton-loader">
<div class="skeleton-item" v-for="i in 4" :key="i" />
</div>
</template>
</ClientOnly>
<!-- 条件懒加载 -->
<LazyProductReviews v-if="showReviews" />
</div>
</template>
<script setup>
// 异步组件定义
const LazyProductRecommendations = defineAsyncComponent(() =>
import('~/components/ProductRecommendations.vue')
)
const showReviews = ref(false)
// 延迟加载评论组件
onMounted(() => {
setTimeout(() => {
showReviews.value = true
}, 2000)
})
</script>
动态导入优化策略
// composables/useModuleLoader.ts
export const useModuleLoader = () => {
const loadModule = async <T>(modulePath: string): Promise<T> => {
try {
// 使用动态导入并添加预取提示
const module = await import(/* webpackChunkName: "dynamic-module" */ modulePath)
return module.default
} catch (error) {
console.error(`Failed to load module: ${modulePath}`, error)
throw error
}
}
const preloadModule = (modulePath: string) => {
// 预加载模块以提升后续加载速度
if (process.client) {
const link = document.createElement('link')
link.rel = 'modulepreload'
link.href = modulePath
document.head.appendChild(link)
}
}
return { loadModule, preloadModule }
}
资源优化和压缩
图像优化全方位策略
// nuxt.config.ts - 图像优化配置
export default defineNuxtConfig({
modules: ['@nuxt/image'],
image: {
// 图像格式优化
formats: {
avif: {
quality: 80
},
webp: {
quality: 85
}
},
// 响应式图像配置
screens: {
xs: 320,
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
xxl: 1536
},
// 图像提供商配置
providers: {
cloudinary: {
baseURL: 'https://res.cloudinary.com/your-cloud/image/fetch/'
}
},
// 预设配置
presets: {
hero: {
modifiers: {
format: 'webp',
quality: 90,
width: 1200,
height: 600
}
},
thumbnail: {
modifiers: {
format: 'webp',
quality: 80,
width: 200,
height: 200
}
}
}
}
})
<!-- 高性能图像组件 -->
<template>
<div class="responsive-image-container">
<NuxtPicture
:src="imageSrc"
:alt="imageAlt"
:preset="imagePreset"
:loading="imageLoading"
:fetchpriority="fetchPriority"
:sizes="responsiveSizes"
@load="onImageLoad"
@error="onImageError"
/>
</div>
</template>
<script setup lang="ts">
interface Props {
imageSrc: string
imageAlt: string
imagePreset?: 'hero' | 'thumbnail' | 'default'
imageLoading?: 'lazy' | 'eager'
fetchPriority?: 'high' | 'low' | 'auto'
responsiveSizes?: string
}
const props = withDefaults(defineProps<Props>(), {
imagePreset: 'default',
imageLoading: 'lazy',
fetchPriority: 'auto',
responsiveSizes: '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw'
})
const onImageLoad = () => {
// 图像加载完成后的处理
console.log('Image loaded successfully')
}
const onImageError = () => {
// 图像加载失败的处理
console.error('Failed to load image')
}
</script>
字体优化和加载策略
// nuxt.config.ts - 字体优化配置
export default defineNuxtConfig({
modules: ['@nuxtjs/google-fonts'],
googleFonts: {
families: {
'Inter': [400, 500, 600, 700],
'JetBrains Mono': [400, 500]
},
display: 'swap',
preload: true,
useStylesheet: true,
download: true,
base64: false
},
app: {
head: {
link: [
// 字体预连接
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{ rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' }
]
}
}
})
CSS 优化和关键路径
// nuxt.config.ts - CSS 优化配置
export default defineNuxtConfig({
css: [
'~/assets/css/critical.css', // 关键 CSS
'~/assets/css/main.css'
],
build: {
// CSS 提取和优化
extractCSS: {
ignoreOrder: true
}
},
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "~/assets/scss/variables.scss" as *;'
}
}
}
}
})
// assets/scss/critical.scss - 关键路径 CSS
// 首屏必需的样式
.layout-header {
position: fixed;
top: 0;
width: 100%;
z-index: 1000;
height: 64px;
background: white;
border-bottom: 1px solid #e5e7eb;
}
.hero-section {
padding-top: 64px;
min-height: 60vh;
display: flex;
align-items: center;
justify-content: center;
}
// 骨架屏样式
.skeleton-loader {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.skeleton-item {
height: 200px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 8px;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
Tree Shaking 和依赖优化
智能依赖分析和优化
// nuxt.config.ts - Tree Shaking 优化
export default defineNuxtConfig({
build: {
// 分析包大小
analyze: process.env.ANALYZE === 'true',
// 优化依赖
optimization: {
treeShake: true,
minimize: true
}
},
// 自动导入优化
imports: {
// 禁用全局导入,按需导入
global: false,
// 自定义导入
imports: [
{ from: 'lodash-es', name: 'debounce', as: 'debounce' },
{ from: 'date-fns', name: 'format', as: 'formatDate' }
]
}
})
// utils/optimized-imports.ts - 优化的导入策略
// 错误示例:导入整个库
// import _ from 'lodash'
// 正确示例:按需导入
import { debounce, throttle } from 'lodash-es'
import { format, addDays } from 'date-fns'
// 动态导入大型库
export const loadChartLibrary = async () => {
const { Chart } = await import('chart.js/auto')
return Chart
}
// 条件导入
export const loadDevTools = async () => {
if (process.dev) {
const { setupDevtoolsPlugin } = await import('@vue/devtools-api')
return setupDevtoolsPlugin
}
}
第三部分:运行时性能优化
服务端渲染优化
SSR 渲染优化策略
// server/api/performance/[...slug].ts
export default defineEventHandler(async (event) => {
const start = Date.now()
try {
// 缓存检查
const cached = await useStorage('redis').getItem(getRequestURL(event).pathname)
if (cached) {
setHeader(event, 'X-Cache', 'HIT')
setHeader(event, 'X-Response-Time', `${Date.now() - start}ms`)
return cached
}
// 数据获取优化
const [userData, contentData] = await Promise.all([
$fetch('/api/user'),
$fetch('/api/content')
])
const result = {
user: userData,
content: contentData,
timestamp: Date.now()
}
// 设置缓存
await useStorage('redis').setItem(
getRequestURL(event).pathname,
result,
{ ttl: 300 } // 5分钟缓存
)
setHeader(event, 'X-Cache', 'MISS')
setHeader(event, 'X-Response-Time', `${Date.now() - start}ms`)
return result
} catch (error) {
throw createError({
statusCode: 500,
statusMessage: 'Internal Server Error'
})
}
})
数据获取优化模式
<!-- pages/products/[id].vue -->
<template>
<div>
<ProductDetail :product="data.product" />
<ProductReviews :reviews="data.reviews" />
<ProductRecommendations :recommendations="data.recommendations" />
</div>
</template>
<script setup>
// 并行数据获取
const { params } = useRoute()
const { data, error, pending } = await useLazyAsyncData(
`product-${params.id}`,
async () => {
// 并行获取所有需要的数据
const [product, reviews, recommendations] = await Promise.all([
$fetch(`/api/products/${params.id}`),
$fetch(`/api/products/${params.id}/reviews`),
$fetch(`/api/products/${params.id}/recommendations`)
])
return {
product,
reviews,
recommendations
}
},
{
// 缓存配置
server: true,
client: true,
default: () => ({
product: null,
reviews: [],
recommendations: []
})
}
)
// SEO 优化
useHead({
title: () => data.value?.product?.title || 'Product',
meta: [
{
name: 'description',
content: () => data.value?.product?.description || 'Product description'
}
]
})
</script>
客户端性能优化
水合(Hydration)优化
<!-- components/OptimizedComponent.vue -->
<template>
<div>
<!-- 关键内容立即渲染 -->
<h1>{{ title }}</h1>
<p>{{ description }}</p>
<!-- 非关键组件延迟水合 -->
<ClientOnly>
<InteractiveChart :data="chartData" />
<template #fallback>
<div class="chart-skeleton">Loading chart...</div>
</template>
</ClientOnly>
<!-- 条件性组件 -->
<LazyModal v-if="showModal" @close="showModal = false" />
</div>
</template>
<script setup>
// 使用 ref 而不是 reactive 提升性能
const title = ref('')
const description = ref('')
const chartData = ref([])
const showModal = ref(false)
// 优化的数据获取
onMounted(async () => {
// 分批加载数据
const basicData = await $fetch('/api/basic-data')
title.value = basicData.title
description.value = basicData.description
// 延迟加载图表数据
setTimeout(async () => {
chartData.value = await $fetch('/api/chart-data')
}, 100)
})
</script>
状态管理优化
// stores/performance.ts
export const usePerformanceStore = defineStore('performance', () => {
// 使用 Map 提升查找性能
const cache = new Map<string, any>()
const loadingStates = new Set<string>()
// 防抖的数据获取
const debouncedFetch = debounce(async (key: string, fetcher: Function) => {
if (cache.has(key)) {
return cache.get(key)
}
if (loadingStates.has(key)) {
return new Promise(resolve => {
const interval = setInterval(() => {
if (cache.has(key)) {
clearInterval(interval)
resolve(cache.get(key))
}
}, 50)
})
}
loadingStates.add(key)
try {
const result = await fetcher()
cache.set(key, result)
return result
} finally {
loadingStates.delete(key)
}
}, 100)
// 内存管理
const clearCache = () => {
cache.clear()
loadingStates.clear()
}
// 缓存大小限制
const limitCacheSize = (maxSize: number = 50) => {
if (cache.size > maxSize) {
const keys = Array.from(cache.keys())
const keysToDelete = keys.slice(0, cache.size - maxSize)
keysToDelete.forEach(key => cache.delete(key))
}
}
return {
debouncedFetch,
clearCache,
limitCacheSize,
getCacheSize: () => cache.size
}
})
缓存策略深度优化
多层缓存架构设计
// utils/cache-manager.ts
interface CacheConfig {
ttl?: number
storage?: 'memory' | 'sessionStorage' | 'localStorage' | 'redis'
namespace?: string
}
export class CacheManager {
private memoryCache = new Map<string, { data: any; expires: number }>()
private readonly defaultTTL = 300000 // 5分钟
async get<T>(key: string, config?: CacheConfig): Promise<T | null> {
const fullKey = this.getFullKey(key, config?.namespace)
// 1. 检查内存缓存
const memoryResult = this.getFromMemory<T>(fullKey)
if (memoryResult !== null) return memoryResult
// 2. 检查浏览器存储
if (process.client && config?.storage) {
const browserResult = this.getFromBrowser<T>(fullKey, config.storage)
if (browserResult !== null) {
// 回填内存缓存
this.setToMemory(fullKey, browserResult, config?.ttl)
return browserResult
}
}
// 3. 检查服务端缓存 (Redis)
if (process.server && config?.storage === 'redis') {
const redisResult = await this.getFromRedis<T>(fullKey)
if (redisResult !== null) return redisResult
}
return null
}
async set<T>(
key: string,
data: T,
config?: CacheConfig
): Promise<void> {
const fullKey = this.getFullKey(key, config?.namespace)
const ttl = config?.ttl || this.defaultTTL
// 设置内存缓存
this.setToMemory(fullKey, data, ttl)
// 设置浏览器存储
if (process.client && config?.storage) {
this.setBrowser(fullKey, data, config.storage, ttl)
}
// 设置服务端缓存
if (process.server && config?.storage === 'redis') {
await this.setRedis(fullKey, data, ttl)
}
}
private getFromMemory<T>(key: string): T | null {
const cached = this.memoryCache.get(key)
if (cached && cached.expires > Date.now()) {
return cached.data
}
if (cached) {
this.memoryCache.delete(key)
}
return null
}
private setToMemory<T>(key: string, data: T, ttl: number): void {
this.memoryCache.set(key, {
data,
expires: Date.now() + ttl
})
// 清理过期缓存
this.cleanupMemoryCache()
}
private cleanupMemoryCache(): void {
const now = Date.now()
for (const [key, value] of this.memoryCache.entries()) {
if (value.expires <= now) {
this.memoryCache.delete(key)
}
}
}
private getFullKey(key: string, namespace?: string): string {
return namespace ? `${namespace}:${key}` : key
}
}
// 单例模式
export const cacheManager = new CacheManager()
智能缓存策略实现
// composables/useSmartCache.ts
export const useSmartCache = () => {
const getCacheStrategy = (path: string) => {
// 基于路径确定缓存策略
if (path.startsWith('/api/static/')) {
return { ttl: 86400000, storage: 'localStorage' } // 1天
}
if (path.startsWith('/api/user/')) {
return { ttl: 300000, storage: 'sessionStorage' } // 5分钟
}
if (path.startsWith('/api/realtime/')) {
return { ttl: 30000, storage: 'memory' } // 30秒
}
return { ttl: 600000, storage: 'memory' } // 默认10分钟
}
const cachedFetch = async <T>(
url: string,
options?: RequestInit
): Promise<T> => {
const cacheKey = `fetch:${url}:${JSON.stringify(options)}`
const strategy = getCacheStrategy(url)
// 尝试从缓存获取
const cached = await cacheManager.get<T>(cacheKey, strategy)
if (cached !== null) {
return cached
}
// 执行请求
const result = await $fetch<T>(url, options)
// 存储到缓存
await cacheManager.set(cacheKey, result, strategy)
return result
}
return { cachedFetch }
}
第四部分:高级优化技术
Nitro 引擎深度优化
边缘渲染和分布式部署
// nitro.config.ts - 边缘优化配置
export default defineNitroConfig({
// 边缘函数配置
experimental: {
wasm: true,
legacyExternals: false
},
// 预设配置用于不同平台
preset: 'cloudflare-pages', // 或 'vercel-edge', 'netlify-edge'
// 路由规则优化
routeRules: {
'/': {
prerender: true,
headers: { 'cache-control': 's-maxage=31536000' }
},
'/api/edge/**': {
headers: { 'cache-control': 's-maxage=60' }
},
'/blog/**': {
isr: 86400, // 24小时增量静态再生
headers: { 'cache-control': 's-maxage=86400' }
}
},
// 压缩和优化
minify: true,
sourceMap: false,
// 存储配置
storage: {
redis: {
driver: 'redis',
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD
}
}
})
服务端组件和岛屿架构
<!-- components/ServerOnlyComponent.vue -->
<template>
<div class="server-component">
<h2>{{ data.title }}</h2>
<p>{{ data.description }}</p>
<!-- 服务端渲染的复杂内容 -->
<div v-html="data.processedContent"></div>
</div>
</template>
<script setup>
// 服务端专用组件,不会发送到客户端
defineOptions({
_hydrationKey: false // 禁用水合
})
const { data } = await $fetch('/api/server-data')
// 服务端处理复杂逻辑
const processContent = (content: string) => {
// 复杂的内容处理逻辑
return content.replace(/\[([^\]]+)\]/g, '<strong>$1</strong>')
}
data.processedContent = processContent(data.content)
</script>
<!-- pages/hybrid-example.vue -->
<template>
<div>
<!-- 服务端组件 -->
<NuxtIsland name="ServerOnlyComponent" />
<!-- 客户端交互组件 -->
<ClientOnly>
<InteractiveWidget />
</ClientOnly>
<!-- 混合组件 -->
<HybridComponent :initial-data="serverData" />
</div>
</template>
<script setup>
// 服务端数据预取
const { data: serverData } = await useFetch('/api/initial-data')
</script>
模块化性能优化
关键性能模块集成
// nuxt.config.ts - 性能模块配置
export default defineNuxtConfig({
modules: [
'@nuxt/image',
'@nuxtjs/fontaine',
'nuxt-delay-hydration',
'@nuxtjs/partytown',
'@nuxt/scripts'
],
// 图像优化模块
image: {
provider: 'cloudinary',
cloudinary: {
baseURL: 'https://res.cloudinary.com/your-cloud/image/fetch/'
},
presets: {
cover: {
modifiers: {
fit: 'cover',
format: 'avif,webp,jpg',
quality: 'auto:best'
}
}
}
},
// 字体优化模块
fontMetrics: {
fonts: ['Inter', 'JetBrains Mono']
},
// 延迟水合模块
delayHydration: {
mode: 'mount',
debug: process.env.NODE_ENV === 'development'
},
// Partytown 配置(第三方脚本优化)
partytown: {
forward: ['dataLayer.push']
},
// 脚本优化模块
scripts: {
registry: {
googleAnalytics: {
id: 'G-XXXXXXXXXX'
}
}
}
})
自定义性能优化插件
// plugins/performance-monitor.client.ts
export default defineNuxtPlugin(() => {
if (process.client) {
// 性能监控
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// 发送性能数据到分析服务
if (entry.name === 'largest-contentful-paint') {
sendMetric('lcp', entry.startTime)
}
if (entry.name === 'first-input-delay') {
sendMetric('fid', entry.processingStart - entry.startTime)
}
if (entry.name === 'cumulative-layout-shift') {
sendMetric('cls', entry.value)
}
}
})
observer.observe({ type: 'largest-contentful-paint', buffered: true })
observer.observe({ type: 'first-input', buffered: true })
observer.observe({ type: 'layout-shift', buffered: true })
// 自定义性能指标
const measureNavigationTiming = () => {
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming
const metrics = {
dns: navigation.domainLookupEnd - navigation.domainLookupStart,
tcp: navigation.connectEnd - navigation.connectStart,
ttfb: navigation.responseStart - navigation.requestStart,
download: navigation.responseEnd - navigation.responseStart,
domLoad: navigation.domContentLoadedEventEnd - navigation.navigationStart,
windowLoad: navigation.loadEventEnd - navigation.navigationStart
}
sendMetrics(metrics)
}
// 页面加载完成后测量
window.addEventListener('load', measureNavigationTiming)
}
})
const sendMetric = (name: string, value: number) => {
if (navigator.sendBeacon) {
const data = JSON.stringify({ metric: name, value, timestamp: Date.now() })
navigator.sendBeacon('/api/metrics', data)
}
}
const sendMetrics = (metrics: Record<string, number>) => {
if (navigator.sendBeacon) {
const data = JSON.stringify({ metrics, timestamp: Date.now() })
navigator.sendBeacon('/api/metrics', data)
}
}
监控和诊断体系
性能监控仪表板
// server/api/metrics.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// 存储性能指标
await useStorage('redis').setItem(
`metrics:${Date.now()}`,
{
...body,
userAgent: getHeader(event, 'user-agent'),
ip: getClientIP(event),
timestamp: Date.now()
}
)
return { status: 'ok' }
})
<!-- components/PerformanceDashboard.vue -->
<template>
<div class="performance-dashboard">
<div class="metrics-grid">
<MetricCard
title="Largest Contentful Paint"
:value="metrics.lcp"
unit="ms"
:threshold="2500"
/>
<MetricCard
title="First Input Delay"
:value="metrics.fid"
unit="ms"
:threshold="100"
/>
<MetricCard
title="Cumulative Layout Shift"
:value="metrics.cls"
unit=""
:threshold="0.1"
/>
<MetricCard
title="Time to First Byte"
:value="metrics.ttfb"
unit="ms"
:threshold="800"
/>
</div>
<PerformanceChart :data="chartData" />
</div>
</template>
<script setup>
const { data: metrics } = await useFetch('/api/performance/summary')
const { data: chartData } = await useFetch('/api/performance/chart')
</script>
实时性能诊断工具
// composables/usePerformanceDiagnostics.ts
export const usePerformanceDiagnostics = () => {
const diagnostics = ref({
bundleSize: 0,
unusedCSS: [],
largeImages: [],
slowQueries: [],
memoryUsage: 0
})
const runDiagnostics = async () => {
if (process.client) {
// 检查包大小
diagnostics.value.bundleSize = await getBundleSize()
// 检查未使用的 CSS
diagnostics.value.unusedCSS = await getUnusedCSS()
// 检查大图像
diagnostics.value.largeImages = await getLargeImages()
// 检查内存使用
diagnostics.value.memoryUsage = getMemoryUsage()
}
if (process.server) {
// 检查慢查询
diagnostics.value.slowQueries = await getSlowQueries()
}
}
const getBundleSize = async (): Promise<number> => {
const entries = performance.getEntriesByType('resource')
return entries
.filter(entry => entry.name.includes('.js'))
.reduce((total, entry) => total + (entry as any).transferSize, 0)
}
const getUnusedCSS = async (): Promise<string[]> => {
const stylesheets = Array.from(document.styleSheets)
const unusedRules: string[] = []
for (const stylesheet of stylesheets) {
try {
const rules = Array.from(stylesheet.cssRules)
for (const rule of rules) {
if (rule instanceof CSSStyleRule) {
if (!document.querySelector(rule.selectorText)) {
unusedRules.push(rule.selectorText)
}
}
}
} catch (e) {
// Cross-origin stylesheets
}
}
return unusedRules
}
const getLargeImages = async (): Promise<Array<{src: string, size: number}>> => {
const images = Array.from(document.images)
const largeImages: Array<{src: string, size: number}> = []
for (const img of images) {
const entry = performance.getEntriesByName(img.src)[0] as any
if (entry && entry.transferSize > 100000) { // 100KB
largeImages.push({
src: img.src,
size: entry.transferSize
})
}
}
return largeImages
}
const getMemoryUsage = (): number => {
return (performance as any).memory?.usedJSHeapSize || 0
}
const getSlowQueries = async (): Promise<string[]> => {
// 服务端查询性能监控
return await $fetch('/api/performance/slow-queries')
}
return {
diagnostics: readonly(diagnostics),
runDiagnostics
}
}
第五部分:企业级性能优化实践
大规模应用性能优化
微前端架构下的性能优化
// nuxt.config.ts - 微前端配置
export default defineNuxtConfig({
// 模块联邦配置
vite: {
build: {
rollupOptions: {
external: ['vue', '@vue/shared'],
output: {
globals: {
vue: 'Vue'
}
}
}
},
plugins: [
// 模块联邦插件
federation({
name: 'host-app',
remotes: {
'micro-app-1': 'http://localhost:3001/assets/remoteEntry.js',
'micro-app-2': 'http://localhost:3002/assets/remoteEntry.js'
},
shared: {
vue: {
singleton: true,
requiredVersion: '^3.0.0'
}
}
})
]
},
// 路由配置
hooks: {
'render:route': (url, result, context) => {
// 动态路由分发到微应用
if (url.startsWith('/micro-app-1')) {
return loadMicroApp('micro-app-1', url, context)
}
}
}
})
多环境性能优化策略
// config/performance.config.ts
interface PerformanceConfig {
cacheStrategy: 'aggressive' | 'moderate' | 'minimal'
bundleSplitting: boolean
imageOptimization: boolean
serverRendering: boolean
}
const getPerformanceConfig = (): PerformanceConfig => {
const env = process.env.NODE_ENV
const isProduction = env === 'production'
return {
cacheStrategy: isProduction ? 'aggressive' : 'minimal',
bundleSplitting: isProduction,
imageOptimization: isProduction,
serverRendering: isProduction
}
}
export const performanceConfig = getPerformanceConfig()
持续性能优化流程
CI/CD 性能检查
# .github/workflows/performance.yml
name: Performance Check
on:
pull_request:
branches: [main]
jobs:
performance-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Run Lighthouse CI
run: |
npm install -g @lhci/cli
lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
- name: Bundle size check
run: |
npm run build:analyze
node scripts/bundle-size-check.js
- name: Performance regression test
run: npm run test:performance
// scripts/bundle-size-check.js
const fs = require('fs')
const path = require('path')
const MAX_BUNDLE_SIZE = 250 * 1024 // 250KB
const BUILD_DIR = '.output/public/_nuxt'
const checkBundleSize = () => {
const files = fs.readdirSync(BUILD_DIR)
const jsFiles = files.filter(file => file.endsWith('.js'))
let totalSize = 0
const largeBundles = []
jsFiles.forEach(file => {
const filePath = path.join(BUILD_DIR, file)
const stats = fs.statSync(filePath)
const size = stats.size
totalSize += size
if (size > MAX_BUNDLE_SIZE) {
largeBundles.push({ file, size })
}
})
console.log(`Total JS bundle size: ${(totalSize / 1024).toFixed(2)}KB`)
if (largeBundles.length > 0) {
console.error('Large bundles detected:')
largeBundles.forEach(({ file, size }) => {
console.error(`- ${file}: ${(size / 1024).toFixed(2)}KB`)
})
process.exit(1)
}
console.log('✅ Bundle size check passed')
}
checkBundleSize()
最佳实践总结
性能优化检查清单
构建时优化
- 启用代码分割和 Tree Shaking
- 配置图像和字体优化
- 实施 CSS 优化和关键路径分离
- 设置依赖分析和包大小监控
- 配置压缩和最小化
运行时优化
- 实施智能缓存策略
- 优化数据获取和状态管理
- 配置服务端渲染优化
- 实施懒加载和延迟水合
- 设置性能监控和诊断
部署和监控
- 配置 CDN 和边缘计算
- 设置性能监控仪表板
- 实施 CI/CD 性能检查
- 配置错误监控和告警
- 建立性能优化迭代流程
性能优化注意事项
重要提醒:
- 性能优化是一个持续的过程,需要根据实际业务场景进行调整
- 过度优化可能导致代码复杂度增加和维护困难
- 始终以用户体验为中心,平衡性能和功能需求
- 定期进行性能测试和监控,及时发现和解决性能问题
推荐的性能优化工具
- 分析工具: Lighthouse, WebPageTest, Chrome DevTools
- 监控工具: Google Analytics, Sentry, LogRocket
- 构建工具: Vite, Webpack Bundle Analyzer
- 图像优化: Cloudinary, ImageKit, TinyPNG
- CDN服务: Cloudflare, AWS CloudFront, Vercel
通过系统性地应用本指南中的优化策略,您可以构建出高性能、可扩展的 Nuxt 应用,为用户提供卓越的 Web 体验。记住,性能优化是一个持续的过程,需要根据应用的发展和用户反馈不断调整和改进。