Web 性能优化:Lighthouse不够用了之后

这次给公司官网做性能优化,从首屏加载 8 秒优化到 2 秒,也学到了不少。先用 Lighthouse 看看网站现状。

性能评估

Lighthouse 基础

先用 Lighthouse 看看网站现状。

# 在 Chrome DevTools 中运行 Lighthouse
# 或者使用命令行
npx lighthouse https://example.com --view

Lighthouse 给出的分数:

  • Performance: 35
  • Accessibility: 85
  • Best Practices: 70
  • SEO: 90

问题很明显:性能分数太低了。

关键指标

Lighthouse 的关键指标:

  • FCP (First Contentful Paint): 首次内容绘制 2.5 秒
  • LCP (Largest Contentful Paint): 最大内容绘制 8 秒
  • TTI (Time to Interactive): 可交互时间 9 秒
  • CLS (Cumulative Layout Shift): 累积布局偏移 0.1
  • FID (First Input Delay): 首次输入延迟 100 毫秒

LCP 8 秒太慢了,用户可能早就不耐烦了。

优化策略

资源压缩

静态资源太大,压缩效果明显。

图片优化

# 使用 WebP 格式
ffmpeg -i image.png -c:v libwebp -quality 80 image.webp

# 压缩 PNG
optipng -o7 image.png

# 压缩 JPEG
jpegoptim --max=80 image.jpg

代码压缩

// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
        terserOptions: {
          compress: {
            drop_console: true,
          },
        },
      }),
    ],
  },
};

结果:静态资源大小减少了 60%。

懒加载

非首屏资源不要急着加载。

图片懒加载

<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" alt="Description">

代码分割

// 动态导入
const LazyComponent = React.lazy(() => import('./LazyComponent'));

// 在路由中使用
<Route path="/dashboard" component={LazyComponent} />

结果:首屏加载的代码减少了 40%。

缓存策略

浏览器缓存能减少重复请求。

HTTP 缓存头

# 静态资源长期缓存
location ~* \.(jpg|jpeg|png|gif|webp|svg|woff|woff2)$ {
  expires: 1y;
  add_header Cache-Control "public, immutable";
}

# HTML 文件短期缓存
location ~* \.html$ {
  expires: 1h;
  add_header Cache-Control "public";
}

# API 响应短期缓存
location /api/ {
  expires: 5m;
  add_header Cache-Control "public, must-revalidate";
}

Service Worker 缓存

// 注册 Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js');
}

// sw.js
const CACHE_NAME = 'v1';
const ASSETS = [
  '/',
  '/static/css/main.css',
  '/static/js/main.js',
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => response || fetch(event.request))
  );
});

结果:重复访问速度提升了 80%。

关键渲染路径优化

阻塞渲染的资源

CSS 阻塞渲染,JavaScript 阻塞解析。

优化策略

<!-- 关键 CSS 内联 -->
<style>
  body { font-family: sans-serif; }
  .header { ... }
</style>

<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">

<!-- JavaScript 异步加载 -->
<script src="non-critical.js" async></script>
<script src="defer.js" defer></script>

结果:FCP 从 2.5 秒降低到 1.2 秒。

预加载重要资源

预加载可以提前开始加载关键资源。

<!-- 预连接到重要域名 -->
<link rel="preconnect" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://api.example.com">

<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/images/hero.webp" as="image">

结果:LCP 从 8 秒降低到 4 秒。

API 优化

后端接口太慢,前端再优化也没用。

请求合并

多个小请求合并成一个大请求。

// 之前的做法
fetch('/api/user/profile').then(r => r.json());
fetch('/api/user/settings').then(r => r.json());
fetch('/api/user/notifications').then(r => r.json());

// 优化后
fetch('/api/user/all').then(r => r.json());

响应压缩

# 开启 Gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;

# 开启 Brotli 压缩(需要安装 ngx_brotli)
brotli on;
brotli_types text/plain text/css application/json application/javascript;

结果:API 响应大小减少了 70%,传输时间减少了 60%。

踩过的坑

坑一:过度优化

一开始想什么都优化,结果花了很多时间在不重要的地方。

解决:先看 Lighthouse 报告,优先优化影响最大的问题。

坑二:缓存失效

改了静态资源,但用户还在用旧缓存。

解决:给静态资源加上版本号。

// webpack.config.js
module.exports = {
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js',
  },
};

坑三:第三方脚本拖慢速度

引入了太多第三方脚本(Analytics、聊天工具、广告)。

解决

  • 延迟加载非关键脚本
  • 用 Service Worker 缓存第三方脚本
  • 定期审查是否还需要这些脚本
<!-- 第三方脚本延迟加载 -->
<script>
  window.addEventListener('load', () => {
    const script = document.createElement('script');
    script.src = 'https://analytics.example.com/script.js';
    document.body.appendChild(script);
  });
</script>

监控与持续优化

性能优化不是一次性工作,需要持续监控。

RUM (Real User Monitoring)

收集真实用户的性能数据。

// 使用 Web Vitals 库
import { getCLS, getFID, getLCP } from 'web-vitals';

getCLS(console.log);
getFID(console.log);
getLCP(console.log);

定期检查

每个月运行一次 Lighthouse,确保性能不会退化。

# 定期运行 Lighthouse
0 0 1 * * npx lighthouse https://example.com --output=json --output-path=report.json

写在最后

Web 性能优化这东西,不是追求 100 分,而是追求合理的投入产出比。

优化优先级

  1. LCP(最大内容绘制)- 影响最大
  2. FCP(首次内容绘制)- 其次重要
  3. TTI(可交互时间)- 影响用户体验
  4. CLS(累积布局偏移)- 影响视觉稳定性
  5. FID(首次输入延迟)- 影响交互响应

优化之前先评估:

  • 用户的使用场景
  • 网络环境
  • 设备性能
  • 业务价值

不是所有优化都值得做,先做影响最大的。


这次性能优化花了三周时间,LCP 从 8 秒优化到 2 秒,跳出率降低了 30%,转化率提升了 15%。性能优化的价值确实明显。

版权声明: 本文首发于 指尖魔法屋-Web 性能优化:Lighthouse不够用了之后https://blog.thinkmoon.cn/post/58-web-performance-optimization-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!