first contentful paint 是什么:怎么量、怎么把首屏那一下提前
first contentful paint 量的是页面上第一块内容被画出来的那一刻,Lighthouse 的及格线是 1.8 秒。这篇讲它到底数什么、哪几个 head 改动能把首屏那一下提前,以及什么时候该改看另一个指标。

first contentful paint 指的是浏览器把页面上第一块内容画到屏幕上的那一刻——可能是一行字、一张图或一块画布,谁先落下算谁。它之前,屏幕是白的;它之后,访客才知道页面在动。Lighthouse 拿 1.8 秒当及格线来给它打分,它通常也是所有加载指标里人们最先感知到的那一个。
读这篇前
你需要能改到站点的 <head>,最好还能碰服务器或 CDN 的设置。不用构建工具。这篇假设你已经知道「head 里一张样式表为什么会拖住首屏」,如果不清楚,render blocking resources 讲了那套机制。这个指标往上接的是 largest contentful paint;至于真实站点现在都发出去多少东西,34 个首页的 first contentful paint 实测 是这篇的读数版。
first contentful paint 到底在量什么
它量的不是「页面能用了」,而是「页面上有任何东西被画出来了」。所以它既便宜又好被误读。浏览器拿到 HTML 后要先建渲染树,再画下至少一个文字或图片节点;谁挡住建树,谁就把 first contentful paint 往后推。多数站点上,那个挡路的是样式表——布局算不出来,浏览器就不敢画文字。
这个指标的起点是导航开始,不是首字节到达、也不是第一段脚本执行。这一点很关键:一台快服务器配一张阻塞样式表,first contentful paint 经常比一台慢服务器配内联临界 CSS 更差,因为服务器省下的时间全被藏在「等样式表」里了。下表是每个阶段各自管什么、first contentful paint 在等谁。
| 阶段 | 何时结束 | 是否挡住 FCP |
|---|---|---|
| DNS、TCP、TLS | 连接就绪 | 是,之前什么都起不来 |
| 服务器响应 | HTML 开始到达 | 是 |
| 阻塞样式表 | CSS 取回并解析完 | 是,没有它不画 |
| head 里的同步脚本 | 脚本跑完 | 是,解析会暂停 |
| 延迟脚本与图片 | 更晚 | 否 |
first contentful paint 就是「你把多少东西挡在了第一像素之前」的代价。
由此有两个推论。第一,余地最大的两个抓手是样式表和 head 里的同步脚本,其余要么躲不掉、要么本来就延迟了。第二,因为只要有任何内容出现 first contentful paint 就会触发,一个先画占位块的页面可以拿到很好的 FCP,却仍然迟迟不给读者想看的东西——那才是 largest contentful paint 负责量的。
还有一件常被忽略的事:同一个页面,你本机看到的数和读者手机上看到的数可能差出好几倍。限速移动端档位就是为了把这个差距显式地摆出来,所以前后对照时必须用同一档位,而不是拿本机的暖和网络去跟读者的冷启动比。
按这个顺序做
五步,每步都有一个能先跑一下的检查。第一步免费,它决定后面几步值不值得做。
- 先量,再动手。把页面丢进 PageSpeed Insights 或 Lighthouse,记下 FCP 值和它列出的阻塞 URL 清单。完成标志是你手上有一个数、一份清单,而不是一种感觉。
- 砍掉阻塞样式表的数量。head 里每一个
<link rel="stylesheet">都是一条单独的链。把总是同时加载的几张拼成一张,把跟首屏无关的挪出关键路径——打印样式、轮播的 CSS、某个组件的 CSS。完成标志是报告里的阻塞条数下降了。 - 给首屏内联临界 CSS。把给页头和大图用的规则抽出来,放进第一张样式表链接之前的一个
<style>块,完整样式表放到后面再加载。完成标志是在限速网络下,首屏能从这段内联块渲染出来。 - 能延后的就延后。必须跑、但不必在第一帧之前跑的经典脚本,加上
defer;不关键的样式表用media切换或「preload 后替换」的方式异步加载。完成标志是 head 里不再有同步脚本。 - 复测,并把差值留下来。在同一个连接档位下,跟第一步的数比。完成标志是你能说清改了什么、变了多少,并且两次读数都存了档。
交付物:五行就能审完的一个 head
下表是我们对自己页面用的那套检查。每一行都对应上面阶段表里的某一格,所以一处没过就指向一个具体的修法,而不是一句笼统的「再快一点」。
| 检查项 | 通过 | 不过时怎么修 |
|---|---|---|
| head 里的样式表 | 一到两张 | 过多且总是同时加载的,合并 |
| 是否内联临界 CSS | 是 | 首屏在等一张 link |
| head 里的同步脚本 | 没有 | 有 <script src> 没加 defer |
| 大图是否在 HTML 里 | 是 | 它靠脚本注入 |
| 字体是否同源 | 是 | head 里有第三方字体样式表 |
如果你要的是原始条数、而不是一个分数,下面这段命令会返回任意 URL 的阻塞样式表和同步脚本数量。它只是一次普通抓取,不需要无头浏览器,所以它跟爬虫看到的一致,也跟浏览器看到的一致。
curl -sL "$URL" | python3 -c '
import sys, re
h = re.search(r"<head[^>]*>(.*?)</head>", sys.stdin.read(), re.S|re.I).group(1)
css = [s for s in re.findall(r"<link\b[^>]*>", h, re.I)
if re.search(r"rel\s*=\s*[\"\x27]?stylesheet", s, re.I)
and not re.search(r"media\s*=\s*[\"\x27]?print", s, re.I)]
js = [s for s in re.findall(r"<script\b[^>]*>", h, re.I)
if re.search(r"\bsrc\s*=", s, re.I)
and not re.search(r"\b(defer|async)\b", s, re.I)
and not re.search(r"type\s*=\s*[\"\x27]?module", s, re.I)]
print("blocking stylesheets:", len(css), "sync scripts:", len(js))'
做错了会怎样
三种翻车最常见,而且都不报错,所以能拖很久才被发现。
- 内联了太多 CSS。临界 CSS 是被塞进 HTML 里下发的,没法单独缓存。只内联首屏;内联块涨到大约 14 KB 以上,它省掉的那次请求反而不划算,尤其是二次访问本来就命中了缓存。
- 把渲染主内容的脚本也 defer 了。如果大图或商品网格是脚本渲染出来的,把它延后等于把绘制往后推,不是往前拉。要么把那块渲染搬回 HTML,要么承认这一页的 FCP 本质是个脚本指标。
- 替换样式表却没有兜底。异步加载的样式表会有一段页面没样式的时间。如果它在替换前闪了一下,你是拿「慢一点的绘制」换来了「肉眼可见的重排」,读者对后者更敏感。
常见问题
first contentful paint 多少算好?
Lighthouse 以 1.8 秒及以内为及格、3 秒以上为差,口径是限速过的移动端档位。把它们当需要越过的线,而不是目标:FCP 很快但 largest contentful paint 很慢的页面,读者依然觉得慢,因为他看到的是个未完的东西。
first contentful paint 会影响排名吗?
它本身不会。它不是三个 Core Web Vitals 之一,也没有任何 Google 文档给它一个排名权重。它的重要性在于——它是访客感知到的第一件事,而能改善它的那些动作(更少的阻塞资源、内联临界 CSS)通常也会改善真正官方的指标。
服务器很快,为什么 first contentful paint 还是很慢?
因为 FCP 等的是渲染树,不是首字节。一次很快的响应,后面跟一张很大的阻塞样式表,出来的 FCP 就是慢的。先看阻塞清单再考虑升级服务器:把一张样式表挪出关键路径,往往比换一台更快的源站划算。
内联临界 CSS 会不会破坏缓存?
会。内联块是 HTML 的一部分,所以用它的每个页面都要重发这些规则,浏览器也没法跨页缓存它们。把块保持小、只覆盖首屏,而完整样式表仍作为正常可缓存的独立文件,紧跟着加载。
边界说清楚:这篇讲的是「让第一块内容更早出现」,不是「让整页更快完成」。我们没有测过单次改动在你的站上会把 FCP 挪动多少,因为它取决于你的标记、CDN 和连接;在热缓存下,差别可能低于 100 毫秒,肉眼根本看不出来。而当读者真正等的是最大的那块元素、不是第一块时,FCP 就已经不是有用的那个数了。把这套动作和 QueryWin 怎么跟踪页面性能 放在一起做是合理的,但第一步的复测要先做。
本文属于 QueryWin 实操手册 · 第 2 阶


