首屏白屏时间长但 domcontentloaded 早触发,说明关键 css 阻塞了 render tree 生成;必须内联首屏关键 css(如 .hero-title、.cta-button),体积≤14kb,非关键 css 通过 media="print" onload 或动态插入异步加载。

首屏白屏时间长,但 DOMContentLoaded 很早就触发了?那基本可以断定:关键 CSS 阻塞了渲染树(Render Tree)生成,而不是 DOM 构建本身。优化核心就一条——把首屏真正用到的 CSS 拿出来,内联进 ;其余 CSS 必须异步加载,且不能破坏媒体查询或暗色模式等逻辑。
怎么提取真正首屏用到的 CSS?别手动拷贝
手动从 app.css 里找 .hero-title、.nav-item 这类选择器,容易漏、难维护、上线后一改结构就失效。真实项目必须走自动化路径:
- 本地开发用
puppeteer+critters:启动无头浏览器访问http://localhost:3000,抓取首屏 DOM 快照,反向计算哪些 CSS 规则被实际用到 - Vite 项目直接配
vite-plugin-critical,build 阶段自动提取并注入<style></style>块 - Webpack 项目建议用
mini-css-extract-plugin分离出critical.css,再通过html-webpack-plugin的templateParameters注入内联内容 - 提取后务必检查体积——内联 CSS 控制在
14KB内(HTTP/2 单个 TCP 包的理想大小),超了反而增加解析开销
非关键 CSS 怎么延迟加载又不触发 FOUC?
不能删掉 <link rel="stylesheet" href="non-critical.css">,否则 @media (prefers-color-scheme: dark) 或 @media (min-width: 768px) 全部失效。关键是让浏览器“先跳过”,等首屏稳定后再加载:
- 推荐写法:
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">——media="print"让浏览器初始不加载,onload在资源就绪后才切换为all,避免闪动(FOUC) - 更稳妥的做法是结合
requestIdleCallback或DOMContentLoaded后动态插入:const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = 'non-critical.css'; document.head.appendChild(link); - 绝对不要用
@import引入任何关键样式——它强制串行加载,实测拖慢FCP超300ms
为什么 DOMContentLoaded 早但页面还是白屏?
因为浏览器渲染流程卡在「CSSOM 构建」这一步。你看到的白屏,不是 HTML 没解析完,而是 Render Tree 没法生成:
- Network 面板里,如果
main.css的Initiator显示为parser,且耗时 >200ms,基本就是它在阻塞 - Performance 面板中,
Recalculate Style阶段明显被卡在某个 CSS 加载之后,说明 CSSOM 构建成了瓶颈 - 把整站 CSS 打包成一个
app.css全量加载,等于让后台管理页、弹窗组件的样式白白拖慢首页——这种“一锅炖”式打包是首屏性能最大敌人之一
关键路径不是只调一个参数或加个 async 就能解决的事。它要求你清楚知道哪几条规则真正在首屏生效、哪段 JS 真正影响布局、哪个字体文件用户第一眼就看见。工具能帮你提取,但判断权始终在你手上——比如 .modal-overlay 即使没在首屏出现,但如果它带了 position: fixed 和 z-index: 9999,就可能影响渲染层叠上下文,得谨慎评估是否纳入关键 CSS。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











