本文约 3,815 字,预计阅读 10 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:LCP 卡住,往往不是图太大,而是 CSS 没到
用 PageSpeed Insights 跑一个内容站,最常见的诊断是「排除阻塞渲染的资源」加一条长长的 CSS 列表。主题为了兼顾各种页面,样式表动辄 60–150KB,浏览器必须把它完整下载解析后才能绘制首屏。用户在 4G 弱网下盯着白屏等一秒多,LCP 数值直接飙到 2.5s 以上,Google 的「良好」阈值就过不去了。
关键 CSS(Critical CSS)的思路很朴素:把渲染首屏真正需要的那一小撮规则(通常 8–20KB)直接内联进 <head>,让浏览器无需等待外部文件就能画出首屏;完整样式表则改成异步加载,画完首屏再补上。这样 LCP 从「等 CSS 下载」变成「等 HTML 到达」,通常能提前 300–800ms。本文给出不依赖商业插件、纯代码可落地的方案,并说明如何按页面类型分发不同的 critical.css。顺带提醒,这项优化和 主题加载的 dashicons 前台无用时的处理 是同一条链路上的事,可以一起做。
原理分析:为什么内联一小段就能提速
浏览器的渲染流水线里,外部 CSS 是「阻塞渲染」资源:HTML 解析到 <link rel="stylesheet"> 时,会暂停构建渲染树,直到该样式下载并解析完成。而内联 <style> 没有网络往返,HTML 一到就能用。二者对比如下:
| 方案 | 首屏绘制时机 | 风险 |
|---|---|---|
| 整份外部 CSS | 等 CSS 下载完成 | 弱网下白屏久 |
| 首屏内联 + 异步加载 | HTML 到达即可绘制 | 内联过大反而变慢 |
| 首屏内联 + 预加载 | 同上,且更快补齐完整样式 | 需正确设置 rel 切换 |
关键点是「内联什么」。判定范围是首屏视口内可见元素所依赖的规则:站点头部、导航、文章标题、首屏正文、首屏图片的占位盒。页脚、评论区、侧边栏往下滚动才出现,都不必进内联。Google 的建议是内联体积控制在 14KB 以内(这是 TCP 慢启动首轮的约数),超过就失去意义。
另外要注意异步加载的副作用:如果只做 rel="preload" 却不在加载完成后切换成 stylesheet,会出现「样式闪烁 FOUC」——先显示无样式内容再突然变好看。所以必须有可靠的 onload 切换和 <noscript> 回退。
保姆级实操
- 生成 critical.css:用 Node 工具
critical针对首页、文章页、列表页分别生成,或用在线生成器;没有构建环境的话,手工抽取头部与正文首屏相关规则也行。 - 放进主题:把生成文件存为
assets/css/critical-home.css、critical-single.css、critical-archive.css。 - 内联:在
wp_head用优先级 1 输出<style>,注意用file_get_contents()读取并做存在性判断。 - 异步化主样式:用
style_loader_tag把主 handle 改成 preload + onload 切换,并保留<noscript>。 - 按模板分发:用
is_front_page()、is_single()、is_archive()判断,避免一个文件打天下。 - 回归验证:Lighthouse 移动模式跑分,对比 FCP/LCP;再拉到浏览器禁用 JS 看
<noscript>是否兜住。
<?php
// 子主题 functions.php:按页面类型内联不同的关键 CSS
add_action( 'wp_head', 'mimangfei_output_critical_css', 1 );
function mimangfei_output_critical_css() {
if ( is_admin() || is_customize_preview() ) {
return;
}
if ( is_front_page() ) {
$file = 'critical-home.css';
} elseif ( is_single() ) {
$file = 'critical-single.css';
} else {
$file = 'critical-archive.css';
}
$path = get_stylesheet_directory() . '/assets/css/' . $file;
if ( ! file_exists( $path ) ) {
return;
}
$css = file_get_contents( $path );
echo '<style id="mimangfei-critical">' . $css . '</style>';
}
<?php
// 主样式改为异步加载,避免阻塞首屏
add_filter( 'style_loader_tag', 'mimangfei_async_main_css', 10, 2 );
function mimangfei_async_main_css( $html, $handle ) {
if ( 'mimangfei-style' !== $handle ) {
return $html;
}
if ( ! preg_match( '/href=[\'"]([^\'"]+)[\'"]/', $html, $m ) ) {
return $html;
}
$href = esc_url( $m[1] );
return '<link rel="preload" as="style" href="' . $href . '" onload="this.rel=\'stylesheet\'">'
. '<noscript><link rel="stylesheet" href="' . $href . '"></noscript>';
}
# 用 critical 生成关键 CSS(Node 环境)
npx critical https://example.com/ --base dist --width 390 --height 844 \
--target "css/critical-home.css" --inline false
常见踩坑
- 内联体积失控:把整份样式都塞进去,HTML 变大,反而拖慢。务必控制在 14KB 内,超出就重新裁剪规则。
- 忘记
<noscript>:JS 被禁用或加载失败时页面完全裸奔,SEO 抓取端也可能只拿到无样式 HTML。回退标签不能省。 - 案例:某站上线后首屏样式错乱两小时。原因是 critical.css 是三个月前生成的,中间主题改过头部布局,内联规则与完整样式表冲突,且异步加载的完整样式反而在内联之后覆盖了正确规则。定位方法是比对两份文件里
.site-header的 padding 值,最后把关键 CSS 的生成纳入发布流程,主题样式一变就重新生成。 - 缓存插件二次优化冲突:WP Rocket、Autoptimize、LiteSpeed Cache 都自带关键 CSS 能力,同时开两份会互相覆盖,出现「样式时好时坏」。只保留一处。
wp_add_inline_style()的误区:它只能附加在某个已注册的 handle 后面,无法在样式表之前内联,也不支持让它异步;想要先内联、后异步,必须用wp_head直出。- 登录态污染:管理员登录时管理条样式会抢占首屏,
is_customize_preview()与登录态要做例外处理,否则你在后台看到的效果和访客不一致。 - HTTPS 与协议相对路径:抓取生成时若用 http 源,生成的 URL 可能混进 http 资源,导致混合内容警告。生成命令里的源站地址要用 https。
总结
关键 CSS 是否值得做,看一个指标:主题的阻塞样式表是否超过 30KB 且首屏只用到其中一小部分。是,就值得;主题本身只有 20KB,收益微乎其微。行动清单:按页面类型各生成一份 → 内联进 wp_head 且控制在 14KB → 主样式改 preload+onload+noscript → 关掉缓存插件里重复的关键 CSS 功能 → 移动模式复测 LCP → 把生成步骤写进发布流程。做完记得同步处理 主题响应式断点设置建议 里提到的断点规则,确保内联部分覆盖各尺寸首屏。
