SSR中style标签顺序与客户端不一致,是因为服务端未按组件依赖顺序收集样式,而是统一dump所有已注册CSS模块,导致style插入head末尾,引发FOUC或覆盖;需在render前新建隔离样式上下文并正确传入。

为什么 SSR 应用里 style 标签顺序和客户端不一致?
SSR 渲染时,CSS 提取逻辑若没和 React/Vue 的渲染生命周期对齐,style 标签会插在 HTML head 末尾,而客户端 hydrate 后样式可能被后加载的 CSS 覆盖,导致 FOUC 或样式错乱。根本原因是:服务端没有“按组件依赖顺序”收集样式,而是统一 dump 所有已注册的 CSS 模块。
- 确保样式收集发生在
renderToString/renderToNodeStream调用前,且每次请求新建独立的样式上下文(避免多用户样式污染) - 使用
StyleSheetManager(Emotion)或ServerStyleSheet(styled-components)这类带 scope 隔离能力的工具,不要复用全局实例 - Next.js 用户注意:
getServerSideProps中不能调用useEffect,但样式提取必须在 render 阶段完成——所以得把 sheet 实例传进renderToString的 context 参数里
如何让 extractCritical 真正提取“首屏关键 CSS”?
extractCritical(如 styled-components 的 API)默认只做“当前已注入样式”的字符串提取,不分析 DOM 结构或视口内元素。它不是 CSS-in-JS 版的 Penthouse,不会爬页面、跑 Puppeteer、做 layout 分析。
- 它提取的是“服务端执行期间所有组件 render 过、且触发了样式注入的规则”,所以确保首屏组件一定在服务端完整 render(比如别用
dynamic(import())包裹首屏内容) - 如果用了 CSS Modules 或 Tailwind,
extractCritical对它们无效——它只认 styled-components / Emotion 这类运行时生成的 style 标签 - 输出的 critical CSS 是纯字符串,需手动插入到 HTML
<head>中,且要放在所有外部 CSS<link>之前,否则优先级可能被覆盖
多个 CSS-in-JS 库混用时,样式提取顺序怎么保证?
一个项目里同时用 styled-components + Emotion + vanilla CSS Modules,服务端无法自动合并它们的输出。每个库维护自己的 injection logic 和 sheet 实例,顺序错乱直接导致 specificity 冲突。
- 禁止在同一个组件中混用不同库的样式声明(比如 Emotion 的
cssprop + styled-components 的styled),服务端无法感知依赖关系 - 如果必须共存,用
ServerStyleSheet(styled-components)和CacheProvider(Emotion)各自提取,再按明确顺序拼接:critical Emotion → critical styled-components → 公共 reset.css - 注意
createGlobalStyle(styled-components)和Global(Emotion)组件必须在服务端 render 阶段执行,否则全局样式不会进入提取结果
提取后的 CSS 字符串里为什么还有 @import 或 url()?
部分 CSS-in-JS 工具(尤其旧版 styled-components)在服务端提取时,不会解析 @import 规则或 url() 中的相对路径,直接原样保留。这会导致浏览器在解析时发起额外请求,破坏关键 CSS 的“零网络请求”目标。
立即学习“前端免费学习笔记(深入)”;
- 升级到 styled-components v6+,启用
shouldForwardProp和ssr: true配置,它会尝试内联@import引入的内容(前提是被 import 的文件也在打包依赖中) - 避免在关键组件样式中写
url('./icon.svg'),改用 base64 内联或提前转成data:URL;否则服务端提取后路径仍为相对地址,客户端加载失败 - 检查提取结果是否含
@charset "UTF-8";—— 某些版本会自动加这个,但若 HTML 已声明<meta charset="utf-8">,重复声明可能触发 IE 兼容模式
真正麻烦的从来不是提取动作本身,而是样式作用域、执行时机、路径解析这三者在服务端没有 DOM 的环境下如何对齐。漏掉任意一环,提取出来的就只是“看起来像关键 CSS”的字符串。










