Prismic 优化检测 免费在线检测工具
首页 › 笔记 › 网站拿不到 Lighthouse 满分,通常卡在这 6 件事

网站拿不到 Lighthouse 满分,通常卡在这 6 件事

我是 Cody Wang,前端工程师,平时做 Prismic / SvelteKit / Next.js 的网站开发和性能优化。

下面这 6 件事,都是我自己一个个改过来的。不写原理综述,只写我遇到的样子、我一开始怎么想、后来怎么改,以及官方文档在哪里。看完你至少能自己排掉一半。

#1. 图片:alt、尺寸、体积

先说我见过最多的:图片没有 alt。

有人补成 alt="图片",有人把关键词堆进去,比如 alt="车库门 车库门安装 新西兰车库门"。这两种屏幕阅读器读出来都很别扭。它其实是在替看不见图的人描述这张图:alt="深灰色钢制车库门安装完成后,从街道看向车道"。纯装饰的图写 alt="",空字符串,不是省略不写。

如果内容是编辑在 CMS 里上传的,模板层得兜底,alt={image.alt || 上下文兜底},别指望每个人都记得填。这一点我在一个官网上改过:32 张图没 alt,改完是 0 张。

官方文档:https://web.dev/learn/accessibility/images

第二件是尺寸。不写 width / height,浏览器不知道图要占多大地方,先把别的元素排上去,图下载完了再重排一次,用户就看到内容往下跳。这就是 CLS 的来源。

<img src="/hero.webp" width="1200" height="630" alt="…" />

官方文档:https://web.dev/articles/optimize-cls

第三件是体积,这个最直观。设计稿导出的 PNG 直接传上去,一张首页横幅就能到 527 KB;还有一个星标图标,SVG,123 KB,正常应该是 2 KB。

我的做法是三件事一起做:转 WebP 或 AVIF;按实际显示尺寸给图,400px 宽的卡片不要塞 2000px 的图;用 CDN 参数自动处理,比如 ?auto=format,compress&q=75&w=800。另外首屏那张图不要加 loading="lazy",要用 fetchpriority="high",其余图片才懒加载。

一个站点我改完是静态资源 996 KB 变 79 KB。

官方文档:https://developer.chrome.com/docs/lighthouse/performance/uses-webp-images

#2. 字体别一次全加载

手机上打开页面,文字先空白一秒才出现,LCP 就慢在这儿。

原因通常是整个字体家族被一次性拉下来:四到六个字重,还带全角字符。字体没到之前,浏览器可能干脆不显示文字。中文站尤其容易踩,因为一个中文字体动辄几 MB。

我的做法:只留真正用到的字重,比如 400 和 600;用 woff2 配 unicode-range 分片加载,英文页面就不要加载中文字体;再加上 font-display: swap,让文字先用系统字体显示出来。

一个站点:Roboto 468 KB 加 Raleway 309 KB,一共 777 KB,拆完是 57 KB 加 43.7 KB,合起来 100.7 KB。

官方文档:https://developer.chrome.com/docs/lighthouse/performance/font-display

#3. 第三方脚本:不是删不删,是什么时候加载

页面滚动发涩、点按钮半天没反应,多半是总阻塞时间太高。

GTM、Facebook Pixel、客服插件这些一般都是同步加载的 JavaScript,跟你的页面抢主线程。主线程被占住的时候,用户点什么都没反应。

直接删掉当然最干净,但很多公司的追踪脚本是业务要的,动不了。那就只在加载时机上想办法:

['scroll','click','keydown','mousemove','touchstart']
  .forEach(e => addEventListener(e, loadTags, { once: true, passive: true }));
requestIdleCallback?.(loadTags);
setTimeout(loadTags, 3500);

页面空闲或者用户第一次交互之后再注入,脚本一个没少,功能也都在。我这样改过一轮,TBT 从 284 毫秒降到 179 毫秒。

这里有个经验:改"什么时候加载"是能跟老板谈的,改"有没有"往往谈不动。这也是我判断一个站能优化到什么程度的前提,第三方脚本保留的情况下,移动端性能分有它自己的上限。

官方文档:https://developer.chrome.com/docs/lighthouse/performance/third-party-summary 和 https://web.dev/articles/tbt

#4. 导航用 button 加点击事件,爬虫看不见

Lighthouse 报 Links are not crawlable,就是说搜索引擎找不到你的链接。

爬虫只认 <a href="...">。如果你的导航是 button 加 on:click 去滚动页面,在爬虫眼里那压根不是链接。站内最重要的入口,搜索里看不见。

<a href="/#whatwedo">我们做什么</a>

样式和滚动体验都不用牺牲,html { scroll-behavior: smooth } 交给 CSS,别用 JS 拦点击。

我在一个站上数过,全站只有 4 个真的 <a href>,其他导航全是 JS。改成真链接之后,SEO 从 83 变成 100。

官方文档:https://developers.google.com/search/docs/crawling-indexing/links-crawlable

#5. canonical 和结构化数据

canonical 是告诉搜索引擎同一个内容以哪个网址为准。带参数的链接、多语言版本、还留着的测试环境,都容易让搜索引擎把它们当成重复内容。结构化数据(JSON-LD)则是让搜索结果里能显示评分、服务范围这类信息。

<link rel="canonical" href="https://example.com/page" />
<meta property="og:url" content="https://example.com/page" />

这段我踩过一个坑,值得单独说。在 Svelte 里写:

<script type="application/ld+json">{JSON.stringify(data)}</script>

页面会把它原样输出成字符串,用户真能在页面上看到 {JSON.stringify(data)} 这行字。因为 Svelte 不会对 <script> 里的内容求值,得改用 {@html ...}。我发现它是因为 Facebook Pixel 报了 Malformed JSON。

所以结构化数据加完,记得在浏览器里看一眼实际渲染出来的东西,别只看代码。

官方文档:https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls 和 https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data

#6. 对比度和触摸目标

可访问性卡在 97、98 上不去的时候,报错基本就是这两个:color-contrast 和 target-size。

浅灰字放白底上看着是挺高级,但 WCAG AA 要求对比度到 4.5:1,大字号可以放宽到 3:1。手机上能点的东西至少要有 24×24 像素,挨得太近也会被判不合格。

这个别靠眼睛判断,跑一遍 Lighthouse,它会直接告诉你具体色值差多少。可读性比"高级灰"重要。

我自己的站上有一处:#a4a4a4 放在 #f7f7f7 上只有 2.85:1,改成 #767676 之后是 4.54:1。页脚的链接本来是 85×20 像素,加了内边距撑到 24 像素以上。

官方文档:https://web.dev/learn/accessibility/color-contrast 和 https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html

#最后说两句实话

Lighthouse 满分不是目的,用户三秒内看到内容、点得动、搜得到才是。它有用的地方是把"用户感觉"翻译成能验证的数字,让你能一条条改,一条条验收。

这 6 件事我自己最多的一次改了 8 轮,才把移动端从 61 分推到 86 分,前提还得是那些第三方追踪脚本一个都不能删。大多数站不用改 8 轮,前三条做完,体积就能砍掉一半。

如果你不确定自己卡在哪,把网址发我,我用自己写的工具给你跑一份免费诊断。会告诉你分数为什么上不去、哪些能改、哪些是第三方脚本的数学上限改了也没用。

工具在这里:https://prismicaudit.com