根本原因是服务端未提取并内联组件样式,导致HTML无对应CSS规则,客户端JS注入时才重绘引发FOUC;需通过extractStyle、useServerInsertedHTML等显式提取并注入样式到<head>。

样式闪烁(FOUC)在 SSR 中出现,根本不是“CSS 加载慢”这么简单——而是服务端输出的 HTML 里压根没包含对应组件的样式,等客户端 JavaScript 执行、useEffect 或 useLayoutEffect 注入样式时,浏览器才重绘。这时候用户已经看到无样式的 DOM 了。
服务端没生成样式,客户端才补,这是最常见原因
多数 CSS-in-JS 库(比如 styled-components、@emotion/react 默认配置)依赖运行时注入,服务端渲染时不会自动提取并内联样式。结果就是:Node.js 渲染出的 HTML 只有 class 名(如 sc-aBvKz),但没有对应的 CSS 规则。
- 检查你 SSR 输出的 HTML 源码,搜索
<style>标签或内联 CSS —— 如果找不到与组件 class 匹配的规则,就是这个原因 -
@ant-design/cssinjs必须配合extractStyle()在服务端调用,否则ConfigProvider主题也不会生效 - Next.js 中若没用
useServerInsertedHTML+StyleProvider,@compiled/react的构建时 CSS 也不会出现在首屏 HTML 中
客户端水合时 class 名不一致,样式直接失效
哪怕服务端吐出了样式,如果 class 名生成逻辑在服务端和客户端不一致,水合后 React 会发现 DOM 节点 class 和虚拟 DOM 不匹配,丢弃旧节点重建,造成视觉跳变。
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
- Vite 6 通过稳定哈希保证
css-modules类名一致,但老版本或自定义generateId函数容易出错 - 使用
styled-components时,必须确保服务端和客户端共用同一个StyleSheetManager实例,且cache对象不能每次新建 - Next.js App Router 下,
"use client"组件里的 CSS-in-JS 默认不参与服务端样式提取,除非显式包裹StyleProvider
useInsertionEffect 用错了时机,反而加剧闪烁
useInsertionEffect 是为在 DOM 更新前插入样式设计的,但它本身不解决“服务端没样式”的问题——它只管客户端那一帧。如果误把它当 SSR 解决方案,就本末倒置了。
立即学习“前端免费学习笔记(深入)”;
-
useInsertionEffect适合处理动态主题切换、运行时主题 patch 等场景,不是替代服务端样式提取的手段 - 在 SSR 环境中调用
useInsertionEffect会触发两次(服务端执行无效果 + 客户端执行),可能造成重复插入或竞态 - 真正该做的是:服务端用
extractStyle()/renderStatic()提取样式,再通过useServerInsertedHTML或模板注入到<head>
最容易被忽略的点是:你以为用了 SSR 就自动有样式,其实绝大多数 CSS-in-JS 库默认是纯客户端行为。是否真正在服务端跑了样式提取逻辑,得看 HTML 源码里有没有那几行 <style> —— 光看页面最终显示正常,不代表首屏没闪。

















