fix(admin-web): 部署版本守卫,彻底防止 stale bundle 崩溃

方案:
- next.config.ts 构建时注入 NEXT_PUBLIC_BUILD_TIME 时间戳
- 新增 /api/version 路由,返回服务器当前 build 时间戳
- DeployGuard 组件每 3 分钟轮询 /api/version,
  发现版本变化立即 window.location.reload(),用户完全无感知
- global-error / admin error 边界仅做最后兜底(静默 reload)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-04 18:57:12 -08:00
parent 36b899d7ed
commit 300d55ff14
4 changed files with 57 additions and 1 deletions

View File

@ -2,6 +2,10 @@ import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'standalone',
// 构建时注入时间戳,客户端和 /api/version 均使用此值判断版本是否更新
env: {
NEXT_PUBLIC_BUILD_TIME: String(Date.now()),
},
reactStrictMode: true,
images: {
remotePatterns: [

View File

@ -0,0 +1,8 @@
import { NextResponse } from 'next/server';
export function GET() {
return NextResponse.json(
{ buildTime: process.env.NEXT_PUBLIC_BUILD_TIME },
{ headers: { 'Cache-Control': 'no-store' } },
);
}

View File

@ -3,6 +3,7 @@
import React, { useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthProvider } from '@/lib/auth-context';
import { DeployGuard } from '@/lib/deploy-guard';
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
@ -20,7 +21,10 @@ export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>{children}</AuthProvider>
<AuthProvider>
<DeployGuard />
{children}
</AuthProvider>
</QueryClientProvider>
);
}

View File

@ -0,0 +1,40 @@
'use client';
import { useEffect } from 'react';
const BUILD_TIME = process.env.NEXT_PUBLIC_BUILD_TIME ?? '';
const POLL_INTERVAL = 3 * 60 * 1000; // 3 分钟
/**
*
*
* 3 /api/version build
* bundle
*
*
* bundle Next.js Server Action
* ID "Application error"
*/
export function DeployGuard() {
useEffect(() => {
if (!BUILD_TIME) return;
const check = async () => {
try {
const res = await fetch('/api/version', { cache: 'no-store' });
if (!res.ok) return;
const { buildTime } = await res.json();
if (buildTime && buildTime !== BUILD_TIME) {
window.location.reload();
}
} catch {
// 网络错误忽略,下次轮询再试
}
};
const timer = setInterval(check, POLL_INTERVAL);
return () => clearInterval(timer);
}, []);
return null;
}