全局变量交叉污染的根源是SSR上下文未按租户隔离;须在请求入口提取tenantId并挂载上下文,禁用跨请求共享状态,所有数据获取和缓存必须显式传入tenantId,Server Component中需封装异步逻辑并用headers()获取租户信息。

多租户 SSR 场景下,全局变量交叉污染的核心原因不是“用了全局变量”,而是服务端渲染上下文未按租户隔离。Node.js 的 SSR 服务通常是单进程多请求共用内存空间,若在渲染前未显式清空或绑定租户专属上下文,上一个租户的 request、session、缓存数据就可能残留并影响下一个租户的页面生成——这正是 Dify 多租户配置中 99% 团队踩坑的根源。
确保每个请求拥有独立的租户上下文
SSR 渲染必须在请求生命周期内完成租户识别与上下文初始化,不能依赖模块级或全局变量存储租户信息。
- 在入口中间件(如 Next.js 的 middleware.ts 或 Express 的 req handler)中,从 header(x-tenant-id)、host(tenant.example.com)或 cookie 中提取租户标识,并挂载到 req.tenant 或 res.locals.tenant
- 禁止在 getServerSideProps 或 load 函数外定义可变状态(例如 let currentUser = null),这类变量会在请求间复用
- 所有数据获取函数(如 fetchUser()、getTenantConfig())必须显式接收 tenantId 参数,不依赖闭包或模块变量推导
禁用共享状态缓存机制
常见错误是为提升性能引入全局缓存(如 Map、LRU Cache),却未按租户维度分片。
- 避免使用 const globalCache = new Map() 存储用户数据;应改为 const tenantCache = new Map
>() ,第一层 key 是 tenantId - 若使用 Redis,键名必须包含租户前缀,例如 cache:tenant_abc123:user_profile:1001,而非 user_profile:1001
- Next.js App Router 中慎用 cache() 或 unstable_cache,除非明确指定 key: [tenantId, ...]
Server Components 中租户感知的数据获取
在 Next.js App Router 的 Server Component 中,数据获取天然支持异步和上下文隔离,但需主动规避隐式共享。
- 不要在组件顶层声明变量并 await 赋值,应把数据获取封装进 async 函数内部,确保每次调用都新建作用域
- 使用 headers() 或 cookies() 获取租户信息,而非依赖外部传入的 context 对象(它可能被复用)
- 对数据库连接、API 客户端等重资源对象,务必使用租户 ID 初始化实例,例如:createApiClient(tenantId),而不是复用单例
验证与防护手段
上线前必须通过并发压测验证隔离性,不能仅靠单元测试。
- 编写模拟双租户并发请求的 E2E 测试:同时发起 tenant-a 和 tenant-b 的 SSR 请求,断言各自 HTML 中不出现对方的用户姓名、logo URL、配置文案
- 在关键渲染路径添加运行时断言,例如:if (process.env.NODE_ENV === 'production' && !req.tenant) throw new Error('Missing tenant context')
- 日志中强制记录 tenantId 字段,便于排查跨租户日志混杂问题

















