diff --git a/app/not-found-tracker.tsx b/app/not-found-tracker.tsx new file mode 100644 index 00000000..e30ffae8 --- /dev/null +++ b/app/not-found-tracker.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { useEffect } from "react"; +import { usePathname } from "next/navigation"; + +// 从 not-found.tsx 拆出来的 umami 404 埋点。 +// 拆分原因:not-found.tsx 必须保持 Server Component(见同目录 not-found.tsx 注释), +// useEffect / usePathname / window.umami 只能在 client。 +export default function NotFoundTracker() { + const pathname = usePathname(); + + useEffect(() => { + if (window.umami) { + window.umami.track("error_404", { + path: pathname, + referrer: document.referrer || "direct", + }); + } + }, [pathname]); + + return null; +} diff --git a/app/not-found.tsx b/app/not-found.tsx index 7364fa6b..2d742024 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -1,23 +1,14 @@ -"use client"; - import Link from "next/link"; -import { useEffect } from "react"; -import { usePathname } from "next/navigation"; -import { useTranslations } from "next-intl"; +import { getTranslations } from "next-intl/server"; import { Button } from "@/app/components/ui/button"; +import NotFoundTracker from "./not-found-tracker"; -export default function NotFound() { - const pathname = usePathname(); - const t = useTranslations("notFound"); - - useEffect(() => { - if (window.umami) { - window.umami.track("error_404", { - path: pathname, - referrer: document.referrer || "direct", - }); - } - }, [pathname]); +// 必须是 Server Component:爬虫向 / 发 POST 时 Next 走 Server Action 路径, +// not-found 渲染不经过 layout,NextIntlClientProvider 不在树里, +// useTranslations 会抛 "No intl context"。getTranslations 走 server, +// 直接读 i18n/request.ts,没有 provider 依赖。 +export default async function NotFound() { + const t = await getTranslations("notFound"); return (