SSR中必须内联CSS字符串到带唯一id的标签,而非,否则首屏必现FOUC;需构建时提取CSS、服务端注入、客户端hydration时精准移除。

因为服务端没有浏览器环境,import './style.css' 在 Node.js 里不会触发样式加载,更不会生成 <style></style> 标签——它只是个无效的模块引用,不报错但也不起作用。
Webpack/Vite 默认不提取 CSS 字符串
现代打包器把 CSS 当作资源模块处理:import 语句只在客户端构建流程中被 style-loader 或 vite-plugin-css-injected-by-js 拦截并注入 DOM;服务端运行时既无 DOM,也无这些 loader 运行上下文。所以即使你写了 import './Button.css',服务端 renderToString 输出的 HTML 里也不会有对应样式。
- Webpack 项目若启用了
MiniCssExtractPlugin,它只在浏览器端构建产物中输出.css文件,服务端代码里require('xxx.css')会直接报Error: Cannot find module - Vite 的
build.cssCodeSplit: true(默认)会让 CSS 拆成多个文件,服务端得手动解析manifest.json才能找全,极易漏掉 - 开发时热更新靠
style-loader注入<style></style>,但该 loader 在 Node 环境下根本不可用
link 标签导致 FOUC 是硬伤
<link rel="stylesheet"> 本质是异步加载,浏览器解析 HTML 时不等它完成就继续渲染——服务端返回的 HTML 里没样式,首屏必然闪白或错位。这不是缓存或 CDN 能解决的问题,而是渲染机制决定的。
- 哪怕 CSS 文件已缓存,DNS、TCP、TTFB 等链路延迟仍存在,且服务端无法控制客户端缓存策略
-
<link rel="preload">只是提前发起请求,不能保证样式在首次 paint 前就绪 - SSR 的核心价值就是消除 FOUC,绕过网络加载环节是唯一可靠路径
服务端无法运行 CSS Modules 或 PostCSS 处理逻辑
Node.js 环境里没有 Webpack loader、没有浏览器 CSSOM,require('./Button.module.css') 直接报错;postcss.process() 虽可调用,但若输入是未经过 css-loader 解析的原始 CSS(含 composes、@value 等),结果大概率为空或出错。
立即学习“前端免费学习笔记(深入)”;
- 必须靠构建阶段导出:用
css-loader的exportOnlyLocals: true生成 class 映射 JSON,CSS 字符串则由extract-css-chunks-webpack-plugin或vite-plugin-css-injected-by-js单独产出 - PostCSS 插件如
autoprefixer应在构建时跑完,服务端只做字符串读取和注入,不重复处理 - 像
critters这类工具虽能动态提取关键 CSS,但它只处理已有<link>,不能替代构建时 CSS 提取——你得先有完整 CSS 内容才能喂给它
真正卡点不在“怎么塞”,而在“塞什么”和“塞到哪”:服务端需要的是构建后稳定存在的 CSS 字符串,且必须精准注入到 中带唯一 id 的 <style></style> 标签里,否则客户端 hydration 时无法识别并移除重复样式。


















