NextJS中启动时设置的Map在其他函数中无法读取的问题排查
问题原因与解决方案
核心原因
1. Serverless环境的实例隔离特性
在Vercel等Serverless平台部署NextJS时,每个请求可能触发全新的函数实例。启动时执行loadAllUserPools的实例与处理登录请求的实例完全独立,内存中的userPoolMap不会在实例间共享。新实例处理请求时,userPoolMap是未初始化的空Map,自然无法取到值。
2. 模块重复加载导致实例不共享
NextJS的模块解析机制在部分场景下(比如不同路由/API路由的隔离加载),会导致包含userPoolMap的模块被多次加载,生成多个独立的Map实例。启动时的loadAllUserPools操作的是其中一个实例,而登录时调用getCognitoUserPoolBySubdomain访问的是另一个未初始化的实例。
解决方案
方案1:改用分布式缓存存储(推荐)
放弃内存存储,使用Redis等分布式缓存统一存储用户池数据,确保所有实例都能访问同一套数据:
import { createClient } from 'redis'; // 初始化Redis客户端 const redisClient = createClient({ // 填写你的Redis配置信息 url: process.env.REDIS_URL }); await redisClient.connect(); export async function loadAllUserPools() { // 从数据库获取数据(示例用模拟数据) const dbUserPoolData = [ { cognitoUserPoolId: 'user-pool-1111111', cognitoClientId: '11111111111', accessUrl: 'tenant1' }, { cognitoUserPoolId: 'user-pool-2222222', cognitoClientId: '222222222222', accessUrl: 'tenant2' }, ]; // 将数据存入Redis for (const item of dbUserPoolData) { await redisClient.set( `userpool:${item.accessUrl}`, JSON.stringify({ userPoolId: item.cognitoUserPoolId, clientId: item.cognitoClientId }) ); } } export async function getCognitoUserPoolBySubdomain(subdomain: string) { const dataStr = await redisClient.get(`userpool:${subdomain}`); return dataStr ? JSON.parse(dataStr) : undefined; }
方案2:确保模块单例与初始化时机
如果坚持使用内存存储,需通过单例模式保证userPoolMap唯一,并在处理请求前完成初始化:
- 封装单例管理类:
// userPoolManager.ts class UserPoolManager { private static instance: UserPoolManager; private userPoolMap: Map<string, { userPoolId: string; clientId: string }>; private constructor() { this.userPoolMap = new Map(); } public static getInstance(): UserPoolManager { if (!UserPoolManager.instance) { UserPoolManager.instance = new UserPoolManager(); } return UserPoolManager.instance; } public async loadAllUserPools() { const dbUserPoolData = [ { cognitoUserPoolId: 'user-pool-1111111', cognitoClientId: '11111111111', accessUrl: 'tenant1' }, { cognitoUserPoolId: 'user-pool-2222222', cognitoClientId: '222222222222', accessUrl: 'tenant2' }, ]; this.userPoolMap.clear(); dbUserPoolData.forEach(item => { this.userPoolMap.set(item.accessUrl, { userPoolId: item.cognitoUserPoolId, clientId: item.cognitoClientId, }); }); } public getCognitoUserPoolBySubdomain(subdomain: string) { return this.userPoolMap.get(subdomain); } } export const userPoolManager = UserPoolManager.getInstance();
- 在入口处确保初始化完成(以
middleware.ts为例):
// middleware.ts import { userPoolManager } from './userPoolManager'; let isInitialized = false; export async function middleware(request: NextRequest) { // 仅初始化一次 if (!isInitialized) { await userPoolManager.loadAllUserPools(); isInitialized = true; } // 后续请求处理逻辑 }
方案3:请求时懒加载数据
在getCognitoUserPoolBySubdomain中检查Map状态,为空则重新加载(需处理并发重复加载问题):
let isLoading = false; let userPoolMap = new Map(); export async function loadAllUserPools() { if (isLoading) return; isLoading = true; try { const dbUserPoolData = [ { cognitoUserPoolId: 'user-pool-1111111', cognitoClientId: '11111111111', accessUrl: 'tenant1' }, { cognitoUserPoolId: 'user-pool-2222222', cognitoClientId: '222222222222', accessUrl: 'tenant2' }, ]; userPoolMap.clear(); dbUserPoolData.forEach(item => { userPoolMap.set(item.accessUrl, { userPoolId: item.cognitoUserPoolId, clientId: item.cognitoClientId, }); }); } finally { isLoading = false; } } export async function getCognitoUserPoolBySubdomain(subdomain: string) { if (userPoolMap.size === 0) { await loadAllUserPools(); } return userPoolMap.get(subdomain); }
内容的提问来源于stack exchange,提问作者Gus
相关产品推荐
相关产品推荐

