去年接手公司一个 Vue 3 电商项目时,首次 Lighthouse 测试结果让我这个自诩"性能优化老手"的开发者也吃了一惊——性能评分只有 72 分,LCP 高达 4.3 秒,CLS 更是达到灾难级的 0.25。用户反馈验证了数据:移动端跳出率 58%,商品详情页转化率不足 1.2%。经过两个月系统性优化,我们最终实现了 Lighthouse 全项 100 分,核心指标提升幅度最高达 92%。这不是简单的分数游戏,而是实打实的业务提升:转化率提升 12%,年度 GMV 增加 230 万美元。
在电商场景中,每个性能指标都直接关联用户行为和商业价值:
Vue 3 + Vite 的组合虽然天生比传统方案更快,但在实际企业级项目中仍面临特殊挑战:
传统按路由分割在大型后台系统中会导致过多碎片化 chunk。我们创新性地采用业务域分组:
typescript复制// router/optimizedRoutes.ts
const routes = [
{
path: '/dashboard',
component: () => import(/* webpackChunkName: "business-domain" */ '@/views/dashboard/index.vue'),
children: [
// 所有仪表板相关路由共享同一 chunk
{ path: 'analytics', component: () => import('@/views/dashboard/Analytics.vue') },
{ path: 'reports', component: () => import('@/views/dashboard/Reports.vue') }
]
}
]
实测效果:chunk 数量减少 40%,缓存命中率提升 65%
通过 Chrome 的 Coverage 工具分析组件使用频率,建立加载优先级模型:
vue复制<script setup>
const loadRecommendations = () => {
if ('requestIdleCallback' in window) {
requestIdleCallback(() => import('@/components/Recommendations.vue'))
} else {
setTimeout(() => import('@/components/Recommendations.vue'), 2000)
}
}
</script>
使用 critters 的进阶配置实现组件级关键 CSS 提取:
typescript复制// vite.config.ts
import critters from 'critters'
export default defineConfig({
plugins: [
critters({
preload: 'swap',
pruneSource: true,
mergeStylesheets: true,
additionalStylesheets: ['@/assets/css/core.css']
})
]
})
配合 Vue 的 <style> 作用域,实现首屏 CSS 体积从 48KB 降到 14KB
开发自适应骨架屏组件,根据容器尺寸动态生成占位元素:
vue复制<!-- components/SmartSkeleton.vue -->
<template>
<div class="skeleton-container" :style="{ width, height }">
<template v-for="i in lineCount">
<div
class="skeleton-line"
:style="{
width: lineWidths[i-1] || '100%',
height: lineHeight + 'px'
}"
></div>
</template>
</div>
</template>
<script setup>
defineProps({
width: { type: String, default: '100%' },
height: { type: String, default: 'auto' },
lineCount: { type: Number, default: 3 },
lineHeight: { type: Number, default: 16 },
lineWidths: { type: Array, default: () => ['100%', '80%', '60%'] }
})
</script>
通过 navigator.connection 实现条件加载:
javascript复制// utils/imageLoader.js
export function getOptimalImageUrl(baseUrl) {
const connection = navigator.connection
let suffix = ''
if (connection) {
if (connection.saveData) suffix = '_lowres'
else if (connection.effectiveType === '4g') suffix = '_hd'
else suffix = '_std'
}
return `${baseUrl}${suffix}.webp`
}
在 Nginx 层实现自动格式转换:
nginx复制# /etc/nginx/conf.d/webp.conf
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
server {
location ~* ^/.+\.(png|jpe?g)$ {
add_header Vary Accept;
try_files $uri$webp_suffix $uri =404;
}
}
采用 FOFT (Flash of Faux Text) 策略优化字体加载:
css复制/* 分三个阶段加载字体 */
@font-face {
font-family: 'Primary';
src: local('Primary Regular'), local('Primary-Regular');
font-display: swap;
}
@font-face {
font-family: 'Primary Fallback';
src: local('Arial');
font-display: swap;
}
body {
font-family: 'Primary Fallback', sans-serif;
}
.font-loaded body {
font-family: 'Primary', sans-serif;
}
配合 JavaScript 的字体加载检测:
javascript复制document.fonts.load('1em Primary').then(() => {
document.documentElement.classList.add('font-loaded')
})
解决 Vue 组件卸载后缓存残留问题:
typescript复制// utils/weakCache.ts
const cache = new Map<string, WeakRef<any>>()
export function getCache(key: string) {
const ref = cache.get(key)
if (ref) {
const value = ref.deref()
if (value !== undefined) return value
cache.delete(key)
}
return null
}
export function setCache(key: string, value: any) {
cache.set(key, new WeakRef(value))
}
开发自动清理的 Composition API 工具:
typescript复制// composables/useAutoCleanup.ts
import { onScopeDispose } from 'vue'
export function useAutoCleanup() {
const cleanupCallbacks = new Set<() => void>()
const register = (callback: () => void) => {
cleanupCallbacks.add(callback)
return () => cleanupCallbacks.delete(callback)
}
onScopeDispose(() => {
cleanupCallbacks.forEach(fn => fn())
cleanupCallbacks.clear()
})
return { register }
}
使用示例:
vue复制<script setup>
const { register } = useAutoCleanup()
const timer = setInterval(() => {}, 1000)
register(() => clearInterval(timer))
const observer = new IntersectionObserver(callback)
register(() => observer.disconnect())
</script>
在 CI 中实现性能预算管控:
yaml复制# .github/workflows/lighthouse.yml
- name: Run Lighthouse CI
run: |
lhci collect --url=https://staging.example.com
lhci assert --preset=perf-budget
lhci upload --target=filesystem --outputDir=./lhci-results
配合 git hooks 实现本地预检查:
bash复制#!/bin/sh
# pre-push hook
lhci collect --url=http://localhost:5173
lhci assert --preset=perf-budget-local || {
echo "性能检查未通过,请优化后再推送"
exit 1
}
使用开源方案实现 Web Vitals 全量采集:
typescript复制// utils/webVitalsRecorder.ts
import { getCLS, getFID, getLCP } from 'web-vitals'
const endpoint = '/api/vitals'
const queue = new Set()
function sendToAnalytics(metric) {
queue.add(JSON.stringify(metric))
if (navigator.sendBeacon) {
navigator.sendBeacon(endpoint, [...queue].join('&'))
queue.clear()
} else {
fetch(endpoint, {
method: 'POST',
body: [...queue].join('&'),
keepalive: true
})
queue.clear()
}
}
export function startVitalsCollection() {
getCLS(sendToAnalytics)
getFID(sendToAnalytics)
getLCP(sendToAnalytics)
}
开发脚本加载管理器:
typescript复制class ThirdPartyLoader {
private loaded = new Set<string>()
private queue = new Map<string, () => Promise<void>>()
register(name: string, loader: () => Promise<void>, priority = 0) {
this.queue.set(name, loader)
}
async loadCritical() {
// 立即加载关键脚本
}
async loadOnIdle() {
if ('requestIdleCallback' in window) {
window.requestIdleCallback(this.processQueue.bind(this))
} else {
setTimeout(this.processQueue.bind(this), 5000)
}
}
private async processQueue() {
for (const [name, loader] of this.queue) {
if (!this.loaded.has(name)) {
await loader()
this.loaded.add(name)
}
}
}
}
针对 SSR + CSR 混合场景的优化策略:
实现示例:
vue复制<template>
<div>
<!-- 静态内容保持 SSR -->
<ProductDescription :product="product" />
<!-- 交互组件延迟 hydration -->
<LazyHydrate when-visible>
<ProductReviewForm :productId="product.id" />
</LazyHydrate>
</div>
</template>
<script setup>
import LazyHydrate from 'vue3-lazy-hydration'
</script>
| 指标 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| Performance | 72 | 100 | +39% |
| FCP | 2.1s | 0.8s | -62% |
| LCP | 4.3s | 1.2s | -72% |
| CLS | 0.25 | 0.02 | -92% |
| TBT | 320ms | 28ms | -91% |
| SI | 3.8s | 1.5s | -61% |
在团队内部推行:
实现 Lighthouse 100 分不是终点,而是性能优化文化的起点。在持续三个月的优化过程中,我们最大的收获不是那个绿色分数,而是建立了贯穿整个研发流程的性能意识。从产品设计时的资源预算,到开发时的性能实践,再到上线后的持续监控,性能优化应该成为每个前端开发者的肌肉记忆。