性能是一切的起点
研究表明:页面加载时间每增加 1 秒,转化率下降约 7%。Google 也将 Core Web Vitals 作为搜索排名因素。性能优化不是锦上添花,而是用户体验和商业价值的核心组成部分。本文将带你从关键指标出发,逐一攻克性能优化的各个维度。
Core Web Vitals:三个核心指标
Google 定义了三个衡量用户体验的核心指标:
- LCP(Largest Contentful Paint,最大内容绘制):页面主要内容加载完成的时间。目标 < 2.5 秒
- INP(Interaction to Next Paint,交互到下次绘制):页面响应用户交互的延迟。目标 < 200 毫秒
- CLS(Cumulative Layout Shift,累计布局偏移):页面加载过程中元素位置的意外偏移。目标 < 0.1
// 使用 web-vitals 库在代码中监控
import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
LCP 是大多数网站的瓶颈,因为它直接受图片、字体和关键资源加载速度的影响。
图片优化:性能的最大杠杆
图片通常占网页总大小的 60% 以上。优化图片是回报率最高的性能优化手段:
使用现代格式
<!-- 使用 picture 元素提供多格式降级 -->
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero image">
</picture>
WebP 比 JPEG 小 25-35%,AVIF 比 WebP 再小 20-30%。如果你的构建工具链支持,应该在构建时自动生成多格式图片。
响应式图片
<img
src="photo-800w.jpg"
srcset="photo-400w.jpg 400w, photo-800w.jpg 800w, photo-1200w.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="描述文字"
loading="lazy"
decoding="async"
>
loading="lazy" 是原生懒加载,无需任何 JavaScript;decoding="async" 让图片解码不阻塞主线程;srcset + sizes 让浏览器根据屏幕宽度选择合适尺寸的图片。
字体加载策略
Web 字体是导致 FOIT(Flash of Invisible Text)和 FOUT(Flash of Unstyled Text)的元凶。正确的加载策略:
/* 1. 使用 font-display 控制加载行为 */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var.woff2') format('woff2');
font-display: swap; /* 先用系统字体,加载完成后替换 */
font-weight: 100 900;
}
/* 2. 预连接字体 CDN */
/* <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> */
进阶优化:
- 子集化(Subsetting):只包含网站实际使用的字符,中文字体不做子集化几乎是不可用的
- 可变字体:一个文件包含所有字重,减少 HTTP 请求
- 自托管字体:避免 Google Fonts 的额外 DNS 查询和连接建立
JavaScript 分包与 Tree Shaking
大型 SPA 应用的首屏 JavaScript 体积往往超过 1MB。解决方案:
// 1. 动态 import 实现路由级代码分割
const Dashboard = lazy(() => import('./pages/Dashboard'));
// 2. 按需加载重型库
button.addEventListener('click', async () => {
const { saveAs } = await import('file-saver');
saveAs(blob, 'report.pdf');
});
// 3. Vite 中手动分包
// vite.config.js
build: {
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'ui-lib': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
},
},
},
}
Tree Shaking 依赖 ES Module 的静态分析。确保你的库以 ESM 格式发布,并且没有副作用(或在 package.json 中声明 sideEffects: false)。
CDN 与缓存策略
静态资源应该通过 CDN 分发,配合合理的缓存策略:
# Nginx 缓存配置示例
location /assets/ {
# 文件名带 hash 的可以永久缓存
location ~* \.[a-f0-9]{8,}\.(js|css|png|jpg|webp|avif|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
location /api/ {
# API 响应不缓存
add_header Cache-Control "no-store";
}
缓存规则的关键:文件名带内容哈希的资源可以长期缓存(因为内容变了文件名就变了),而 HTML 文件应该不缓存或短缓存(确保用户总是拿到最新版本)。
性能监控与 Lighthouse
优化不是一次性的工作,需要持续监控。推荐的工具链:
- Lighthouse:Chrome DevTools 内置,提供全面的性能评分和优化建议
- PageSpeed Insights:Google 的在线版本,包含真实用户数据(CrUX)
- Web Vitals 库:在代码中收集真实用户的性能数据,发送到分析平台
- Lighthouse CI:在 CI 流水线中运行 Lighthouse,防止性能退化
# 在 CI 中运行 Lighthouse
npx lighthouse https://staging.example.com \
--output=json \
--output-path=./lighthouse-report.json \
--chrome-flags="--headless --no-sandbox"
总结
性能优化的优先级:先优化图片(最大杠杆)-> 优化字体加载 -> JavaScript 分包 -> 缓存策略 -> 持续监控。不要试图一次性做完所有优化——从 LCP 指标开始,找到最大的瓶颈,解决它,测量效果,然后进入下一个。记住:没有测量就没有优化。