ESModule在SSR中不能自动执行,因Node.js默认不解析type="module",需框架控制加载;正确做法是通过框架资源收集机制输出<script type="module">,并确保服务端支持ESM(.mjs或"type":"module"),避免混用CommonJS,动态导入路径需本地化,构建时分离服务端(CJS/ESM)与客户端(ESM)产物。

ESModule 在服务端渲染(SSR)中不能直接靠 import 语句“自动执行”或“注入模块标记”,关键在于:服务端(如 Node.js)默认不解析浏览器端的 type="module",而 SSR 框架(如 Next.js、Nuxt、Remix)需主动控制模块加载时机与输出方式。正确做法是——让服务端生成 HTML 时,把 ESM 相关逻辑(如动态导入、模块预加载、<script type="module">)交由框架的资源收集与序列化机制处理,而非手动拼接。
确保服务端支持 ESM 语法(Node.js 层)
Node.js 从 v12.20+ 开始原生支持 ESM,但需满足以下任一条件:
- 文件后缀为
.mjs,或 -
package.json中声明"type": "module",且所有import/export用法符合 ESM 规范(不能混用require()) - 启动时加
--experimental-loader(仅调试用,不推荐生产)
⚠️ 注意:若 SSR 入口(如 server.js 或 entry-server.js)是 CommonJS(.cjs 或无 type: module),则无法直接 import ESM 文件——此时应改用 import() 动态导入,或通过 createRequire 兼容。
在框架中正确声明和输出 type="module" 脚本
SSR 框架通常提供 renderToString / renderToPipeableStream 等 API,并附带资源收集能力(如 getServerScript、getPreloadLinks)。不要手动写 <script type="module" src="..."> 到 HTML 字符串里,而应:
立即学习“Java免费学习笔记(深入)”;
- Next.js:在
app/目录下使用use client的组件会自动被标记为客户端 ESM 模块;服务端生成的<script>标签由 Next 内部按buildManifest注入,含type="module"和crossorigin - Nuxt 3:使用
<ClientOnly>或defineAsyncComponent的组件,其 chunk 默认以 ESM 方式加载;useHead({ script: [...] })中显式添加脚本时,可设{ type: 'module', src: '/main.mjs' } - Remix:路由模块本身是 ESM,客户端 hydration 脚本由
entry.client.tsx打包为 ESM;服务端模板中通过Scripts组件注入,自动带type="module"
避免服务端执行 ESM 特有语法(如顶层 await、动态 import 表达式)
ESM 的一些特性在纯服务端(无打包器)环境中不可用:
- 顶层
await:Node.js 支持,但 Vite / Webpack 等构建工具在 SSR 构建阶段可能不兼容——建议只在async function内使用 -
import('./foo.js')动态导入:服务端可用,但路径必须是相对或绝对本地路径(不能是 URL);若目标是浏览器端加载,应确保该路径经构建后映射为 public 可访问地址(如/assets/chunk-xxx.mjs) -
import.meta.url:服务端可用,常用于构造绝对路径(new URL('./data.json', import.meta.url)),但注意它在打包后可能被重写,需配合构建工具配置(如 Vite 的build.rollupOptions.treeshake: false防误删)
配合构建工具输出正确的模块格式与入口
SSR 场景下,同一份源码往往要同时输出两套产物:
-
服务端 bundle:CommonJS 或 ESM(取决于运行环境),禁用
type="module"输出,不包含浏览器专属 API(window,document) -
客户端 bundle:必须为 ESM(
type="module"),启用top-level await、dynamic imports等特性,由框架注入到 HTML 中
例如 Vite 配置中:
export default defineConfig({
build: {
rollupOptions: {
output: {
// 客户端产物强制为 es
format: 'es',
}
}
},
ssr: {
// 服务端入口指定为 .mjs 或设置 resolve.conditions = ['node']
}
})
最终 HTML 中的脚本标签类似:
<script type="module" crossorigin src="/assets/index.Bk5a7zvV.js"></script> <script nomodule src="/assets/index.DLQX4YtF.js"></script>


















