Skip to content

Commit e74b5fd

Browse files
refactor(i18n): URL 段化让 docs 全量 SSG,砍 Vercel Fluid CPU 50%+ (#330)
* refactor: i18n URL 段化(/zh/...、/en/...)让 docs 全量 SSG,砍 Vercel Fluid CPU 主因:next-intl 原方案用 cookie + RSC 切语言(i18n/request.ts 调 cookies()), 让全站 RSC 树都被钉成 dynamic,318 篇 docs 每次访问现 SSR 一次,30 天 Vercel Fluid Active CPU 月用量逼近 4h(Hobby 上限)。 改造: 1. next-intl 切到 URL routing - i18n/routing.ts (defineRouting) localePrefix: always - i18n/navigation.ts (createNavigation 出 Link/router/redirect) - i18n/request.ts 从 requestLocale 读 locale,不再 await cookies() 2. 路由重排 - 17 个 user-facing page + admin 全部移到 app/[locale]/... - app/api/* / app/sitemap.ts / app/robots.ts 保留在根 - app/layout.tsx 极简化(不读 cookies;只剩 html/body + 全局 metadata + theme inline script + structured data + analytics) - app/[locale]/layout.tsx 调 setRequestLocale + 包所有 provider (NextIntlClientProvider / ThemeProvider / AuthProvider / fumadocs RootProvider) 3. fumadocs i18n - lib/source.ts defineI18n: zh/en, dot parser, fallbackLanguage zh - mdx 内容从 app/docs 拆到 content/docs(路由文件 / 内容分离,符合 fumadocs 推荐) - normalize 8 对 conflict(无后缀=英文 + .zh.mdx=中文翻译 swap 成 无后缀=zh、.en.mdx=英文翻译,单一规则) - app/[locale]/docs/[...slug]/page.tsx 用 source.getPage(slug, locale) + setRequestLocale + force-static - SectionIndex 简化(删手写翻译版剪枝,靠 fumadocs i18n 的 pageTree 按 locale 隔离) 4. SEO + 切换 - LocaleToggle 改 next-intl router.replace + locale prefix - sitemap.ts 输出双语 entry + alternates.languages(hreflang 自动) - robots.ts disallow 用 wildcard 匹配两种语言 - canonical / hreflang 在 docs page generateMetadata 重新生成 5. proxy 合并 (Next.js 16 用 proxy.ts 不是 middleware.ts) - 删掉旧 IP geo + Accept-Language + cookie 写入逻辑(next-intl createMiddleware 原生支持) - 老 leetcode 中文 slug 301 redirect 保留,先于 i18n middleware 跑 6. 路径统一 - DOCS_BASE / contributors normalize / api/chat fs.readFile / scripts 全部 app/docs → content/docs 预期: - /[locale]/docs/[...slug] 从 ƒ Dynamic 变 ● SSG,build 时 322 静态页面 预渲染 - Vercel Fluid CPU docs 这块归零(占当前 50%) - 首页 / events / feed 等仍 ƒ(fetchHomepageEvents 等 server fetch 阻挡, 下一轮单独 PR 处理) 验证: - pnpm exec tsc --noEmit: 0 错误 - pnpm build: 通过 - .next/prerender-manifest.json: 322 routes prerendered * docs(dev): 补 i18n URL 段化架构说明 + content/ 目录 README dev_docs/i18n_url_routing.md(217 行): - 为什么从 cookie 切到 URL 段(CPU 死结的来龙去脉) - 文件分工:i18n/ + proxy.ts + app/[locale]/ + content/docs/ - SSG 开关:每个 page/layout 必须 setRequestLocale + docs 加 force-static - 文档命名约定(dot parser 规则 + 加新文章 / 缺译 fallback) - 切换语言流程 + SEO(hreflang / canonical / sitemap / robots) - proxy 流程图 + 调试 5 类常见问题("为什么 page 还是 ƒ") - 已知未做的下一轮工作清单(首页 SSG 等) content/README.md: - 说明 content/docs 是 fumadocs mdx 内容根(与 app/ 路由分离) - 命名约定快速版(避免新人放 .zh.mdx 触发 build 冲突) - 历史:2026-05 从 app/docs 拆出 --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent d9f77b8 commit e74b5fd

489 files changed

Lines changed: 1046 additions & 859 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* 对应后端:/api/admin/community/* (走 @SaCheckRole("admin"))
1111
*/
1212

13-
import type { SharedLinkView } from "@/app/feed/types";
13+
import type { SharedLinkView } from "@/app/[locale]/feed/types";
1414

1515
interface ApiResponse<T> {
1616
success: boolean;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
*/
1313

1414
import { useEffect, useState } from "react";
15-
import { AdminGuard } from "@/app/admin/events/AdminGuard";
16-
import type { SharedLinkView } from "@/app/feed/types";
15+
import { AdminGuard } from "@/app/[locale]/admin/events/AdminGuard";
16+
import type { SharedLinkView } from "@/app/[locale]/feed/types";
1717
import { sanitizeExternalUrl, sanitizeMediaUrl } from "@/lib/url-safety";
1818
import { approveLink, listPendingLinks, rejectLink } from "./lib";
1919

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414

1515
import { useState, useRef, type FormEvent } from "react";
1616
import { useRouter } from "next/navigation";
17-
import type { EventRequest, EventView, EventStatus } from "@/app/events/types";
17+
import type {
18+
EventRequest,
19+
EventView,
20+
EventStatus,
21+
} from "@/app/[locale]/events/types";
1822
import { createEvent, updateEvent } from "./lib";
1923

2024
interface Props {

app/admin/events/[id]/edit/page.tsx renamed to app/[locale]/admin/events/[id]/edit/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import { use, useEffect, useState } from "react";
1111
import Link from "next/link";
12-
import type { EventView } from "@/app/events/types";
12+
import type { EventView } from "@/app/[locale]/events/types";
1313
import { AdminGuard } from "../../AdminGuard";
1414
import { EventForm } from "../../EventForm";
1515
import { getAdminEvent } from "../../lib";
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import type { ReactNode } from "react";
66
* 之前这里单独挂 Header / Footer 是因为当时还没有 /admin/layout.tsx。现在根 admin
77
* 已经有共享 layout,这层只是透传,保留文件是为了 Next 路由分段还能命中。
88
*/
9-
export default function AdminEventsLayout({ children }: { children: ReactNode }) {
9+
export default function AdminEventsLayout({
10+
children,
11+
}: {
12+
children: ReactNode;
13+
}) {
1014
return <>{children}</>;
1115
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* - 避免 SSR 缓存污染 admin 视角的数据
1010
*/
1111

12-
import type { EventRequest, EventView } from "@/app/events/types";
12+
import type { EventRequest, EventView } from "@/app/[locale]/events/types";
1313

1414
interface ApiResponse<T> {
1515
success: boolean;

0 commit comments

Comments
 (0)