diff --git a/apps/frontend/src/components/common/AppErrorBoundary.tsx b/apps/frontend/src/components/common/AppErrorBoundary.tsx
new file mode 100644
index 00000000..ebb8e7ee
--- /dev/null
+++ b/apps/frontend/src/components/common/AppErrorBoundary.tsx
@@ -0,0 +1,107 @@
+import { Component, type ErrorInfo, type ReactNode } from 'react'
+import { Button, Result, Space, Typography } from 'antd'
+
+import { isChunkLoadError, shouldAutoReloadChunkFailure } from '@/utils/chunk-load-error'
+import { isWorkerSite } from '@/utils/site'
+
+type AppErrorBoundaryProps = {
+ children: ReactNode
+ /** inline:只占内容区,保留侧边栏/导航;page:整页错误页。 */
+ variant?: 'page' | 'inline'
+ /** 路由变化时自动清除错误,避免一个页面崩溃后整个会话都停在错误页。 */
+ resetKey?: string
+}
+
+type AppErrorBoundaryState = {
+ error: Error | null
+}
+
+/**
+ * 全局兜底错误边界。
+ *
+ * 没有它时,任意一个页面在渲染期抛错(典型场景:部署后点击尚未加载过的路由,
+ * 旧的 lazy chunk 已被新镜像删除,`import()` 直接失败)会让 React 卸载整棵树,
+ * 用户看到的就是没有任何提示的纯白页面。这里统一转成可操作的错误页,并对
+ * 分片加载失败自动刷新一次拿新版本。
+ */
+export default class AppErrorBoundary extends Component<
+ AppErrorBoundaryProps,
+ AppErrorBoundaryState
+> {
+ state: AppErrorBoundaryState = { error: null }
+
+ static getDerivedStateFromError(error: unknown): AppErrorBoundaryState {
+ return { error: error instanceof Error ? error : new Error(String(error)) }
+ }
+
+ componentDidCatch(error: unknown, info: ErrorInfo) {
+ if (isChunkLoadError(error) && typeof window !== 'undefined') {
+ // 旧版本页面引用了已删除的分片:刷新一次即可拿到新 index.html 与新分片。
+ // 防抖窗口内再次失败则不再刷新,落到下面错误页,避免无限刷新。
+ if (shouldAutoReloadChunkFailure(window.sessionStorage)) {
+ window.location.reload()
+ return
+ }
+ }
+
+ console.error('[AppErrorBoundary] 页面渲染失败', error, info.componentStack)
+ }
+
+ componentDidUpdate(prevProps: AppErrorBoundaryProps) {
+ if (this.state.error && prevProps.resetKey !== this.props.resetKey) {
+ this.setState({ error: null })
+ }
+ }
+
+ render() {
+ const { error } = this.state
+ if (!error) return this.props.children
+
+ const chunkFailed = isChunkLoadError(error)
+ const inline = this.props.variant === 'inline'
+ const homePath = isWorkerSite() ? '/hall' : '/'
+
+ return (
+