ssr 下 css 闪烁的根源是服务端未将样式注入 html 的 ,导致浏览器先绘制无样式的 html,js 执行后才动态插入 ,引发 fouc;需确保服务端与客户端共享同一 css-in-js cache 实例、class 名一致、样式仅注入一次且时机正确。

服务端没把样式塞进 HTML,客户端才补,必然闪
SSR 下 CSS 闪烁的根源不是“样式加载慢”,而是服务端渲染完 HTML 后,style 标签压根没出现在 里。浏览器拿到裸 HTML 就开始绘制,等 JS 加载执行、调用 collectStyles() 或 extractStyle(),再动态插入 <style></style>,中间这段空窗期就是 FOUC。
验证方法:右键 → “查看页面源代码”,搜索 <style data-> 或 <code><link rel="stylesheet"</style>。如果首屏关键类(如 .header、.hero)没在源码中出现,只在 DevTools 的 Elements 面板里能看到,说明服务端漏提了样式。
- Next.js Pages Router:必须在
pages/_document.tsx中调用sheet.getStyleTags()并插入head字段 - Next.js App Router:不能用
ServerStyleSheet,改用@emotion/server或@ant-design/cssinjs的 SSR API - Nuxt3:需配合
@css-render/vue3-ssr在服务端collect(),再通过useServerHead({ style: [...] })注入
客户端复用服务端 cache 实例,否则 class 名对不上
CSS-in-JS 库(如 @emotion/react、@ant-design/cssinjs)靠 cache 实例生成 class 名。服务端用 createCache() A,客户端又新建 B,两端 class 名(如 css-1a2b3c vs css-4d5e6f)不一致,hydration 时 React 直接丢弃服务端 DOM,重绘——视觉上就是“闪一下再正常”。
- Next.js App Router 必须用
useState(() => createCache())初始化RootStyleRegistry,确保服务端和客户端共享同一实例 -
cache的key必须固定(如'css'),stylisPlugins数组内容与顺序必须完全一致 - 服务端渲染后调用
extractStyle(cache),客户端 hydration 前必须用同一cache初始化StyleProvider - 绝对不要在组件内部或
getInitialProps里重复调用createCache()
重复注入 = 服务端提一次 + 客户端又提一次
常见错误是服务端已提取并内联样式,但客户端 JS 还在 useEffect 里调用 extractCritical() 或手动插入 <style></style>,导致同一套样式被插入两次。轻则体积膨胀,重则 class 名冲突、样式覆盖错乱,甚至触发强制重排。
- 客户端 hydration 阶段只负责水合,**不执行任何样式提取逻辑**
- 若使用
useServerInsertedHTML(Next.js),确保返回的是纯字符串,不带额外 wrapper 或事件监听 -
<style></style>标签必须带正确data-属性(如data-emotion="css"),否则 SSR 提取工具无法识别,会漏掉或重复处理 - Emotion 用户务必设
disableVendorPrefixes: true,避免服务端加的前缀与客户端不一致
Less/CSS Modules 在 Server Component 里根本不会产出 class 名
Next.js App Router 的 Server Component 执行时,import styles from './Button.module.css' 返回的是空对象或 {},className={styles.button} 渲染结果是 class="" 或 undefined。客户端 hydration 时才真正解析出 Button_button__abc123,DOM 不匹配直接触发重绘。
- CSS Modules 必须只在标记
"use client"的 Client Component 中使用 - Less 文件必须在
app/layout.tsx顶层import,不能写在组件内部,否则构建工具不会将其纳入 SSR 流程 - Vite 用户检查
css.modules.generateScopedName是否引用了process.env或随机函数;推荐静态格式如[name]_[local]_[hash:5] - 避免
clsx(styles.button, props.className)这类写法——styles.button为空时,结果变成props.className,模块化 class 彻底丢失
<style></style>,都必须和客户端 hydration 时的计算结果逐字一致。任何环节引入运行时变量、环境差异或缓存残留,都会让这个一致性崩塌。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











