SSR中不存在线程安全问题,而是请求隔离失效导致的跨请求状态污染;必须避免在原型或模块顶层添加可变共享状态,所有请求相关状态须绑定到req/context或使用局部变量、依赖注入的REQUEST作用域。

服务端渲染(SSR)环境本身不涉及“线程”概念,Node.js 是单线程事件循环模型,不存在传统多线程并发修改共享内存的问题。所谓“多实例共享原型属性的线程安全”实际是误用术语,真正需关注的是多请求并发下共享状态引发的竞态与污染风险。
明确问题本质:不是线程安全,而是请求隔离失效
在 SSR(如 Next.js、Nuxt 或自建 Express + React/Vue 渲染服务)中:
- Node.js 进程内多个 HTTP 请求共用同一个 V8 实例和全局作用域;
- 若在原型(如
Object.prototype、Array.prototype)或模块顶层对象上挂载可变状态(例如MyClass.cache = new Map()),该状态会被所有请求共享; - 一旦某个请求写入后未清理,后续请求可能读到残留数据,导致模板渲染错误、用户信息错乱、缓存穿透等严重问题。
禁止在原型或模块级添加可变共享状态
以下写法必须避免:
-
Array.prototype.lastModified = Date.now();—— 污染原生原型,影响所有代码; -
MyService.instance = new MyService();(且未做请求隔离)—— 单例持有用户上下文或临时数据; -
const sharedCache = new Map();在模块顶层定义并直接复用 —— 多请求混用同一 Map。
这类设计在 SSR 下极易引发跨请求数据泄露,比“线程不安全”更危险,因为它是确定性污染而非概率性竞态。
正确做法:按请求生命周期管理状态
所有与请求相关的状态,必须绑定到当前请求上下文:
- 使用
req对象挂载(Express)或context(Next.js App Router 的generateStaticParams/generateMetadata中不可用,应改用服务层传参); - 在渲染函数内部创建局部变量或闭包状态,例如:
function renderPage(req) { const localStore = new Map(); /* ... */ }; - 若需跨中间件/组件共享,通过依赖注入容器(如 InversifyJS)配合
TRANSIENT或REQUEST作用域,确保每次请求获得独立实例。
特别注意 SSR 框架的静态生成陷阱
在 getStaticProps、generateStaticParams 等构建时执行的函数中:
- 它们运行在构建阶段,非请求时,共享状态影响的是生成结果的一致性;
- 禁止在其中初始化全局缓存、修改原型、或读写文件系统(除非明确用于构建流程);
- 所有数据获取应通过纯函数 + 参数驱动,确保相同输入恒定输出。

















