多租户SSR需通过隔离、清理、防误用三层机制防止状态交叉污染:隔离租户上下文(如闭包/Map/专属Store)、关键链路加带租户日志的try-catch、请求结束后主动清理缓存。

多租户 SSR 场景下,每个请求对应一个租户上下文(如子域名、请求头标识或路径前缀),而服务端是共享进程的。若状态未严格隔离,一个租户的渲染错误可能污染全局变量、缓存、原子状态或中间件上下文,导致后续请求拿到错误数据——这就是“生成器状态交叉污染”。核心不是靠“更严密的 try-catch”,而是靠**隔离 + 清理 + 防误用**三层机制。
隔离租户执行上下文
Next.js 默认不提供租户级作用域,必须主动构造:
- 在 getServerSideProps 或 getStaticProps 入口处,立即提取租户标识(如
context.req.headers.host或context.params.tenant),并基于它创建独立的数据获取通道 - 避免使用模块级变量(
let cache = {})存储租户数据;改用 闭包绑定 或 Map 实例按租户键隔离:const tenantCache = new Map();<br>const getTenantData = (tenantId) => {<br> if (!tenantCache.has(tenantId)) {<br> tenantCache.set(tenantId, new Map());<br> }<br> return tenantCache.get(tenantId);<br>}; - 若使用 Jotai 等状态库,不要复用全局 atom;为每个租户动态创建原子实例,或通过
useAtomValue(atom, { store })传入租户专属 Store 实例
在关键链路嵌入防御性 try-catch
try-catch 不是兜底,而是明确边界。只在以下三处加,且必须带租户上下文日志:
-
数据获取层:fetch 调用包裹 try-catch,失败时返回租户默认数据(非 throw),并记录
tenantId + error.stack - 模板注入点:对所有用户输入字段(如租户配置 JSON、富文本内容)做 DOMPurify 清洗,清洗失败则降级为空字符串,并标记该租户内容异常
-
第三方库调用前:例如调用 framer-motion 的动画逻辑前,先判断
if (typeof window === 'undefined'),再用 try-catch 包裹其初始化代码,防止 SSR 时意外抛出
强制清理与重置机制
租户请求结束 ≠ 状态自动消失。需主动归零:
- 在 Next.js API Route 或自定义中间件 中,响应发送后手动清空该租户的临时缓存 Map 条目
- 若使用 Express 封装 Next.js,可在
res.on('finish', () => { cleanupForTenant(tenantId); })中触发清理 - 禁止在组件内使用
useEffect初始化全局副作用(如监听事件、设置定时器);这类操作必须限定在客户端,且用isClient && !isMountedRef.current双重防护
验证是否真正隔离
上线前跑一个简单压力测试:
- 并发发起两个不同租户的请求(如
tenant-a.example.com和tenant-b.example.com) - 在其中一个租户请求中人为 throw Error,观察另一个租户响应体是否仍含正确数据、无堆栈泄漏、无缓存错乱
- 检查日志中两个租户的 traceId 是否完全分离,无跨租户的 warning 或 error 关联

















