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

Vercel部署Next.js后旧标签页跳转404问题求解

问题根因

你排查的定位完全准确,该问题是Next.js客户端路由机制与Vercel默认部署策略共同导致的共性问题:

  • Next.js客户端路由跳转时,会携带当前页面加载时拿到的buildId请求/_next/data/[buildId]/xxx.json接口获取页面数据
  • Vercel完成新版本部署后,默认会立刻清理旧版本对应的函数实例、/_next/data/路径下的历史数据接口,旧构建ID对应的接口直接返回404
  • 部署前打开的旧标签页仍缓存着旧版客户端JS与旧buildId,跳转时请求已被清理的旧接口,就会触发404,手动刷新后加载新版资源、拿到新buildId,异常消失。
解决方案

按落地成本、覆盖完整度排序,可选择以下方案:

方案1:配置Vercel保留旧版本构建资源(零代码改动,最快生效)

  • 进入Vercel对应项目的控制台,打开「Settings」-「Deployments」配置页
  • 找到Deployment Retention配置项,将历史构建的保留时长从默认的「即时清理」调整为7~30天,时长匹配你站点用户的平均标签页停留周期即可
  • 配置生效后,旧版本的/_next/data/接口不会在新部署后立刻失效,旧标签页的跳转请求可以正常响应,等用户后续手动刷新时自然切换到新版本。

方案2:客户端检测版本变更自动软刷新(覆盖最完整,用户无感知)

通过版本检测逻辑,在发现用户处于旧版本标签页时自动触发刷新,完全不需要用户手动操作,不依赖平台配置:

  1. 首先修改next.config.js,统一构建ID生成规则,暴露构建ID到客户端:
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 优先使用Vercel注入的Git提交哈希作为构建ID,保证每次部署ID唯一
  generateBuildId: async () => {
    return process.env.VERCEL_GIT_COMMIT_SHA || `build_${Date.now()}`
  },
  // 将构建ID暴露给客户端代码
  publicRuntimeConfig: {
    buildId: process.env.VERCEL_GIT_COMMIT_SHA || `build_${Date.now()}`
  }
}
module.exports = nextConfig
  1. 修改Vercel项目的构建命令,在执行next build前将当前构建ID写入静态文件,供客户端轮询最新版本:
# 构建命令示例
echo $VERCEL_GIT_COMMIT_SHA > public/build-id.txt && next build
  1. 在全局布局中加入版本检测组件(Pages Router挂载到_app.tsx,App Router挂载到根layout.tsx):
// 组件文件:components/VersionGuard.tsx
'use client'
import getConfig from 'next/config'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'

const { publicRuntimeConfig } = getConfig()
const CURRENT_BUILD_ID = publicRuntimeConfig.buildId

export default function VersionGuard() {
  const router = useRouter()

  const checkVersionUpdate = async () => {
    try {
      const res = await fetch('/build-id.txt', { cache: 'no-store' })
      const latestBuildId = (await res.text()).trim()
      if (latestBuildId !== CURRENT_BUILD_ID) {
        // 版本不一致时直接刷新,加载最新资源
        window.location.replace(window.location.href)
      }
    } catch (e) {}
  }

  useEffect(() => {
    const savedBuildId = localStorage.getItem('site_current_build')
    // 首次访问存储当前版本
    if (!savedBuildId) {
      localStorage.setItem('site_current_build', CURRENT_BUILD_ID)
      return
    }
    // 本地存储版本和当前加载版本不一致,说明刚刷新过新版本,更新缓存
    if (savedBuildId !== CURRENT_BUILD_ID) {
      localStorage.setItem('site_current_build', CURRENT_BUILD_ID)
      return
    }
    // 路由跳转前检测版本
    const handleRouteStart = () => checkVersionUpdate()
    router.events?.on('routeChangeStart', handleRouteStart)
    // 定时轮询兜底,覆盖用户长时间停留标签页的场景,每5分钟检测一次
    const timer = setInterval(checkVersionUpdate, 5 * 60 * 1000)

    return () => {
      router.events?.off('routeChangeStart', handleRouteStart)
      clearInterval(timer)
    }
  }, [router])

  return null
}

方案3:自定义404页兜底

作为以上方案的补充,在极端场景下自动触发刷新,不需要用户手动操作:

  • 在自定义404组件(Pages Router为pages/404.tsx,App Router为app/not-found.tsx)中加入检测逻辑,识别到是旧/_next/data/接口失效触发的404时自动刷新:
'use client'
import { useEffect } from 'react'

export default function NotFound() {
  useEffect(() => {
    const hasOldDataReqError = window.performance
      .getEntriesByType('resource')
      .some(entry => entry.name.includes('/_next/data/') && entry.responseStatus === 404)
    if (hasOldDataReqError) {
      window.location.reload()
    }
  }, [])

  return <div>页面加载中,请稍候...</div>
}
选型建议
  • 不想修改业务代码的场景直接选方案1,配置后5分钟即可生效
  • 对用户体验要求高、需要覆盖所有停留场景的站点,选方案2即可彻底解决问题
  • 方案3可以作为前两个方案的兜底配置,进一步降低异常触发概率

内容的提问来源于stack exchange,提问作者Utkarsh Chaudhary

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 12:06:17