73 lines
2.0 KiB
TypeScript
73 lines
2.0 KiB
TypeScript
"use client";
|
||
|
||
/**
|
||
* 根级错误边界
|
||
*
|
||
* Next.js 16 约定:必须为 Client Component;reset prop 在 v16 中被重命名为
|
||
* `unstable_retry`(参见 `node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/error.md`)。
|
||
*
|
||
* 行为:把 `error` 打到 console(保留 digest 便于服务端聚合),并提供"重试"
|
||
* 与"返回首页"两个动作。
|
||
*/
|
||
|
||
import { useEffect } from "react";
|
||
import Link from "next/link";
|
||
|
||
import { ExceptionHandler } from "@/core/errors";
|
||
import { ROUTES } from "@/router/routes";
|
||
import { Logger } from "@/utils/logger";
|
||
|
||
const log = new Logger("AppError");
|
||
|
||
interface ErrorProps {
|
||
error: Error & { digest?: string };
|
||
unstable_retry: () => void;
|
||
}
|
||
|
||
export default function GlobalError({ error, unstable_retry }: ErrorProps) {
|
||
useEffect(() => {
|
||
// 把错误送到 console;后续接 Sentry/Datadog 时改这里。
|
||
log.error("[app/error.tsx]", error);
|
||
}, [error]);
|
||
|
||
const errorMessage = ExceptionHandler.message(error, "Unexpected error");
|
||
|
||
return (
|
||
<div className="flex flex-1 flex-col items-center justify-center gap-md p-lg">
|
||
<h1
|
||
className="text-2xl font-semibold"
|
||
style={{ color: "var(--color-text-primary)" }}
|
||
>
|
||
Something went wrong
|
||
</h1>
|
||
<p
|
||
className="max-w-md text-center text-sm"
|
||
style={{ color: "var(--color-text-secondary)" }}
|
||
>
|
||
{errorMessage}
|
||
{error.digest ? ` (digest: ${error.digest})` : null}
|
||
</p>
|
||
<div className="flex gap-sm">
|
||
<button
|
||
type="button"
|
||
onClick={unstable_retry}
|
||
className="rounded-md px-lg py-sm text-sm"
|
||
style={{
|
||
background: "var(--color-accent)",
|
||
color: "var(--color-text-primary)",
|
||
}}
|
||
>
|
||
Retry
|
||
</button>
|
||
<Link
|
||
href={ROUTES.splash}
|
||
className="rounded-md border px-lg py-sm text-sm"
|
||
style={{ borderColor: "var(--color-border)" }}
|
||
>
|
||
Back to start
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|