Lighthouse 全绿但 CrUX 不达标:Core Web Vitals 的 INP、LCP、CLS 分阶段修法
Core Web Vitals 优化经常卡在同一处:Lighthouse 分数全绿,部署也正常,现场数据不动。拖住分数的大多是 Interaction to Next Paint(INP),而标准 Lighthouse 实验室审计测不到 INP。
基础性能优化会在整个技术栈里叠加,但每个 Core Web Vital 在不同阶段出问题的原因不同。把 INP、LCP、CLS 当成三个独立瓶颈,分别给三套动作,是让页面越过第 75 百分位线最可靠的方式。
什么算好的 Core Web Vitals
Core Web Vitals 是三个现场数据指标,分别衡量真实用户的加载性能、交互性和视觉稳定性。Google 在 web.dev 上发布了阈值:
- Largest Contentful Paint(LCP)应在页面开始加载后 2.5 秒内发生。
- Interaction to Next Paint(INP)应保持在 200 毫秒以内。
- Cumulative Layout Shift(CLS)应低于 0.1。
Google 按第 75 百分位评估每个 Core Web Vital:LCP、INP、CLS 都要在至少 75% 的访问中处于良好范围。三项全部达标,页面才算通过。
Google 表示 Core Web Vitals 会被其排名系统使用,良好的 Core Web Vitals 和其他页面体验因素符合核心排名系统要奖励的方向。内容相关性和质量仍然是比页面体验信号更重要的排名因素,Core Web Vitals 达标不会把内容平庸的页面送到第一位。但当两个相关性相近的页面竞争同一查询时,页面体验可以成为排名平局裁决因素。
2025 Web Almanac 显示,移动端 48% 的网站通过 Core Web Vitals,桌面端 56% 通过;移动端 INP 比桌面端落后 20 个百分点。
为什么 Lighthouse 和现场数据打架
Lighthouse 标准审计只加载页面,不交互,所以不会产生 INP 分数。一个页面可以在 PageSpeed Insights 里拿到满分 100 的实验室分数,而同一份报告里的 CrUX 现场面板显示 Core Web Vitals 失败。
现场数据(Real User Monitoring,RUM)来自真实访客在真实条件下的访问,包含设备能力、网络速度、地理位置和浏览行为的差异。
Google 的 CrUX(Chrome User Experience Report)数据集从选择加入的 Chrome 用户那里收集现场数据,Google 的 Core Web Vitals 评估完全基于现场数据。
实验室数据来自固定环境中的受控测试:Lighthouse 使用模拟设备,WebPageTest 使用固定位置的真人设备。实验室测试可重复,适合诊断具体问题,但不反映真实用户体验的多样性。
INP 暴露了页面加载实验室测试的主要限制。标准 Lighthouse 审计加载页面但不交互,因此无法报告 INP 分数。实验室工具可以用脚本化交互近似 INP,但结果只反映你选择测试的交互。在 Lighthouse 中,Total Blocking Time(TBT)与 INP 相关,但不能预测 INP,因为 TBT 猜不到真实用户何时交互,而且页面可以 TBT 很好但 INP 很差。
| 工具 | 数据类型 | 真实用户数据 | 适用场景 |
|---|---|---|---|
| Lighthouse | 实验室 | 否 | 控制环境诊断加载问题,看 TBT;标准审计不测 INP |
| PageSpeed Insights(Lab) | 实验室 | 否 | 固定环境跑分,定位具体审计项 |
| PageSpeed Insights(Field) | 现场(CrUX) | 是 | 在同一报告中查看 URL 的 CrUX 现场摘要 |
| web-vitals | RUM 库 | 是,需要自建采集 | 自定义字段采集,获取 INP 归因数据 |
| CrUX | 现场数据集 | 是 | Google 官方第 75 百分位评估的数据来源 |
| Vercel Speed Insights | RUM 产品 | 是 | 在 Vercel 项目中查看真实用户 Core Web Vitals |
INP:从输入到下一帧的三个阶段
INP 拖住页面时,原因在用户输入到下一次绘制之间的三个阶段之一,每个阶段需要不同修法。
INP 捕获页面生命周期中的每次离散交互(点击、轻触、按键),计时从输入到处理再到下一次绘制,并报告最长的交互,忽略离群值。CrUX 等现场工具再按第 75 百分位聚合页面访问。
用 web-vitals 库的归因数据看一次交互把时间花在哪里,以及用户交互的是哪个元素。从最大的计时值开始:
inputDelay:主线程在事件处理器开始前就很忙。processingDuration:事件处理器内部的工作。presentationDelay:处理器结束后渲染慢。
输入延迟
当用户交互时,主线程正在执行长任务(浏览器在主线程上做任何超过 50 毫秒的事情),输入延迟就会累积。只有长任务与交互重叠时,输入延迟才会增加。水合在点击前四秒完成,不会增加分数;每次滚动触发的第三方分析脚本会增加。
降低输入延迟要让主线程空出来,及时开始事件处理器。把非关键脚本推迟到页面可交互之后,把长任务拆成更小块,并在块之间用 scheduler.yield() 让出。scheduler.yield() 已在 Chromium 和 Firefox 142+ 提供,Safari 中用 setTimeout 兜底。
处理时长
事件处理器里昂贵的 DOM 读写会增加处理时长。写完 DOM 后立刻读布局属性,会触发强制同步布局。浏览器必须在事件处理器继续前重新计算布局,处理时长随之增加。
减少处理时长,要减少事件处理器在浏览器能渲染下一帧之前做的工作。批量 DOM 变更,用 requestAnimationFrame 安排视觉更新,把非 UI 计算移到 web worker。对于触摸和滚动交互,当 touchstart、touchmove、wheel 监听器不调用 preventDefault() 时,把它们标为 passive。浏览器随后可以不必等待监听器就开始滚动。
呈现延迟
大 DOM 树和复杂 CSS 选择器会拖慢渲染管线,增加事件处理器结束到下一次绘制之间的时间。content-visibility: auto 让浏览器跳过屏外内容的渲染工作,同时保留给页内查找和可访问性树使用。用 transform 和 opacity 做动画可以避免触发布局或绘制,动画跑在合成器线程上,will-change 提前告诉浏览器可能即将发生的动画。
React 应用里,交互后的大规模 React 重渲染会导致下一次绘制慢。React 18 可以暂停和恢复非紧急渲染,useTransition 允许把某个状态更新标记为非紧急,让 React 优先处理用户等待的响应。这些工具只控制 React 的渲染工作。如果 onClick 处理器同步刷新分析数据或运行昂贵的第三方脚本,要在处理器内部减少或推迟这些工作。仅迁移到 App Router 不会改善它。
按 2025 Web Almanac,23% 的移动网站未达到“良好”INP,原因可追溯到以上三个阶段。
LCP:四个阶段与对应修法
Largest Contentful Paint(LCP)有四个阶段,最慢的阶段告诉你优先修哪里:
- Time to First Byte(TTFB):服务器响应时间。
- 资源加载延迟:TTFB 到浏览器开始获取 LCP 资源之间的间隔。
- 资源加载时长:该资源下载耗时。
- 元素渲染延迟:下载完成到元素出现在屏幕上的时间。
多数 LCP 修法都在做三件事之一:让 LCP 资源更早开始下载、缩短下载、移除延迟元素最终渲染的工作。
更早获取 LCP 资源
LCP 资源经常被发现得太晚,因为浏览器必须先下载 HTML、解析 CSS,然后找到图片引用。在文档 `` 里加 preload 提示,可以立即开始图片获取,与 CSS 和 JavaScript 并行,fetchpriority="high" 会提升它的优先级。两者一起可以明显缩短资源加载延迟。
解除渲染阻塞
外部 CSS 样式表默认阻塞渲染,浏览器在 head 中阻塞 CSS 下载并解析前不会绘制(只有 media 属性匹配当前环境的样式表会阻塞渲染)。把首屏关键 CSS 直接内联进 HTML,其余异步加载,这在较慢的移动连接上收益最大,因为 CSS 下载可能花掉数百毫秒。
缩小图片
AVIF 的压缩率比 JPEG 好大约 50%,但浏览器覆盖略低于 WebP。浏览器支持时用 AVIF,下一个回退是 WebP,最后回退 JPEG。 元素让浏览器选择它支持的第一个格式。Next.js 的 Image 组件在服务端用浏览器的 Accept 请求头做同样的协商,应用响应式尺寸,并懒加载首屏以下图片,因此一个组件同时覆盖 LCP 图片和通过尺寸预留防止 CLS。相关文档:Next.js Image。
LCP 改善时,转化率也会改善。2021 年案例研究中,Vodafone 将 Largest Contentful Paint 改善了 31%,销售增长 8%,lead-to-visit 率改善 15%,cart-to-visit 率改善 11%。
CLS:会话窗口、算例与三种常见成因
CLS 反映的是意外布局位移的最大突发(一个上限 5 秒的会话窗口,其中位移间隔小于 1 秒),而不是生命周期总和。大部分突发通常来自少数几个大位移,可以提前阻止。
分数由每个意外布局位移的两个因子相乘得出:
- 影响分数:布局变化期间视口中被位移的部分。
- 距离分数:不稳定元素移动的距离,相对于视口最大维度。
假设一个横幅把整个视口向下推了高度的 22%。该位移的距离分数是 0.22,影响分数是 1.0,得到 CLS 分数 0.22。单次位移就超过良好 CLS 的 0.1 阈值。因为分数是影响乘以距离,一次大的晚期位移可以花光预算,而页面中部的小位移贡献很小。
用户交互后 500 毫秒内的布局位移不计入计算,因为有意位移(菜单打开、表单展开)不应算在分数里。
CLS 最常见的原因是没有显式尺寸的图片、动态注入内容(广告或 cookie 横幅),以及替换字体与回退字体尺寸不同。
防止 CLS,要在媒体、字体或客户端内容改变页面之前给浏览器稳定布局。
为视觉内容预留空间
图片和视频加载时没有 width 和 height 属性,浏览器无法预留空间,媒体加载后下面所有内容都会下移。始终在 、、`` 元素上包含 width 和 height,或者对响应式图片使用 CSS aspect-ratio。Next.js Image 组件对静态导入图片自动推断尺寸,并在传入 width 和 height 或 fill 属性时预留空间。对于渲染时尺寸未知的内容,例如第三方小部件或注入横幅,合理的 min-height 可以把容器增长时的位移限制住。
稳定 Web 字体
使用 font-display: swap 时,文本先用回退字体渲染,自定义字体加载后再重渲染;如果度量不同,替换会导致周围内容重排。用 CSS 覆盖描述符(size-adjust、ascent-override、descent-override、line-gap-override)让回退字体匹配自定义字体,使两者占据相同垂直空间,替换就不可见。Next.js 的 Font 模块在构建时自动完成预加载和度量匹配。相关文档:Next.js Font。
防止水合布局不匹配
在 React 和 Next.js 应用中,服务端渲染的 HTML 与客户端水合时渲染的内容不同,会导致 CLS 飙升。在 useEffect 中用 window.innerWidth 切换移动或桌面导航,会在水合后从一个版本跳到另一个版本;对已认证组件在服务端渲染 null,客户端注入时会推下页面。响应式组件切换应依赖纯 CSS(@media 查询)。
基础设施对 LCP 的影响
TTFB 是 LCP 的第一阶段,服务器响应和网络路径直接决定它。把 HTML 放到离用户更近的边缘、用 CDN 缓存内容、减少源站往返,都可以直接压缩 TTFB。Shopify 的中位桌面 LCP 降低了 228ms。
返回导航时,bfcache 可以避免重新下载和重新渲染,从而改善 LCP 和 INP。但页面上有 unload 监听会失去 bfcache 资格,Cache-Control:no-store 也会阻止页面进入 bfcache。用 Not Restored Reasons API 查看页面没有进入 bfcache 的原因。
生产环境怎么量
CrUX 使用 28 天滚动窗口,并按第 75 百分位报告。Google 的 Core Web Vitals 评估只基于现场数据,所以生产验收看 CrUX 和 RUM,实验室工具用来诊断。
用 web-vitals 采集真实用户指标,并利用 inputDelay、processingDuration、presentationDelay 归因定位 INP。Vercel Speed Insights 可以直接查看部署在 Vercel 上的真实用户 Core Web Vitals。实验室数据回答“哪里可能有问题”,现场数据回答“用户实际经历了什么”。