今天顺手给 agent101 跑了一次 PageSpeed Insights。
桌面端 88,移动端 58。
58 那个橙圈往那儿一摆,挺刺眼。但我看完报告之后的第一反应不是“完了要重构”,而是——这 58 分丢得特别蠢。
蠢到什么程度:整个页面真正慢的原因只有一个,藏在一个我从来没打开看过的文件里,一行。
先立个靶子。很多人跑完 PageSpeed 就干两件事:看那个大圆圈,然后把报告里飘红的条目从上到下挨个改一遍。
这两件事都不太对。
分数是五个指标的加权平均。你不知道权重,就不知道该先修哪个。更要命的是,报告告诉你的是“哪儿慢”,不是“为什么慢”——这中间隔着一段得自己动手挖的路。
我这篇就按这个顺序来:先把指标讲明白(每个测什么、及格线多少),再回头看我这份报告,然后把我一路挖到根因的过程原样写出来,包括我猜错的那几次。
尽量不说行话。

一、先说清楚:那个分数是怎么算出来的
这是最该先知道的一件事,但几乎没人一上来就讲。
Lighthouse 的性能分不是什么综合评价,它就是五个指标按固定权重加权平均出来的一个数:
| 指标 |
权重 |
一句话说它管什么 |
| TBT(总阻塞时间) |
30% |
页面卡不卡 |
| LCP(最大内容绘制) |
25% |
主要内容多久出来 |
| CLS(累积布局偏移) |
25% |
页面跳不跳 |
| FCP(首次内容绘制) |
10% |
第一眼多久有反应 |
| SI(速度指数) |
10% |
内容填充得快不快 |
(这是目前主流版本的权重,Google 改过几次,以你自己跑出来的那版为准。)
看出问题没有——
TBT 一个指标就占 30%,比 FCP 和 SI 加起来还多一倍。
意思是如果你 JS 写得很重、主线程被长任务卡住,你在最贵的那 30% 上直接归零,后面怎么压图片都救不回来。
反过来,TBT 和 CLS 都拿满,你手里就已经攥着 55 分的基本盘。
记住这个数:TBT + CLS = 55%。这是性能分的胜负手。

二、这些指标,挨个说人话(附及格线)
已经很熟的可以跳到第三节看我的报告。
我按用户打开一个页面的时间顺序排,比按权重排好理解。
TTFB(首字节时间) 你点了链接,服务器把第一个字节吐回来用了多久。测的是服务器和网络,跟前端代码基本无关。及格线:800ms 以内算好。
FCP(首次内容绘制) 屏幕上出现第一个内容——第一行字、第一个色块。用户的感受是“哦,它动了”。及格线:1.8 秒以内算好,超过 3 秒算差。
LCP(最大内容绘制) 首屏里最大的那块东西画完的时刻,通常是主视觉图或者大标题。用户的感受是“加载完了”。及格线:2.5 秒以内算好,超过 4 秒算差。
这个是三大核心指标之一,也是 SEO 真在意的那几个之一。
SI(速度指数) 不看某个时间点,看整个加载过程里屏幕被“填满”的速度。可以理解成 FCP 和 LCP 的连带结果。及格线:3.4 秒以内算好。
TBT(总阻塞时间) 从 FCP 开始,主线程被超过 50ms 的长任务占住的总时长。说白了就是:这段时间你点什么都没反应。及格线:200ms 以内算好。
CLS(累积布局偏移) 加载过程中内容乱跳的累计量。就是那个经典体验:手指刚要点“取消”,上面一张图加载出来把按钮顶下去了,你点成了“确认”。及格线:0.1 以内算好。
INP(交互到下一次绘制) 不在 Lighthouse 打分里,但它 2024 年 3 月已经替掉 FID,成了官方三大核心指标之一。测的是真实用户点击后页面响应的延迟。及格线:200ms 以内算好。
TBT 和 INP 什么关系?TBT 是实验室里观察主线程阻塞情况的指标,和真实用户 INP 有关联,但不能互相替代。 TBT 高,真实用户的 INP 通常也需要重点关注。

三、我的 58 分,是怎么丢的
先说一句报告上很多人被绕过的话。
我这份报告最上面写着「无任何数据」。
PageSpeed 其实是两套数据拼起来的:上半部分是真实用户数据(CrUX),来自全世界真正访问过你站的 Chrome 用户,这个才是 Google 拿去参考的;下半部分是实验室数据,Google 在自己机房里模拟一台设备跑一遍。
我站太新,访问量没到采样门槛,所以上半部分是空的,只有实验室数据。
那实验室长什么样?报告角落写得很清楚:模拟 Moto G,低速 4G 节流。
一台中低端安卓机,一条被掐过的网。
它不是在测“我的电脑连宽带打开有多快”,是在测“一个网络不好的普通安卓用户打开有多慢”。
好,看数据。被测的是 agent101 首页,一个 Next.js 做的 AI Agent 术语站:
| 指标 |
移动端 |
桌面端 |
及格线 |
| FCP |
7.7s 🔴 |
1.6s |
< 1.8s |
| LCP |
8.0s 🔴 |
1.6s |
< 2.5s |
| SI |
7.7s 🔴 |
1.6s |
< 3.4s |
| TBT |
0ms 🟢 |
0ms |
< 200ms |
| CLS |
0.001 🟢 |
0 |
< 0.1 |
| 性能分 |
58 |
88 |
— |
把第一节那张权重表拿过来一对账,58 分怎么来的一目了然:
TBT 0 毫秒满分,CLS 0.001 满分。 这俩加起来 55%,我全拿了。
FCP 7.7s、LCP 8.0s、SI 7.7s,全部远超“差”的判定线。 这仨加起来 45%,我几乎全丢。
55 + 一点零头 ≈ 58。
这份报告最有意思的地方在这:我的页面一点都不卡。
TBT 是 0,主线程几乎没被阻塞过。CLS 接近 0,加载过程稳得很。DOM 只有 390 个元素、深度 10,很轻。
TTFB 只有 20 毫秒。 服务器快得离谱。
一个服务器 20ms 响应、主线程不卡、布局不跳的页面,首屏花了 8 秒才画出来。
那 8 秒它在干嘛?

四、往下挖:根因是一行 @import
报告说是 Google Fonts 阻塞了渲染,还给了个数:移动端移除这条阻塞,预计缩短 7390 毫秒。
7.4 秒。8 秒的 LCP 里有 7 秒多在等字体。
但报告到这儿就停了。它只说“Google Fonts 阻塞”,没说这东西是从哪儿来的。
我第一反应是:那我去 HTML 头部把那行 <link> 挪一下不就完了。
结果一看,HTML 里压根没有 Google Fonts。
curl -s https://agent101.gao-fei.com/ > home.html
grep -oE 'fonts\.(googleapis|gstatic)\.com[^"]*' home.html
空的。
整个首页就一个样式表 ec91b116b7009434.css,一个图片 preload,一个 script preload。没有字体,没有 preconnect,什么都没有。
那字体从哪儿来的?只可能在 CSS 里。翻开那个 CSS,第一行:
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500;700&family=Noto+Sans+SC:wght@400;500&display=swap");
找到了。
这是字体能待的最坏的位置。
写在 HTML 的 <link> 里,浏览器扫描 HTML 时就能看见它,可以提前发请求,跟别的资源并行。
写在 CSS 的 @import 里,链条变成串行的四跳:
HTML → app.css → fonts.googleapis.com/css2 → 一堆 woff2
浏览器必须先把 app.css 下完、解析完,才知道还得去 Google 要东西。而这中间每一跳都是一次完整的 DNS 查询 + TLS 握手 + 往返。
这也顺带解释了我为什么没加 preconnect——HTML 里根本看不见 fonts.gstatic.com 这个域名,谁会想到去给一个自己看不见的域名建连接。
报告能告诉你哪儿慢。为什么慢,得自己 curl 一下。

五、我明明加了 font-display: swap
回去看那行 @import 的结尾:&display=swap。
加了。一直都加着。
那为什么 FCP 还是 7.7 秒?
这个地方我卡了一会儿,也是这次整个排查里我觉得最值钱的一个收获:
font-display: swap 和“渲染阻塞”,是两件完全不同的事。九成的性能文章混着讲。
swap 管的是:字体文件没到的时候,字要不要先用系统后备字体显示出来。
它管不了:字体的那个 CSS 文件没到的时候,页面敢不敢开始画。
浏览器的逻辑是——我看见一个样式表引用,我不知道它会不会改变页面长相,那我就先不画,等它下完再说。
@import 引进来的那个 css2?family=...,它本身就是一个样式表。
所以顺序是这样的:
- 浏览器等 app.css(阻塞)
- 解析 app.css,发现
@import,去要 Google 的 CSS(继续阻塞)
- Google 的 CSS 回来了,
swap 这时候才开始生效
- 但第一屏早就该画完了
swap 在第三步才上场。而我丢的那 7 秒,是在第一步和第二步里丢的。
swap 救的是字,救不了页面。
这一条我原本的草稿里写的是“去加 font-display: swap”。要不是动手 curl 了一次,我就把一条我早就做过的事当成建议写出去了。

六、800KB 的中文字体,用户手机里本来就有
接着挖。我在本机开 DevTools,勾上 Disable cache,挂上 Slow 4G 节流,刷新,筛 Font。
底下那行数字:
17 / 34 requests,934 kB / 1,381 kB transferred。
翻译一下:整页 34 个请求,17 个是字体。整页传输 1.38 MB,字体占 934 KB,七成。
再看列表。14 个文件名长得一模一样,只有末尾编号不同:
k3kXo84MPvpLmixcA63oeALhLOCT-...V.102.woff2 60.8 kB
k3kXo84MPvpLmixcA63oeALhLOCT-...V.105.woff2 53.1 kB
k3kXo84MPvpLmixcA63oeALhLOCT-...V.110.woff2 61.6 kB
...
这是 Noto Sans SC。中文字多,Google Fonts 会按 unicode-range 把它切成上百个子集,浏览器用到哪几个字就下哪几个文件。听着挺聪明,但落到我这儿就是一次拉了 14 个包,800 多 KB。
Time 列更能说明问题:从 2.85 秒一路排到 5.98 秒,十几个请求密密麻麻挤在 5 到 6 秒那一段。它们不是排队一个个下的,是全都卡在同一条起跑线后面——得先拿到 Google 的 CSS,才知道要下哪些子集。
而我的 DOMContentLoaded 是 2.47 秒。
DOM 两秒半就准备好了,第一个字体 2.85 秒才落地,最后一个拖到将近 6 秒。中间这三秒半,页面就在那儿干等。
这就是报告里“LCP 主要耗时在元素渲染延迟”那句话的真身。
然后我去看了 DevTools 的 Rendered Fonts,这一眼最扎心:
Noto Sans SC — Network resource
Inter — Network resource
Network resource,意思是走网络下的。
可我的 font-family 是这么写的:
font-family: Inter, -apple-system, BlinkMacSystemFont,
PingFang SC, Noto Sans SC, Microsoft YaHei, sans-serif;
PingFang SC 是 iOS 和 macOS 自带的。Microsoft YaHei 是 Windows 自带的。安卓那边系统字体本来就是 Noto 系。
每一个访问我这个站的中文用户,设备里都已经装着一套能用的中文字体。
800 KB,将近四秒等待,只为了让用户看到一份他手机里本来就有的字。
(还有个我没搞明白的:Rendered Fonts 那儿显示的是 Noto Sans SC Thin,但我 @import 里只请求了 400 和 500,压根没要 Thin。这个先记着,等下一篇搞清楚了再说。)

七、修复清单,和我的排期
按性价比从高到低排。可以直接抄。
第一刀,砍掉 Noto Sans SC。
这一刀最狠,省 800 KB,直接砍掉 14 个请求。中文交给系统字体,PingFang SC / Microsoft YaHei 在真实设备上的表现跟 Noto 差别很小,不值 800 KB。
/* 改前 */
@import url("...&family=Noto+Sans+SC:wght@400;500&display=swap");
/* 改后:只留拉丁字体和等宽 */
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap");
顺手把 JetBrains Mono 的 700 也砍了,我数了一下代码块里没用到粗体等宽。
第二刀,把字体从 @import 里搬出来。
搬到 HTML 头部,四跳变三跳,而且能跟别的资源并行:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap">
fonts.gstatic.com 那行的 crossorigin 不能漏,漏了这条 preconnect 白建。字体是匿名跨域请求,不带这个属性,浏览器会另开一条连接,等于没提前。
第三刀,自托管,第三方请求归零。
Next.js 直接用 next/font,构建期把字体下到自己域名下,自动 preload,自动做 fallback 字形匹配(这个能顺手把 CLS 再压一点):
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
weight: ['400', '500', '600'],
display: 'swap',
})
这一步做完,前两刀都不用了。
第四刀,关掉多余 polyfill。 报告揪出来打包了 Array.at、flat、flatMap 这些现代 API 的垫片,白浪费 11.6 KB。调 browserslist,别再为 2018 年的浏览器编译:
"browserslist": ["last 2 versions", "not dead", "> 0.5%"]
我自己的排期:
第一刀和第二刀今天就上,成本半小时,改两个文件。这两刀下去,按报告给的估算,移动端那 7.4 秒能收掉大半。
第三刀放下个迭代,因为顺带要处理等宽字体的子集化,麻烦一点。
第四刀跟着下次依赖升级一起做。
改完我重新跑一遍 PSI,把前后对比发出来。这种带实测数字的前后对比,比任何优化教程都直观。

八、顺便说说那个 96 分和那两个 100 分
性能之外还有三个维度:无障碍 96,最佳做法 100,SEO 100。
看着漂亮,里面有两个坑。
坑一:96 分的无障碍,扣在“高级灰”上。
主要扣分项是文本对比度不足,具体是我那个 .text-ink-mute 类——代码说明、时间戳、标签、备注,全是小字配浅灰。
典型的设计审美和可读性打架。设计稿上看着克制、有呼吸感,但对视力一般的人、对户外强光下看手机的人,那几行字基本等于不存在。
还有一个更隐蔽的:我好几个链接的文字都是「详情 →」。
视觉上没问题,你能看见它在哪张卡片里。但屏幕阅读器用户会听到一连串“详情、详情、详情”,完全不知道每个链接跳去哪。
报告里还有 10 项标着“需要人工复核”——键盘导航、自定义控件焦点、ARIA 标签这些,自动化测不了,得自己拿键盘 Tab 一遍。这活儿我还没干。
坑二:最佳做法 100 分,不等于安全。
这个特别容易让人麻痹。100 是满分,但报告下面那堆不计分的诊断项里写着一串缺失:
- 没有 CSP(缺
script-src),XSS 防线敞着
- 没有 COOP 响应头
- 没配 Trusted Types
- HSTS 配了,但缺
includeSubDomains 和 preload
Lighthouse 只给它认识的项打分,不给你的风险打分。 满分的意思是“我查的这几项你都过了”,不是“你安全了”。
这几条对个人项目优先级不高,但心里得有数,而不是看见 100 就觉得稳了。

九、报告里那个新东西:智能体浏览 2/2
最后这块是今年的新玩意儿,不少人跑报告时看到过但没细看。
分数栏里性能、无障碍、最佳做法、SEO 后面,多了第五个:智能体浏览。它不给 0 到 100 的分,只给一个比值。我这次是 2/2。
这是 2026 年新增的实验性类别,目前官方仍把它定位为探索中的能力,标准也还在演进。
它测的东西和前面四个都不一样——
前四项测的是人用得爽不爽,SEO 测的是爬虫读不读得懂。
这一项测的是:一个 AI Agent 能不能读懂你的页面,并且在没有人操作的情况下把事办完。
目前主要看几样:
无障碍树干不干净。 Agent 不看你渲染出来的漂亮按钮,它读的是无障碍树,就是屏幕阅读器读的那套结构。按钮有没有名字、角色对不对、层级乱不乱,直接决定 Agent 能不能找到该点的东西。
布局稳不稳(CLS)。 这里换了个含义。对人来说 CLS 差是“手滑点错”,对 Agent 来说是它定位到了坐标,等它动手时按钮已经挪走了,于是点错。
llms.txt。 放在域名根目录的一个 Markdown 文件,给 AI 工具一份站点导览。它目前不是正式标准,也不应被当成 SEO 加分项;如果维护成本很低,可以把它当作额外的机器可读说明。
WebMCP。 让页面把自己的能力以工具形式注册出去,让 Agent 直接调用而不是模拟点击。规范还在动,现在实现属于追一个移动靶。
我这次的 2/2,说白了不是因为我做了什么智能体适配,是无障碍做得还行、CLS 接近 0,白捡的。llms.txt 和 WebMCP 在报告里都标着待人工完善。
但这事有点意思。一个讲 AI Agent 的站,被一个 AI Agent 友好度的审计项打了满分,而满分的来源,是我当初为人类做的无障碍工作。
你为屏幕阅读器做的每一分努力,现在都自动折算成了 Agent 的可用性。
这可能是无障碍这件事这些年最实际的一次回报。

最后说几句
回到那个公式:
TBT 30% + LCP 25% + CLS 25% + FCP 10% + SI 10%。
我的 58 分,本质是 55 分的基本盘,加上一条被一行 @import 拖垮的关键渲染路径。代码没毛病,服务器 20ms,DOM 干干净净。毛病在一个我从来没打开看过的 CSS 文件的第一行。
三条最实在的建议:
一、先看指标构成,别看总分。 打开报告第一件事是找五个指标里哪个红,然后对着权重算这个红的值多少分。有些红条目改完只涨一两分,纯属浪费周末。
二、报告只到“哪儿慢”,“为什么慢”得自己挖。 我要是信了报告那句“Google Fonts 阻塞”就直接去改 HTML,会发现 HTML 里根本没有 Google Fonts,然后原地懵掉。curl 一下,八个字符,省两小时。
三、中文站慎用 Google Fonts 的中文字体。 拉丁字体几十 KB,中文字体几百 KB 起步,而你的用户设备上早就有一套能用的了。这笔账在低速网络下特别不划算。
我这次的改动今天就上,preconnect 加上、Noto Sans SC 砍掉。改完重新跑一遍 PSI,前后对比我下次发。
性能优化最难的从来不是修,是知道该修哪个。而知道该修哪个之前,你得先愿意打开那个文件看一眼。
你的站跑出来多少分?评论区甩个数,最惨的那个我帮你看报告。