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

Next.js生产主域名下服务端generateMetadata中Fetch请求超时,其他环境/子域名正常

Next.js生产主域名下服务端generateMetadata中Fetch请求超时,其他环境/子域名正常

根据你的描述,Next.js在generateMetadata函数中发起的服务端fetch请求,在本地、测试HTTPS环境和子域名都正常,但主域名生产环境出现UND_ERR_CONNECT_TIMEOUT连接超时错误,且客户端请求完全正常。结合你的代码和日志,我整理了几个核心排查方向和解决方案:


1. 优先排查生产服务器的内网连通性

服务端的fetch请求是从Next.js部署的服务器发起的,而非客户端浏览器,所以最可能的原因是生产服务器无法访问后端API的网络链路问题:

  • 登录Next.js生产服务器,执行curl命令模拟服务端请求:
    curl -v -H "Accept-Language: en" https://new..io/api/blog/team-performance-metrics-what-to-measure-and-why
    
  • 如果curl也超时,说明是纯网络问题:
    • 检查DNS解析:执行nslookup new..io,看是否能正常解析到后端服务器的IP
    • 检查防火墙/安全组:确认Next.js服务器的出站规则允许访问后端的443端口,同时后端服务器的入站规则允许Next.js服务器的IP访问
    • 若后端和Next.js服务器在同一内网,尝试用内网IP直接访问API(比如http://192.168.xxx.xxx/api/...),排除公网DNS或路由问题

2. 简化Fetch请求的自定义Headers

你当前手动设置了Origin、Referer、Accept-Encoding等Headers,但这些是客户端浏览器专属的请求头,服务端发起请求时添加这些可能触发后端的安全拦截,甚至导致异常:

  • 建议简化Headers配置,只保留必要的字段:
    headers: {
      Accept: 'application/json', // 只保留JSON接受头
      'Accept-Language': locale || i18nConfig.defaultLocale,
    }
    
  • 移除Origin、Referer、Accept-Encoding:Next.js的fetch会自动处理编码,服务端请求不需要Origin/Referer

3. 调整超时时间配置

你当前设置的超时时间只有1秒(const timeout = 1_000),生产环境网络可能存在延迟,这个时间过短会导致不必要的超时终止:

  • 调整超时时间为10秒甚至更长,测试是否还会超时:
    const timeout = 10_000; // 10秒超时
    const timeoutId = setTimeout(() => {
      controller.abort()
      logToFile('❌ Request timed out')
    }, timeout)
    

4. 检查后端Yii2的安全验证规则

如果网络连通性正常,可能是后端Yii2的安全配置拦截了主域名的服务端请求:

  • 检查Yii2的AccessControl组件:是否设置了IP白名单,未包含Next.js生产服务器的IP
  • 检查CSRF验证:虽然服务端请求不需要CSRF Token,但如果后端强制开启了全局CSRF验证,可能会拒绝无Token的请求(此时一般返回403而非超时,但仍需排查)
  • 查看后端Nginx/Apache的访问日志,确认是否收到了Next.js生产服务器的请求,以及返回的状态码

5. 修正Fetch的配置细节

结合你的代码,还有几个小细节可以优化:

  • 确认生产环境的NEXT_PUBLIC_BASE_URL环境变量是否正确(比如末尾是否多了斜杠,导致API URL拼接错误),可以在日志中打印完整的环境变量值:
    logToFile(`Base URL from env: ${process.env.NEXT_PUBLIC_BASE_URL}`)
    
  • 若排查期间可以暂时关闭next.revalidate缓存,直接请求后端,排除缓存逻辑的干扰

优化后的示例代码

export async function generateMetadata({ params, }: { params: Promise<{ slug: string, locale: string }> }): Promise<Metadata> {
  console.log('metadata start')
  const { slug, locale } = await params
  const apiUrl = `${process.env.NEXT_PUBLIC_BASE_URL}${BLOG_DETAIL.replace('{slug}', slug)}`
  
  logToFile(` Fetching metadata for slug: ${slug}`)
  logToFile(` API URL: ${apiUrl}`)
  logToFile(` Locale: ${locale}`)
  logToFile(`Base URL from env: ${process.env.NEXT_PUBLIC_BASE_URL}`)

  const controller = new AbortController()
  const timeout = 10_000; // 调整为10秒超时
  const timeoutId = setTimeout(() => {
    controller.abort()
    logToFile('❌ Request timed out')
  }, timeout)

  try {
    const res = await fetch(apiUrl, {
      signal: controller.signal,
      next: { revalidate: 600 },
      headers: {
        Accept: 'application/json',
        'Accept-Language': locale || i18nConfig.defaultLocale,
      },
    })
    logToFile(`✅ Response status: ${res.status}`)

    if (!res.ok) {
      logToFile(`❌ Non-OK response: ${res.status}`)
      throw new Error(`API returned status ${res.status}`)
    }

    const data = await res.json()
    logToFile(`✅ Parsed metadata: ${JSON.stringify(data)}`)

    const { title, subtitle: description, photo } = data.data
    return {
      title,
      description,
      openGraph: {
        title,
        description,
        images: [photo.thumbnail],
      },
      twitter: {
        title,
        description,
        images: [photo.thumbnail],
      },
    }
  } catch (error: any) {
    clearTimeout(timeoutId)
    logToFile(`❌ Fetch error: ${error?.message}`)
    if (error?.cause) logToFile(`Cause: ${JSON.stringify(error.cause)}`)
    return {
      title: 'Ошибка загрузки блога',
      description: 'Не удалось получить данные этого блога',
    }
  }
}

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 10:08:09