Web 性能优化完全指南:15 个技巧 + 本站 Lighthouse 实测,同站页面移动端 LCP 从 1.9 秒到 11.8 秒
Google 在 2023 年的研究数据揭示了一个残酷的现实:页面加载时间每增加 1 秒,移动端转化率下降 20%。BBC 发现每多花 1 秒加载时间,就额外失去 10% 的用户。Amazon 估算延迟 100 毫秒会导致销售额下降 1%。
性能不是"锦上添花",而是直接影响业务的核心指标。本文系统梳理 15 个经过实践验证的优化技巧,从最大收益的图片优化,到精细的运行时调优,帮你构建真正快速的 Web 应用。
Core Web Vitals:Google 定义的性能标准
在优化之前,先了解 Google 用来评估用户体验的三个核心指标(Core Web Vitals):
| 指标 | 全称 | 含义 | 良好标准 |
|---|---|---|---|
| LCP | Largest Contentful Paint | 最大内容元素的渲染时间 | ≤ 2.5 秒 |
| INP | Interaction to Next Paint | 用户交互到下一帧渲染的延迟(2024年替代FID) | ≤ 200ms |
| CLS | Cumulative Layout Shift | 累积布局偏移(页面元素意外移动) | ≤ 0.1 |
这三个指标直接影响 Google 搜索排名。优化它们既是性能工程,也是 SEO 策略。
图片优化:最大的性能收益点
图片通常占网页总字节数的 50%~70%,是性能优化中性价比最高的方向。
技巧 1:使用现代图片格式
| 格式 | 相比 JPEG | 相比 PNG | 浏览器支持 |
|---|---|---|---|
| WebP | 小 25~35% | 小 26% | 97%+ |
| AVIF | 小 50% | 小 50%+ | 90%+(2024年) |
| JPEG XL | 小 35% | - | 仍在推进中 |
<!-- 使用 picture 元素提供多格式回退 -->
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero image" width="1200" height="600">
</picture>
技巧 2:响应式图片(srcset)
<img
src="photo-800w.jpg"
srcset="photo-400w.jpg 400w,
photo-800w.jpg 800w,
photo-1600w.jpg 1600w"
sizes="(max-width: 600px) 400px,
(max-width: 1200px) 800px,
1600px"
alt="响应式图片示例"
>
浏览器根据设备屏幕宽度和分辨率自动选择最合适的图片,避免手机下载 2MB 的桌面图片。
技巧 3:图片懒加载
<!-- 原生懒加载,现代浏览器支持 -->
<img src="below-fold.jpg" loading="lazy" alt="延迟加载图片">
<!-- 视口内的关键图片:显式声明 eager(默认行为)-->
<img src="hero.jpg" loading="eager" alt="首屏图片">
懒加载可将初始页面数据量减少 50% 以上,显著提升 LCP。
技巧 4:设置明确的图片尺寸
<!-- ✅ 正确:声明宽高,浏览器预留空间,避免布局偏移(CLS) -->
<img src="avatar.jpg" width="120" height="120" alt="用户头像">
<!-- ❌ 错误:图片加载后撑开布局,导致 CLS 分数飙升 -->
<img src="avatar.jpg" alt="用户头像">
资源加载优化
技巧 5:CSS/JS 压缩与代码分割
现代构建工具(Vite、webpack、Next.js)均自动完成压缩(Minify)。代码分割(Code Splitting)则需要主动配置:
// React 动态导入:按需加载组件
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<HeavyComponent />
</Suspense>
);
}
代码分割可将首屏 JavaScript 包体积减少 40%~60%。
技巧 6:关键 CSS 内联
将首屏渲染所需的最少 CSS 直接内联到 <head> 中,消除渲染阻塞:
<head>
<!-- 内联关键 CSS(通常 < 14KB) -->
<style>
/* 首屏布局和核心样式 */
body { margin: 0; font-family: system-ui; }
.hero { height: 100vh; display: flex; }
</style>
<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
</head>
技巧 7:预加载关键资源
<head>
<!-- 预加载 LCP 图片(首屏最大元素) -->
<link rel="preload" as="image" href="hero.webp" fetchpriority="high">
<!-- 预加载关键字体 -->
<link rel="preload" as="font" href="inter.woff2" type="font/woff2" crossorigin>
<!-- 预连接第三方域名 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
</head>
技巧 8:字体优化
@font-face {
font-family: 'Inter';
src: url('inter.woff2') format('woff2');
/* swap: 先用系统字体显示文字,字体加载完成后替换 */
/* 避免 FOIT(不可见文字闪烁) */
font-display: swap;
}
字体子集化:只包含实际使用的字符,中文字体从 5MB 压缩到 100KB 以内。工具推荐:glyphhanger、fonttools。
网络优化
技巧 9:CDN 分发静态资源
CDN(内容分发网络)将静态资源缓存到全球节点,用户从最近的节点获取,延迟从 200ms 降至 20ms。
主流选择:Cloudflare(免费套餐强大)、阿里云 CDN、腾讯云 CDN。Next.js、Vercel 等平台默认集成 CDN。
技巧 10:HTTP/2 与 HTTP/3
HTTP/2 相比 HTTP/1.1 的核心改进:
- 多路复用:单连接并发多个请求,消除队头阻塞
- 头部压缩(HPACK):减少重复请求头的传输体积
- 服务器推送:提前推送客户端可能需要的资源
HTTP/3 基于 QUIC 协议(UDP),在网络不稳定场景(移动网络)下表现更好。主流 Web 服务器(Nginx 1.25+、Caddy)均已支持。
技巧 11:缓存策略
# Nginx 缓存配置示例
# 永久缓存(带内容哈希的静态资源)
location ~* \.(js|css|woff2)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# HTML 不缓存(需要及时更新)
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
# 图片:中等缓存时间
location ~* \.(jpg|webp|png)$ {
add_header Cache-Control "public, max-age=86400";
}
技巧 12:Gzip / Brotli 压缩
Brotli 压缩比 Gzip 高 20~26%,现代浏览器全面支持:
# Nginx Brotli 配置
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/javascript application/json;
文本资源(HTML/CSS/JS/JSON)压缩后体积可减少 60%~80%。
运行时优化
技巧 13:避免布局抖动(Layout Thrashing)
布局抖动是指代码在同一帧内交替读取和写入 DOM 样式,迫使浏览器反复重新计算布局:
// ❌ 错误:每次循环都触发强制重排
elements.forEach(el => {
const height = el.offsetHeight; // 读取(触发重排)
el.style.height = height + 10 + 'px'; // 写入(使布局失效)
});
// ✅ 正确:批量读取,再批量写入
const heights = elements.map(el => el.offsetHeight); // 批量读取
elements.forEach((el, i) => {
el.style.height = heights[i] + 10 + 'px'; // 批量写入
});
工具库 fastdom 可以自动调度读写操作,避免布局抖动。
技巧 14:虚拟列表处理长列表
渲染 10000 条数据会创建 10000 个 DOM 节点,导致页面卡顿。虚拟列表只渲染可视区域内的节点:
// 使用 react-virtual 库
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }) {
const parentRef = useRef(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // 每行预估高度
});
return (
<div ref={parentRef} style={{ height: '500px', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map(item => (
<div key={item.key} style={{ transform: `translateY(${item.start}px)` }}>
{items[item.index].name}
</div>
))}
</div>
</div>
);
}
10000 条数据从渲染 10000 个节点变为仅渲染 ~20 个可视节点,性能提升数十倍。
技巧 15:Web Worker 处理计算密集型任务
JavaScript 是单线程的,CPU 密集型任务会阻塞主线程,导致页面无响应:
// main.js:在主线程创建 Worker
const worker = new Worker('worker.js');
worker.postMessage({ data: largeDataset });
worker.onmessage = (event) => {
const result = event.data;
// 在主线程更新 UI
updateUI(result);
};
// worker.js:在 Worker 线程执行耗时计算
self.onmessage = (event) => {
const { data } = event.data;
// 执行耗时计算,不阻塞主线程
const result = heavyComputation(data);
self.postMessage(result);
};
适用场景:图像处理、数据加密解密、大数据排序过滤、Markdown 解析等。
性能测量工具
| 工具 | 用途 | 访问方式 |
|---|---|---|
| Lighthouse | 综合评分 + 优化建议 | Chrome DevTools → Lighthouse |
| PageSpeed Insights | 实验室数据 + 真实用户数据(CrUX) | pagespeed.web.dev |
| WebPageTest | 多地区测试、瀑布图分析 | webpagetest.org |
| Chrome DevTools Performance | 运行时性能分析、火焰图 | F12 → Performance |
| web-vitals.js | 在真实用户环境采集 Core Web Vitals | npm install web-vitals |
本机实测:拿本站 4 个页面跑 24 次 Lighthouse
问题背景
上面 15 个技巧每一条都对,但这篇文章的旧标题写的是「提升 50%」,那个 50% 不是我测出来的。本站自己就是一个 Next.js 站点,首页、工具页、文章页三种页面形态齐全,GTM、AdSense、百度统计三家第三方脚本都挂着,正好拿来问一句:按 Google 的口径,这个站现在到底几分、LCP 几秒、慢在哪一层? 这一节把这件事补上,标题里的数字换成实测读数。
问题分析
要分开量的是三件事:
- 分数和三大指标本身。Lighthouse 的性能分是 FCP / LCP / TBT / CLS / Speed Index 五项加权,移动端预设是模拟一台 Moto G Power 走 slow 4G(4 倍 CPU 降速、RTT 150ms、带宽 1.6Mbps),桌面端预设不降 CPU、RTT 40ms、带宽 10Mbps。同一页面两套预设的差距,本身就是「移动端为什么要单独优化」的答案。
- LCP 元素是谁。LCP 是「最大内容元素」的渲染时间,元素不同,优化方向完全不同:是服务端渲染的标题,那就看 TTFB 和阻塞资源;是水合后才出现的客户端节点,那就是 JS 体积和执行时间的问题。
- 第三方脚本占了多少。GTM、AdSense、百度统计是站长最常见的「加了就忘」的项。Lighthouse 有
--blocked-url-patterns,可以不改一行代码就量出「把它们全拿掉能快多少」。
另外还有一个很容易忽略的口径问题:Lighthouse 默认的节流方法是 simulate,即在无节流的环境跑一遍拿到 trace,再用 Lantern 模型推算「如果在慢设备慢网络下会怎样」。它输出的是模型值,不是量出来的值。这一点在后面的对照实验里差点让我写出相反的结论。
技术方案与选型
- 直接打生产站,不测本地 dev server。
next dev不做生产优化,测出来的包体积和分数没有意义;本机也没有一份与线上一致的 standalone 构建。生产站就是用户走的路径,代价是数字里带着本机到北京机房的网络,后面单列 TTFB 说明。 - 用 npx 的 Lighthouse 13.4.1 CLI,不用 Chrome DevTools 面板点。 一共 60 多次运行,CLI 才能把每次的完整 JSON 落盘,再用脚本取中位数,避免手抄。排除 PageSpeed Insights:它跑在 Google 的机器上、还带 CrUX 真实用户数据,本来是更好的对照,但匿名接口当天配额已满(HTTP 429),用 GA 的服务账号换 token 又报 scope 不足(403),只能作罢,下面如实标「未测」。
- 4 个页面覆盖三种形态:首页(工具卡片网格)、Markdown 编辑器页(重客户端组件)、文件转换页(轻客户端组件)、本文(纯服务端渲染的文章页)。移动端、桌面端各 3 轮,取中位数,共 24 次基线运行。
- 必须指定浏览器 UA。 本站的 middleware 会按 UA 拦掉
curl/、HeadlessChrome等自动化流量(返回 403),Lighthouse 跑的是 headless Chrome。实测下来默认设置能过,原因见踩坑一节,但保险起见所有正式运行都显式传--emulatedUserAgent为真实 Chrome UA。 - 对照实验两组:① 屏蔽第三方脚本(GTM / GA / AdSense / DoubleClick / 百度统计 / reCAPTCHA),移动端 4 页 × 3 轮;② 同样的屏蔽前后,换
--throttling-method=devtools真节流再跑首页和 Markdown 页各 3 轮,用来裁决 simulate 的结果能不能信。
实测过程
跑一次的命令长这样(桌面端加 --preset=desktop):
npx --yes lighthouse https://tools.cooconsbit.com/zh \
--output=json --output-path=raw/home.mobile.1.json --quiet \
--emulatedUserAgent="Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36" \
--chrome-flags="--headless=new --no-first-run"
24 次基线跑了 611 秒。每次先核对 JSON 里主文档的状态码是 200、runtimeError 为空,再从 audits 里取五项指标、network-requests 里按资源类型汇总传输字节,按页面 × 端取 3 轮中位数:

对照实验加 --blocked-url-patterns(每个域名一个参数):
npx --yes lighthouse https://tools.cooconsbit.com/zh --output=json ... \
--blocked-url-patterns="*googletagmanager.com*" --blocked-url-patterns="*google-analytics.com*" \
--blocked-url-patterns="*googlesyndication.com*" --blocked-url-patterns="*doubleclick.net*" \
--blocked-url-patterns="*adtrafficquality.google*" --blocked-url-patterns="*hm.baidu.com*" \
--blocked-url-patterns="*google.com/recaptcha*" --blocked-url-patterns="*gstatic.com*"
真节流那组再加 --throttling-method=devtools,其余参数不变。
实践效果
基线:4 个页面 × 两端,3 轮中位数
| 页面 | 端 | 性能分 | FCP | LCP | TBT | CLS | Speed Index | TTFB | 请求数 | 传输 |
|---|---|---|---|---|---|---|---|---|---|---|
| 本文(文章页) | 桌面 | 100 | 0.32s | 0.40s | 0ms | 0 | 0.66s | 109ms | 60 | 1014KB |
| 本文(文章页) | 移动 | 99 | 1.10s | 1.88s | 133ms | 0 | 2.16s | 110ms | 54 | 1015KB |
| 首页 | 桌面 | 99 | 0.33s | 0.83s | 0ms | 0 | 0.63s | 113ms | 62 | 1033KB |
| 首页 | 移动 | 93 | 1.25s | 2.98s | 111ms | 0 | 2.12s | 113ms | 51 | 1014KB |
| 文件转换 | 桌面 | 99 | 0.29s | 0.83s | 0ms | 0 | 0.61s | 42ms | 75 | 1017KB |
| 文件转换 | 移动 | 91 | 0.98s | 3.44s | 85ms | 0 | 2.08s | 40ms | 59 | 1010KB |
| Markdown 编辑器 | 桌面 | 86 | 0.30s | 2.47s | 96ms | 0 | 0.81s | 48ms | 63 | 1654KB |
| Markdown 编辑器 | 移动 | 60 | 0.97s | 11.81s | 551ms | 0 | 3.42s | 53ms | 55 | 1646KB |
抖动不大:首页移动端 3 轮性能分 88 到 94、LCP 2.98 到 3.89 秒;Markdown 页移动端 3 轮 LCP 11.76 到 11.98 秒,三次都稳稳落在「差」区间。可访问性 96 到 97(Markdown 页移动端 91)、SEO 100;最佳实践 8 组全是 58 分,扣分项三条一样:一条弃用 API 警告、3 个第三方 cookie、DevTools issues 面板有条目,全部来自广告和统计脚本。
四点读法:
- CLS 全部为 0,TTFB 40 到 113ms。布局稳定性和服务端响应这两层没有问题,图片尺寸、字体、SSR 的功课本站已经做完了,所以「技巧 4」「技巧 8」在这个站上没有优化空间。
- 同一个站,移动端 LCP 差 6 倍:文章页 1.88 秒(LCP 元素是服务端渲染的
<h1>,渲染延迟只有 93ms),Markdown 编辑器页 11.81 秒。桌面端这两页分别 0.40 秒和 2.47 秒。 - Markdown 页慢在一个 chunk 上。它的首方 JS 一共 20 个文件、传输 941KB、解压后 2856KB,其中
e39dd30a996a1574.js一个文件传输 497KB、原始 1,478,867 字节,里面是@uiw/react-md-editor带进来的 refractor 全语言代码高亮包和 remark / rehype 管线;另一个 125KB 的 chunk 是导出图片用的 html2canvas。编辑器组件用dynamic(..., { ssr: false })加载,LCP 元素是编辑器容器那个<div>,本机无节流时它的渲染延迟就已经是 1879ms,模拟 4 倍 CPU 降速加 slow 4G 后被放大到 11.8 秒。这就是「技巧 5 代码分割」在本站最该落地的地方:高亮语言按需加载,html2canvas 到点击导出时再拉。 - 首页和文件转换页移动端的 LCP 元素,是 cookie 同意横幅的那段文字。 选择器是
body > div.fixed > div.mx-auto > p.text-sm,节点文本「我们使用 Cookie 来保持您的登录状态……」。curl 拉首页 HTML 搜这个类名是 0 次命中,因为横幅组件初始visible为false、返回null,要等水合后的useEffect读完 localStorage 才显示。于是 LCP 时间等于「JS 下载 + 执行 + 水合」的时间:真节流下首页 FCP 2.0 秒、DOMContentLoaded 1.98 秒,但那批 Low 优先级的 JS chunk 要到 4.3 秒才下完,LCP 落在 5.14 秒,渲染延迟 4891ms。一个 91 像素高的横幅把首页的 LCP 从服务端渲染的内容抢走了。修法很直接:横幅服务端渲染出来、用 CSS 隐藏,或者干脆把它做得比首屏的标题小。
第三方脚本占多少:屏蔽前后(移动端,各 3 轮中位数)
| 页面 | 性能分 | LCP | TBT | 请求数 | 传输 |
|---|---|---|---|---|---|
| 文章页 | 99 → 99 | 1.88s → 2.16s | 133ms → 3ms | 54 → 38 | 1015KB → 410KB |
| 首页 | 93 → 83 | 2.98s → 4.61s | 111ms → 4ms | 51 → 35 | 1014KB → 411KB |
| 文件转换 | 91 → 84 | 3.44s → 4.48s | 85ms → 16ms | 59 → 43 | 1010KB → 405KB |
| Markdown 编辑器 | 60 → 64 | 11.81s → 8.60s | 551ms → 446ms | 55 → 39 | 1646KB → 1047KB |
三家第三方合计每页约 600KB:GTM 316KB、Google 广告 220 到 245KB、adtrafficquality 22KB、百度统计 12KB。屏蔽后 TBT 基本归零、体积少 60%,这两项符合预期。但首页和文件转换页的 LCP 反而变差了 1 到 1.6 秒,分数掉了 7 到 10 分。 我第一反应是 LCP 元素换了,查 JSON:屏蔽前后 LCP 元素都是那段 cookie 文字,本机无节流的观测 LCP 只从 380ms 变成 476ms。变的是 Lantern 的推算值。
于是换 devtools 真节流把同样的对照重跑一遍:

| 页面 | 节流方法 | 性能分 | LCP 基线 → 屏蔽后 | 变化 |
|---|---|---|---|---|
| 首页 | simulate | 93 → 83 | 2.98s → 4.61s | +1.63s(+55%) |
| 首页 | devtools | 79 → 83 | 5.13s → 4.45s | −0.69s(−13%) |
| Markdown 编辑器 | simulate | 60 → 64 | 11.81s → 8.60s | −3.21s(−27%) |
| Markdown 编辑器 | devtools | 53 → 57 | 12.25s → 9.35s | −2.89s(−24%) |
真节流下方向对了:首页快 13%、Markdown 页快 24%,TBT 首页从 56ms 到 0。所以本站第三方脚本的真实代价是「LCP 一到三成、TBT 全部」,不是模型算出来的负数。另一个顺带的发现:devtools 真节流的首页 LCP 是 5.13 秒,比 simulate 的 2.98 秒慢得多,因为真节流对每个 TLS 握手、每个请求都实打实加 150ms 往返,Lantern 的模型对小请求更乐观。两种方法的绝对值不能互相比,同一种方法内的前后差才有意义。
PSI / CrUX 真实用户数据:未测。 匿名接口 HTTP 429,服务账号 token 403,本节所有数字都是实验室数据,不含真实用户样本。
踩坑
- Lantern 模拟节流的差值不能直接当实验结论。 默认
--throttling-method=simulate输出的是模型推算值,屏蔽第三方脚本这种改变请求图结构的对照,模型给首页算出 +55% 的 LCP,真节流量出来是 −13%。凡是「改动前后对比」,至少用 devtools 真节流复核一次;simulate 适合看单次绝对分数和抖动小的重复测量。 - 站点按 UA 拦截时,Lighthouse 默认设置其实能过,
--no-emulated-user-agent才会 403。 提前拿到的提示是「默认 UA 带 HeadlessChrome 会被拦」,实测 5 种设置:默认移动端仿真、--preset=desktop、显式--emulatedUserAgent真实 Chrome UA 三种主文档都是 200,因为 Lighthouse 默认就会把 UA 覆盖成 moto g 或 Mac Chrome 字符串(configSettings.emulatedUserAgent里能看到);--emulatedUserAgent="curl/8.7.1"和--no-emulated-user-agent才是 403,报ERRORED_DOCUMENT_REQUEST。判断有没有被拦别猜,看 JSON 里network-requests第一条的statusCode。
- 同一页面有的轮次 12 秒跑完,有的 55 秒。 差别在 GA 的
/g/collect请求:本机不挂代理直连,这个请求一直挂着不返回,Lighthouse 等到maxWaitForLoad45 秒上限才收尾。首页 6 轮全中,Markdown 页 4 轮中,其余页面不发这个请求就 7 到 12 秒结束。不影响指标(FCP / LCP 早就定了),但批量跑要按最坏情况预留时间。 environment.networkUserAgent不反映--emulatedUserAgent。 我传的是 Chrome/140,JSON 里这个字段写的是 Chrome/152(宿主 Chrome 版本)。它记录的是 Lighthouse 的默认字符串,真正生效的值在configSettings.emulatedUserAgent。curl UA 那一轮 403 证明了参数确实生效。- Lighthouse 13 的审计 id 和老版本不一样。
largest-contentful-paint-element、third-party-summary、mainthread-work-breakdown这些老 id 已经没有了,LCP 元素和分阶段在lcp-breakdown-insight,第三方在third-parties-insight。写汇总脚本前先Object.keys(lhr.audits)看一眼,别按记忆写。 - PageSpeed Insights 接口没有 key 基本用不了。 匿名调用共享一个每日配额,当天已经 429;GA 的服务账号 token 也不行(PSI 只认 API key,报 403 scope 不足)。要 CrUX 真实用户数据得先去 Google Cloud 建一个 key。
- SSR HTML 里搜不到的元素,别当它是服务端渲染的。 cookie 横幅那一条,我先入为主以为「文字肯定是 SSR 的、LCP 慢是字体的锅」,字体审计其实是满分。
curl拉 HTML 搜类名 0 命中,才反应过来它是水合后才挂上去的。判断 LCP 元素属于哪一层,先看它在不在原始 HTML 里。
原始 JSON(raw/、raw-blocked/、raw-devtools-*/、raw-noua/)、运行脚本(run-lh.mjs / run-blocked.mjs / run-devtools.mjs)和汇总脚本(summarize.mjs / compare-devtools.mjs / ua-check.mjs)都在仓库的 tmp/2026-09-16-cwv-lighthouse/,表里每个数字都能回到那里对上。
FAQ
Q:首屏加载多快算合格?
根据 Google Core Web Vitals 标准:LCP ≤ 2.5 秒为"良好",2.5~4 秒为"需改善",> 4 秒为"差"。对于电商网站,建议将首屏可交互时间(TTI)控制在 3 秒以内。在弱网环境(3G)下测试,通过才算真正合格。
Q:Next.js 等现代框架能自动优化哪些性能?
Next.js 自动提供:图片优化(next/image 组件自动 WebP 转换、懒加载、尺寸优化)、代码分割(按页面自动分割)、字体优化(next/font 自动子集化 + font-display: swap)、静态生成(SSG 预渲染 HTML)、Brotli 压缩(Vercel 部署时)。但需要手动处理的包括:避免客户端过大的 JS 包、第三方脚本的加载策略、数据请求的缓存策略。
Q:移动端和桌面端优化重点一样吗?
不一样。移动端额外需要关注:网络带宽(4G/5G 波动大,需要更激进的缓存和资源压缩)、CPU 性能(移动端 CPU 是桌面的 1/4~1/8,需减少 JavaScript 执行量)、触摸交互延迟(避免 300ms 点击延迟,用 touch-action: manipulation)。Google PageSpeed Insights 会分别显示移动端和桌面端的评分,优先关注移动端——Google 搜索已全面切换为移动优先索引。
总结
15 个技巧的优先级排序,供参考:
- 图片格式 + 懒加载(最大收益,改动成本低)
- 设置图片尺寸(CLS 直接降零)
- 关键资源预加载(LCP 立竿见影)
- 代码分割(首屏 JS 瘦身)
- CDN + Brotli + HTTP/2(网络层全局优化)
性能优化的最佳工作流:先测量(Lighthouse)→ 找瓶颈(DevTools)→ 有针对性地优化 → 验证效果。不要凭感觉优化,数据说话。本站自己的例子:同一个站移动端 LCP 从 1.88 秒到 11.81 秒,慢的那页问题在一个 1.48MB 的高亮包,快的那页 LCP 元素却是 cookie 横幅——不测,这两条都猜不到。