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:
parent
36b899d7ed
commit
300d55ff14
|
|
@ -2,6 +2,10 @@ import type { NextConfig } from 'next';
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
output: 'standalone',
|
output: 'standalone',
|
||||||
|
// 构建时注入时间戳,客户端和 /api/version 均使用此值判断版本是否更新
|
||||||
|
env: {
|
||||||
|
NEXT_PUBLIC_BUILD_TIME: String(Date.now()),
|
||||||
|
},
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
|
|
|
||||||
|
|
@ -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' } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { AuthProvider } from '@/lib/auth-context';
|
import { AuthProvider } from '@/lib/auth-context';
|
||||||
|
import { DeployGuard } from '@/lib/deploy-guard';
|
||||||
|
|
||||||
export function Providers({ children }: { children: React.ReactNode }) {
|
export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
const [queryClient] = useState(
|
const [queryClient] = useState(
|
||||||
|
|
@ -20,7 +21,10 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<AuthProvider>{children}</AuthProvider>
|
<AuthProvider>
|
||||||
|
<DeployGuard />
|
||||||
|
{children}
|
||||||
|
</AuthProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue