You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Next.js初始请求中从Middleware传数据到getServerSideProps(含Cookie)

解决方案:解决Next.js Middleware设置Cookie后初始请求getServerSideProps无法读取的问题

问题核心

初始请求时,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 08:17:44