如何在Next.js初始请求中从Middleware传数据到getServerSideProps(含Cookie)
问题核心
初始请求时,Middleware在getServerSideProps之前执行,但Middleware是将Cookie添加到响应中返回给客户端,当前请求的req对象并不会包含这个新设置的Cookie,因此getServerSideProps无法读取到。后续请求时客户端会自动带上Cookie,所以能正常读取。
可行方案
方案1:通过查询参数传递地理数据(无重复接口调用)
在Middleware中获取地理数据后,除了设置Cookie,将数据编码后添加到请求的查询参数中,让getServerSideProps可以直接从查询参数读取数据,避免重复调用接口。
修改Middleware:
export async function middleware(req: NextRequest) { console.log('Middleware execution!') const geoCookieKey = USER_GEOLOCATION_DATA_COOKIE_KEY; if (req.cookies.has(geoCookieKey)) { console.log('Middleware: Cookie detected') return NextResponse.next() } else { console.log('Middleware: executing call!') const userGeolocationData = await getUserGeolocationData() console.log('Has already finished call') // 1. 设置Cookie const response = NextResponse.next() response.cookies.set( geoCookieKey, JSON.stringify(userGeolocationData), ) // 2. 将地理数据编码后添加到查询参数,通过rewrite传递给后续流程 const url = new URL(req.url) url.searchParams.set('geo-data', encodeURIComponent(JSON.stringify(userGeolocationData))) return NextResponse.rewrite(url, { headers: response.headers }) } }
修改getServerSideProps:
export const getServerSideProps = (async ({ req, query }) => { const geoCookieKey = USER_GEOLOCATION_DATA_COOKIE_KEY; let userGeolocationData = null; // 优先从查询参数读取数据 if (query['geo-data']) { try { userGeolocationData = JSON.parse(decodeURIComponent(query['geo-data'] as string)) console.log('Got geo data from query params') } catch (e) { // 解析失败则尝试读Cookie const cookie = req.cookies[geoCookieKey] if (cookie) { userGeolocationData = JSON.parse(cookie) } } } else { // 从Cookie读取 const cookie = req.cookies[geoCookieKey] if (cookie) { userGeolocationData = JSON.parse(cookie) console.log('Habemus cookie SSR') } else { console.log('NO COOKIE SSR') } } return { props: { userGeolocationData }, } }) satisfies GetServerSideProps<RootPageProps>
说明:使用NextResponse.rewrite不会改变浏览器地址栏的URL,用户无感知;若用重定向会导致地址栏变化,体验不如rewrite。
方案2:使用缓存复用地理数据(避免重复接口调用)
将地理数据的获取逻辑封装为带缓存的函数,以用户IP(或其他唯一标识)为缓存key,这样即使Middleware和getServerSideProps都调用该函数,第二次调用会直接命中缓存,不会重复请求外部接口。
示例缓存函数:
// 生产环境建议用Redis等分布式缓存替代内存Map const geoCache = new Map<string, { data: any; timestamp: number }>(); const CACHE_TTL = 3600 * 1000; // 1小时过期 export async function getCachedUserGeolocationData(ip: string) { const now = Date.now(); const cached = geoCache.get(ip); // 缓存存在且未过期则直接返回 if (cached && now - cached.timestamp < CACHE_TTL) { return cached.data; } // 缓存失效或不存在,调用接口获取 const data = await getUserGeolocationData(); geoCache.set(ip, { data, timestamp: now }); // 设置缓存过期清理 setTimeout(() => geoCache.delete(ip), CACHE_TTL); return data; }
修改Middleware:
export async function middleware(req: NextRequest) { console.log('Middleware execution!') const geoCookieKey = USER_GEOLOCATION_DATA_COOKIE_KEY; if (req.cookies.has(geoCookieKey)) { console.log('Middleware: Cookie detected') return NextResponse.next() } else { console.log('Middleware: executing call!') // 获取用户IP(Next.js中从req.ip或请求头中取) const userIp = req.ip || req.headers.get('x-forwarded-for') || 'unknown'; const userGeolocationData = await getCachedUserGeolocationData(userIp) console.log('Has already finished call') const response = NextResponse.next() response.cookies.set( geoCookieKey, JSON.stringify(userGeolocationData), ) return response } }
修改getServerSideProps:
export const getServerSideProps = (async ({ req }) => { const geoCookieKey = USER_GEOLOCATION_DATA_COOKIE_KEY; const userGeolocationDataCookie = req.cookies[geoCookieKey] let userGeolocationData = null; if (userGeolocationDataCookie) { console.log('Habemus cookie SSR') userGeolocationData = JSON.parse(userGeolocationDataCookie) } else { console.log('NO COOKIE SSR, fetching from cache') // 从缓存获取,不会重复调用外部接口 const userIp = req.ip || req.headers.get('x-forwarded-for') || 'unknown'; userGeolocationData = await getCachedUserGeolocationData(userIp) } return { props: { userGeolocationData }, } }) satisfies GetServerSideProps<RootPageProps>
优势:无需修改请求参数,逻辑更简洁;分布式缓存可支持多实例部署,适合生产环境。
方案对比
- 方案1适合不想引入缓存依赖的场景,通过查询参数传递数据,无额外资源消耗;
- 方案2适合需要复用地理数据到其他请求/页面的场景,缓存机制更灵活,可减少整体接口调用量。
内容的提问来源于stack exchange,提问作者Juan Manuel Sierra
相关产品推荐
相关产品推荐

