fix(admin-web): 添加 global-error.tsx 修复部署后 Server Action 崩溃

问题:每次重新部署容器后,浏览器持有旧 bundle,Next.js App Router
内部 Server Action ID("r"/"multi")与新服务器不匹配,导致客户端抛出
未捕获异常,触发全屏 "Application error"(周期性崩溃根因)。

修复:
- 添加 src/app/global-error.tsx(根级错误边界),检测到 stale bundle
  相关错误时自动调用 window.location.reload(),无感知恢复
- 添加 src/app/(admin)/error.tsx(admin 路由段错误边界),同样自动刷新
- 两个边界均提供「立即刷新」「重试」按钮,防止极端情况下自动刷新失效

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-04 18:50:22 -08:00
parent 4878449f8c
commit f642ef1d56
2 changed files with 197 additions and 0 deletions

View File

@ -0,0 +1,92 @@
'use client';
import { useEffect } from 'react';
/**
* Admin
*
* (admin)
* Next.js stale bundle / Server Action
*/
export default function AdminError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
const msg = error?.message ?? '';
const isStaleBundle =
msg.includes('Server Action') ||
msg.includes('Failed to find') ||
msg.includes('An unexpected response was received') ||
(error?.digest ?? '').startsWith('NEXT_');
if (isStaleBundle) {
window.location.reload();
}
}, [error]);
return (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
minHeight: '400px',
fontFamily: 'system-ui, sans-serif',
}}
>
<div
style={{
textAlign: 'center',
padding: '40px',
background: '#fff',
borderRadius: '12px',
boxShadow: '0 2px 16px rgba(0,0,0,0.08)',
maxWidth: '400px',
}}
>
<div style={{ fontSize: '40px', marginBottom: '16px' }}></div>
<h2 style={{ margin: '0 0 8px', fontSize: '18px', color: '#1a1a1a' }}>
</h2>
<p style={{ margin: '0 0 24px', color: '#666', fontSize: '14px' }}>
</p>
<div style={{ display: 'flex', gap: '12px', justifyContent: 'center' }}>
<button
onClick={() => window.location.reload()}
style={{
padding: '8px 20px',
background: '#2563eb',
color: '#fff',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '14px',
}}
>
</button>
<button
onClick={() => reset()}
style={{
padding: '8px 20px',
background: '#f5f5f5',
color: '#333',
border: '1px solid #e0e0e0',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '14px',
}}
>
</button>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,105 @@
'use client';
/**
* Next.js App Router
*
* Next.js
* "Application error: a client-side exception has occurred"
*
*
* - bundle Server Action ID"r" / "multi"
* Failed to find Server Action
* - bundle
*
* global-error.tsx <html><body> layout
*/
import { useEffect } from 'react';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// 检测 Next.js stale bundle 导致的 Server Action 不匹配错误
// 自动刷新页面以加载新版本
const msg = error?.message ?? '';
const isStaleBundle =
msg.includes('Server Action') ||
msg.includes('Failed to find') ||
msg.includes('An unexpected response was received') ||
(error?.digest ?? '').startsWith('NEXT_');
if (isStaleBundle) {
window.location.reload();
}
}, [error]);
return (
<html lang="zh-CN">
<body
style={{
margin: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
background: '#f5f5f5',
fontFamily: 'system-ui, sans-serif',
}}
>
<div
style={{
textAlign: 'center',
padding: '40px',
background: '#fff',
borderRadius: '12px',
boxShadow: '0 2px 16px rgba(0,0,0,0.08)',
maxWidth: '400px',
}}
>
<div style={{ fontSize: '40px', marginBottom: '16px' }}></div>
<h2 style={{ margin: '0 0 8px', fontSize: '18px', color: '#1a1a1a' }}>
</h2>
<p style={{ margin: '0 0 24px', color: '#666', fontSize: '14px' }}>
</p>
<div style={{ display: 'flex', gap: '12px', justifyContent: 'center' }}>
<button
onClick={() => window.location.reload()}
style={{
padding: '8px 20px',
background: '#2563eb',
color: '#fff',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '14px',
}}
>
</button>
<button
onClick={() => reset()}
style={{
padding: '8px 20px',
background: '#f5f5f5',
color: '#333',
border: '1px solid #e0e0e0',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '14px',
}}
>
</button>
</div>
</div>
</body>
</html>
);
}