css-in-js首屏闪烁(fouc)的根本原因是样式动态注入晚于首次渲染:js执行前无样式,浏览器先以默认样式绘制,js运行后插入才重绘;唯一可靠解法是服务端预提取并内联关键样式,或用普通css内联基础样式。

为什么CSS-in-JS会在首屏渲染时闪一下
因为样式生成和注入发生在 JavaScript 执行之后,而浏览器在 HTML 解析完、CSSOM 构建完成前不会绘制——但 CSS-in-JS 的 style 标签是 JS 运行时动态插入的,根本没进初始阻塞链。
典型流程是:HTML → 解析 → 渲染(无样式)→ JS 下载执行 → 创建 style 标签 → 插入 head → 浏览器重新计算样式 → 重绘。这中间的“无样式渲染”就是 FOUC(Flash of Unstyled Content)。
- 哪怕用了
styled-components或Emotion,只要没做 SSR 或预提取,<style></style>标签就不可能出现在初始 HTML 响应体里 - 服务端渲染(SSR)能解决,但需要后端或静态生成阶段把组件对应样式序列化为内联
<style></style>,否则客户端首次 hydration 仍要等 JS 加载完才注入 - 某些库(如 Emotion)支持
cache.inserted+extractCritical提取关键样式,但必须显式配置,否则默认不启用
CSS-in-JS 工具链里哪些环节容易漏掉关键样式
不是所有组件都参与首屏渲染,但工具不会自动判断“哪个组件在首屏”,它默认提取所有已挂载组件的样式——如果路由懒加载或条件渲染没触发,对应样式就压根没生成。
比如一个 Modal 组件只在点击后显示,它的样式就不会被包含在首屏关键 CSS 中;但如果首屏有个 Header 用到了 styled.div,而你没在服务端或构建时提前渲染它,那它的样式就只能等 JS 执行后才出现。
-
styled-components的ServerStyleSheet必须包裹整个应用树,且需调用getStyleTags()合并到 HTML 中,漏掉任意一步都会导致部分样式缺失 - Emotion 的
@emotion/server要配合renderStylesToString使用,且需确保所有首屏组件都在 render 函数中同步执行(不能靠useEffect或异步数据触发) - Vite + Emotion 用户常忽略
vite-plugin-emotion的ssr: true配置项,默认只做客户端注入
为什么加了 preload 或 defer 也救不了 CSS-in-JS 闪烁
<link rel="preload"> 和 defer 只影响资源下载时机,对 CSS-in-JS 完全无效——它的样式不是文件,而是 JS 执行结果。JS 没跑完,<style></style> 就不存在,浏览器连“样式资源”这个概念都还没见到。
- preload
main.js只会让 JS 下载更快,但无法绕过 JS 执行耗时(解析、编译、运行、插入 style 标签) - 即使 JS 被压缩到 50KB,现代手机上执行+注入仍需 20–60ms,足够让浏览器完成一次无样式的 layout/paint
- 更隐蔽的问题:如果首屏组件依赖异步数据(比如
fetch),那样式注入会被进一步推迟到数据返回后
真正可控的解法只有两个动作
要么让样式在 HTML 到达浏览器时就已经存在(即 SSR 或静态提取),要么让样式注入发生在 JS 执行之前(即内联脚本抢跑)。其他方案都是障眼法。
- SSR 场景下,必须确认服务端渲染输出的 HTML 包含完整的
<style data-emotion="..."></style>或<style data-styled="..."></style>标签,且内容非空 - 静态站点(如 Vite + React)可用
critters提取关键 CSS,但它只处理传统<link>,对 CSS-in-JS 无效;得换用@emotion/react的cache+extractCritical或styled-components的collectStyles - 最简方案:把主题变量、基础排版、字体声明这些不影响逻辑的样式写成普通 CSS 并内联到
,CSS-in-JS 只管动态部分(如主题色、尺寸响应)
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











