fix(admin-web): 简化错误边界为静默 reload,去掉多余 UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f642ef1d56
commit
36b899d7ed
|
|
@ -2,91 +2,20 @@
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
/**
|
|
||||||
* Admin 路由段错误边界
|
|
||||||
*
|
|
||||||
* 捕获 (admin) 路由内的客户端异常。
|
|
||||||
* 当检测到 Next.js stale bundle / Server Action 不匹配错误时自动刷新页面。
|
|
||||||
*/
|
|
||||||
export default function AdminError({
|
export default function AdminError({
|
||||||
error,
|
error,
|
||||||
reset,
|
|
||||||
}: {
|
}: {
|
||||||
error: Error & { digest?: string };
|
error: Error & { digest?: string };
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
}) {
|
}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const msg = error?.message ?? '';
|
const key = 'gcx_admin_reload_count';
|
||||||
const isStaleBundle =
|
const count = parseInt(sessionStorage.getItem(key) ?? '0', 10);
|
||||||
msg.includes('Server Action') ||
|
if (count < 3) {
|
||||||
msg.includes('Failed to find') ||
|
sessionStorage.setItem(key, String(count + 1));
|
||||||
msg.includes('An unexpected response was received') ||
|
|
||||||
(error?.digest ?? '').startsWith('NEXT_');
|
|
||||||
|
|
||||||
if (isStaleBundle) {
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return null;
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,105 +1,26 @@
|
||||||
'use client';
|
'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';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export default function GlobalError({
|
export default function GlobalError({
|
||||||
error,
|
error,
|
||||||
reset,
|
|
||||||
}: {
|
}: {
|
||||||
error: Error & { digest?: string };
|
error: Error & { digest?: string };
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
}) {
|
}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 检测 Next.js stale bundle 导致的 Server Action 不匹配错误
|
// 捕获根级异常后立即刷新,防止无限循环限制 3 次
|
||||||
// 自动刷新页面以加载新版本
|
const key = 'gcx_admin_reload_count';
|
||||||
const msg = error?.message ?? '';
|
const count = parseInt(sessionStorage.getItem(key) ?? '0', 10);
|
||||||
const isStaleBundle =
|
if (count < 3) {
|
||||||
msg.includes('Server Action') ||
|
sessionStorage.setItem(key, String(count + 1));
|
||||||
msg.includes('Failed to find') ||
|
|
||||||
msg.includes('An unexpected response was received') ||
|
|
||||||
(error?.digest ?? '').startsWith('NEXT_');
|
|
||||||
|
|
||||||
if (isStaleBundle) {
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<body
|
<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>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue