关键CSS内联必须紧贴之后作为中第二个标签,禁用@import,体积控制在1KB–14KB间,需基于真实视口动态提取首屏样式并验证;非首屏HTML应延迟注入而非display: none。

首先,聊一个最容易被忽略的细节:关键CSS究竟应该放在哪里。许多人认为放在
里就行,但实际位置稍有偏差,效果就会大相径庭。浏览器按顺序解析HTML,标签必须在所有阻塞资源之前生效。如果它被放在任何一个之后,哪怕中间只隔了一个,浏览器都会暂停渲染去加载外部CSS——这样FCP不仅没有改善,甚至可能更差,这通常就是原因所在。在DevTools中,如果看到Parse HTML阶段卡在某个main.css请求上,基本可以断定关键CSS没有抢到“首发位置”。
正确的做法其实就几条铁律:必须是
必须是第二个,中间不能有任何空行、注释或其他标签插队。所有必须乖乖排在后头。还有一条红线:@import绝对禁用,因为它会在内部触发同步网络请求,相当于自己增加了一个阻塞点。
内联体积超过1KB就应警惕传输延迟
很多人认为内联越多越快,但从实测数据来看,内联CSS从1KB增长到5KB,在3G弱网环境下会使HTML主文档传输延迟增加400ms以上,TTFB和初始解析都会受到影响。更关键的是,服务端无法缓存这部分样式,每次HTML变更都必须重传全部内联内容。
真正有效的提取,必须基于真实视口和首屏DOM节点动态捕获,而不是简单截取main.css开头部分。具体工具方面,可以使用critters(Vite/webpack)或critical CLI驱动Puppeteer,模拟{ width: 375, height: 667 }访问页面。手动验证时,禁用网络后刷新,还能正常显示的标题、按钮、轮播容器,其对应的选择器才值得保留。CSS-in-JS生成的动态类名(如jsx-abc123)识别率低,建议SSR时用renderStylesToString()序列化关键样式。内联体积建议控制在14KB以内(HTTP/2帧大小限制),gzip后目标为4–8KB。
非首屏HTML结构必须“不创建”,而非display: none
很多团队习惯用display: none包裹非首屏区块,但这其实是个误区。它仍然会参与CSSOM计算和DOM构建,只是不绘制而已。真正需要的是“不创建”。所谓非首屏,是指用户滚动前不可见、且不参与初始布局计算的内容。强行渲染它们会延长DOM构建时间、增加内存占用,还可能触发不必要的重排。
正确的做法是:将轮播图下方的推荐列表、评论区、侧边栏等,改用或纯JS动态插入(innerHTML或document.createElement)。对于tab切换类内容,初始只保留激活tab的HTML,其余用data-html属性暂存字符串,切换时再解析挂载。Chrome DevTools的Rendering > FPS Meter和Layers面板可以观察首屏区域是否包含大量隐藏/折叠节点。
preload不能替代内联,且仅适用于浏览器无法自动发现的资源
不是越多越好,滥用会抢占带宽、挤掉真正关键的资源。它只影响下载优先级,不改变执行时机。判断是否该使用preload,有一个简单标准:这个资源是否在DOMContentLoaded前就必须就位?如果不是,大概率不需要。
具体建议是:只预加载首屏强依赖、且浏览器无法自动发现的资源——比如背景图、关键Web字体(记得加crossorigin)。不能替代内联,只能辅助非关键CSS;预加载的样式仍需后续挂载,无法参与初始渲染树构建。不要对所有都preload:它不会推迟执行,反而可能干扰真正关键的资源。旧浏览器会忽略preload,无副作用,但别指望它解决兼容性问题。
最后说一个最容易被忽略的点:内联关键CSS不是“复制粘贴”,而是提取加验证。工具生成后,必须打开DevTools → Network → Disable cache,刷新看FCP是否提前,再对比移除内联后的差异。否则优化可能只是表面功夫。
