服务端渲染时import的css未内联,因webpack/vite默认不提取;需用mini-css-extract-plugin(webpack)或vite-plugin-css-injected-by-js(vite)配合服务端收集并注入css字符串到html的中。

服务端渲染时import的CSS为什么没进HTML
SSR中直接import './style.css',生成的HTML里没有内联样式——不是漏了,是Webpack/Vite默认不提取。现代打包器把CSS当模块处理,import只是触发加载逻辑,服务端执行时没走CSS提取流程,自然不会塞进<style></style>标签。
常见错误现象:document.querySelector在客户端首次渲染前拿不到样式、首屏闪动、Lighthouse提示“未内联关键CSS”。
- 使用场景:Next.js(
getServerSideProps)、Nuxt、或自建Express+React SSR - 关键区别:开发时热更新CSS靠
style-loader注入<style></style>,但服务端没DOM,style-loader完全不生效 - 必须用
css-loader+mini-css-extract-plugin(Webpack)或vite-plugin-css-injected-by-js(Vite)这类能产出CSS字符串的方案
如何从JS模块里安全提取renderToString所需的CSS
核心思路:让每个页面组件的import行为可追踪,服务端运行时收集已加载的CSS内容,拼成字符串塞进HTML模板的里。
实操建议:
- Webpack项目:启用
mini-css-extract-plugin,并配置chunkFilename为稳定路径(如css/[name].css),避免哈希导致服务端找不到文件 - 服务端需同步读取对应CSS文件(如
fs.readFileSync('./dist/css/home.css', 'utf8')),不能依赖浏览器动态加载 - 若用CSS-in-JS(如Emotion),必须开启
cache.sheet.toString()或extractCritical,否则renderToString不触发样式收集 - Vite用户注意:
build.cssCodeSplit默认为true,会导致CSS被拆成多文件——服务端得遍历manifest.json找所有关联CSS,容易漏
critical CSS提取失败的三个典型信号
不是所有CSS都能自动变成“关键CSS”,提取逻辑卡住时往往有明确表现:
- 错误信息:
Cannot find module './style.css' in /server——服务端Node.js里没配css-loader,或require.resolve路径不对 - 页面渲染后
document.head.innerHTML里有<link rel="stylesheet">但没<style></style>——提取步骤没挂到renderToString之前,或CSS文件本身是空的(比如只含@media规则) - 提取出的CSS体积远小于预期(如只有几行)——可能用了
postcss-preset-env且stage: 0,部分语法被忽略;或CSS里写了composes但没配css-modules正确解析
客户端水合时CSS重复注入怎么办
服务端注入了内联<style></style>,客户端再加载同名CSS文件,样式会叠两次——尤其影响伪类、动画、transition。
解决办法很直接:
- Webpack:给
mini-css-extract-plugin加ignoreOrder: true,避免控制台报错,但不解决重复 - 更可靠的是客户端跳过加载:在
useEffect里检查document.getElementById('ssr-css')是否存在,存在则return - Vite项目需禁用
build.rollupOptions.output.manualChunks对CSS的拆分,否则index.css和home.css可能被同时注入 - 关键点:服务端提取的CSS必须带唯一
id(如<style id="ssr-css"></style>),客户端才能精准识别并跳过
真正麻烦的从来不是怎么提,而是提哪些——路由级CSS、组件级CSS、第三方UI库的CSS,谁该进关键集,谁该懒加载,得看实际首屏元素,而不是全塞进去。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











