Critical CSS必须内联于最顶部、体积≤1KB(gzip后)、精准匹配真实移动端视口,剔除@font-face、@keyframes及url()等同步请求项,并确保其后无阻塞型,否则反而加剧首屏延迟。

内联 CSS 能绕过网络请求阻塞,但塞错位置、塞错内容、塞太多,反而让首屏更慢——关键不是“要不要内联”,而是“哪些样式必须内联、怎么塞进 HTML 才真起效”。
critical CSS 必须放在 最顶部,且在所有 <link rel="stylesheet"> 之前
浏览器解析 HTML 时,遇到第一个 <link rel="stylesheet"> 就会暂停渲染,直到该 CSS 下载并构建完 CSSOM。哪怕你前面已经写了 <style></style>,只要后面跟了个未处理的 <link href="app.css">,整个首屏仍被卡住。
- 错误顺序:
<style data-critical>...</style>→<link href="app.css">→ 白屏持续到 app.css 加载完 - 正确顺序:
<style data-critical>...</style>→<link href="non-critical.css" media="print" onload="this.media='all'"> - 用
media="print"是为了让浏览器跳过初始加载,等 DOM 就绪后再激活,避免 FOUC 和样式抖动
提取的 critical CSS 必须匹配真实首屏视口,不能靠手写或全量复制
手动复制 main.css 进 <style></style> 是最常见误区。首屏关键样式只包含影响当前视口内元素渲染的最小集合,比如 html/body 重置、首屏容器尺寸、按钮基础类、骨架屏占位符,不包括折叠区、模态框、@media (min-width: 1200px) 规则。
- 移动端优先:工具提取时必须设 viewport 为
{ width: 375, height: 667 }(iPhone SE)或{ width: 414, height: 896 }(iPhone 12) - 过滤掉
@font-face、@keyframes、display: none元素的样式——它们不参与首屏绘制,却拖慢 TTFB - 暗色模式站点需显式保留
@media (prefers-color-scheme: dark)下的关键规则,否则首屏闪白/闪黑
内联体积必须 ≤1KB(gzip 后),且不能含 @import 或 url()
内联 CSS 不走缓存,每次请求都重传;体积过大还会突破 TCP 初始拥塞窗口(约 14.6KB 压缩后),触发额外 RTT,反而延迟首屏。更重要的是,@import 和 url()(如背景图)在 <style></style> 里仍会发起同步请求,直接阻塞整条规则应用。
立即学习“前端免费学习笔记(深入)”;
- 实测显示:
@import会拖慢 FCP 超 300ms,必须移除 - 背景图、字体文件等资源应从 critical CSS 中剥离,保留在外部 CSS 中异步加载
- 推荐用
critters(Webpack/Vite)或criticalCLI自动 minify + 过滤,别信“手写压缩”
内联后仍需保留完整外链 CSS,且确保无规则冗余
内联只是解决首屏阻塞,不是替代外部 CSS。打印样式、滚动后交互、响应式断点、懒加载模块都依赖外链 CSS。但如果内联部分和外链部分有重复规则,不仅浪费带宽,还可能因优先级冲突导致样式错乱。
- 用 DevTools 的 Coverage 面板检查外链 CSS 实际使用率,删掉被内联覆盖的冗余规则
- 构建时加
data-critical标记便于后续清理,上线前务必离线验证:Network 面板勾选 Offline → 刷新 → 首屏文字/按钮/色块是否正常显示 - SSR 场景下,若用
ReactDOMServer.renderToString,需确保 hydration 前的 DOM 结构与真实首屏一致(比如禁用条件渲染的 loading skeleton),否则提取结果为空或漏掉动态 class
最容易被忽略的一点:内联 CSS 生效的前提是首屏 DOM 已存在。如果页面只有 <div id="app"></div>,再快的 critical CSS 也画不出任何东西——这时候得先解决 SSR 或 hydration 初始化问题,而不是优化 CSS 引入方式。


















