从 3.2s 到 0.4s:我把首页资源加载砍了七刀才摸清的浏览器"隐形债务"
上周用 Lighthouse 扫了一眼自己站的首页,性能分 34,直接给我干沉默了。明明服务器响应才 120ms,白屏时间却飙到 3 秒多——问题全卡在浏览器那边。折腾了三四天,记录一下这七刀是怎么砍的,刀刀都是之前觉得"无所谓"的懒账。
第一刀:图片的 srcset 不是摆设,但我以前当摆设
站里用户头像和封面图之前统一丢的 800px 原图,手机端也硬拉。改成根据 DPR 和容器宽度动态取 200/400/800 三档,WebP 优先、JPEG 兜底。单这一项,图片体积从 1.8MB 压到 380KB。注意 srcset 的 w 描述符和 sizes 要配对写,sizes 写错等于白配,浏览器会按最大的取。
第二刀:字体文件我养了三个"胖子"
之前图省事,中文字体直接引了完整 TTF,一个就 8MB。现在拆成子集化(unicode-range 按需切片),只打包常用 3500 字,再压成 WOFF2。首屏字体从阻塞渲染改成 font-display: swap,先让系统黑体顶着,加载完再换。FOUT 是有点闪,但比白屏干等强十倍。
第三刀:CSS 的 @import 是串行刺客
有个习惯特别坑:主 CSS 里 @import 引谷歌字体和图标库。浏览器遇到 @import 必须停下来先下载、解析完才能继续,链越长越慢。全改成 link 标签并行加载,rel="preconnect" 提前握手 fonts.googleapis.com,DNS+TCP+TLS 省掉小半秒。
第四刀:JS 的 defer 和 async 我用混了两年
以前不管三七二十一全加 async,结果依赖 jQuery 的脚本偶尔报错 "$ is not defined"。理清楚之后:独立且无依赖的分析脚本、广告脚本走 async;需要操作 DOM 的业务脚本走 defer;首屏关键路径的少量内联 JS 直接写死在 HTML 里,省一次往返。模块化的用 type="module" 天然 defer,但记得给旧浏览器备 nomodule 回退。
第五刀:缓存策略不是 max-age 越大越好
之前静态资源一股脑 Cache-Control: public, max-age=31536000,结果更新后用户刷不出来。现在分档:HTML 用 no-cache 或短时效(重新验证),带 hash 的 JS/CSS 才敢给一年,字体图片给一个月。CDN 回源策略配合 ETag + Last-Modified,更新时文件名里嵌 content-hash,旧资源自然淘汰,不用喊用户清缓存。
第六刀:预加载不是越多越好,是越准越好
跟风加了一堆 link rel="preload",结果 Chrome 控制台飘黄警告"未使用"。现在只预加载 LCP 元素(首屏最大图或关键字体),用 prefetch 给下一页可能用的路由组件,dns-prefetch 给第三方域名。preload 的 as 类型必须写对,写错会重复下载,比不加还亏。
第七刀:骨架屏不是面子工程,是感知性能
这刀最玄学。实际加载时间没减多少,但用户看到骨架屏在动,白屏焦虑没了。用 CSS 渐变画了个极简骨架,内联在 HTML 头部,不依赖任何外部资源。配合 Service Worker 做离线兜底,二次访问秒开。Lighthouse 的 Performance 分从 34 爬到 91,但说实话,用户体感变化比数字更直观。
最后一点碎碎念
优化别只盯着 TTFB 和服务器。我这次 120ms 的服务器响应,被浏览器渲染拖成 3.2 秒,问题全在前端资源调度上。Lighthouse 的 Opportunities 一条条过,DevTools 的 Performance 面板录十遍,比看十篇教程管用。另外,改完记得用 3G 节流模拟测,WiFi 下全是幻觉。
你们站的首屏最大资源杀手一般是啥?我这次是图片和字体,下次可能轮到第三方脚本了。