Skip to content

fix(seo): 修复排行榜链接全 404 + 补段化前 .en/.zh 旧 URL 的 301#348

Merged
longsizhuo merged 3 commits into
mainfrom
fix/leaderboard-canonical-urls-and-legacy-redirects
May 24, 2026
Merged

fix(seo): 修复排行榜链接全 404 + 补段化前 .en/.zh 旧 URL 的 301#348
longsizhuo merged 3 commits into
mainfrom
fix/leaderboard-canonical-urls-and-legacy-redirects

Conversation

@longsizhuo
Copy link
Copy Markdown
Member

背景

Google Search Console 报告 212 条 404 + 排行榜(/rank)所有文档链接点进去全是 404。两者根因都是 2026-05 i18n 段化迁移 后 URL 方案变了(locale 从文件后缀 .en/.zh 变成 URL 段前缀 /en/zh),但有两处没跟上。

改动

1. 排行榜链接全 404(线上 bug)

scripts/generate-leaderboard.{mjs→mts} 从后端 docId 反查 .source 文件路径手拼 URL,产出的是 /docs/learn/cs/.../01-static-array.zh 这种——缺 /zh|/en 前缀、带 .zh/.en 后缀、带 /index、leetcode 没拼音化,每条都 404。

  • 重写 buildCanonicalDocUrl:按后缀选 locale 前缀、去 /index、leetcode 按题号指向英文版 /en/docs/.../<ascii-slug>(中文翻译文件名经 fumadocs i18n 解析后真实 slug 不可预测,英文 ASCII slug 一定能解析且只在 /en 渲染)。
  • 过滤内容树里已不存在的孤儿 docId 死链(17 条,旧代码也会 fallback 成 404 的 /docs/<cuid>)。
  • 实测 153/153 排行榜链接全部 200(原 0/153),对照完整 sitemap + 直连 curl 双重验证。

2. GSC 上百条 .en/.zh 后缀旧 URL(最大一类 404)

next.config.mjs redirects() 补 301。必须放 next.config 而非 proxy.ts——proxy 的 matcher 排除了 .*\..*.en/.zh 后缀全带点,根本不进中间件;next.config redirects 跑在路由层不受影响。

  • .en/.zh 后缀剥离 + locale 前缀、/index 剥离、裸 /computer-science/* 映射、no-locale /docs/* 兜底。
  • 六类重定向 curl 实测均单跳 301 到正确 canonical。

3. 脚本运行方式

.mjs → .mts(因导入 lib/leetcode-slug.ts),prebuild 改用 node 跑(tsx 在 Node 24 下解析 .ts 具名导出报错;与兄弟脚本 generate-leetcode-slug-map.mts 一致)。

验证

  • pnpm build 通过(exit 0),路由表 + sitemap 正常。
  • dev server 实测:153/153 排行榜链接 200;6 类旧 URL 301 落到正确 canonical。
  • 详见 dev_docs/i18n_url_routing.md 新增的「段化前旧 URL 的 301」「排行榜链接 canonical」两节。

已知残留(非本 PR 范围)

  • GSC 里少量旧 CommunityShare 中文 leetcode URL(带点)经 proxy slug-map 仍 404(同一 fumadocs slug 不可预测问题),属内容文件命名归一的后续工作。
  • 非贡献者 /u/{id} 与代码块假路径的 404 是正确行为,无需修。

排行榜每条链接都 404:generate-leaderboard 从文件路径手拼 URL,没跟上
2026-05 的 i18n 段化——少 /zh|/en 前缀、保留 .en/.zh 后缀和 /index、leetcode
没拼音化。重写 buildCanonicalDocUrl 产出真实 canonical:按后缀选 locale 前缀、
去 /index、leetcode 按题号指向英文版 /en(中文翻译文件名经 fumadocs 解析后
slug 不可预测,英文 ASCII slug 一定 200),并过滤内容树里已不存在的孤儿 docId
死链。dev server 实测 153/153 链接全部 200(原 0/153)。

GSC 上百条 .en/.zh 后缀旧 URL(最大一类 404):在 next.config redirects 补 301
(必须放这而非 proxy.ts——proxy matcher 排除带点路径,.en/.zh 后缀全带点碰不到
中间件)。含 .en/.zh 后缀剥离、/index 剥离、裸 /computer-science 映射、no-locale
/docs 兜底。六类重定向 curl 实测均单跳 301 到正确 canonical。

脚本因导入 lib/leetcode-slug.ts 改用 node 跑 .mts(tsx 在 Node 24 下解析 .ts
具名导出报错),prebuild 同步改。
Copilot AI review requested due to automatic review settings May 24, 2026 13:11
@vercel
Copy link
Copy Markdown

vercel Bot commented May 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
involutionhell-github-io Ready Ready Preview, Comment May 24, 2026 1:52pm
website-preview Ready Ready Preview, Comment May 24, 2026 1:52pm

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 针对 2026-05 i18n 段化迁移后遗留的 SEO 问题,修复排行榜(/rank)中大量文档链接 404,并为段化前的 .en/.zh 旧 URL 补齐 301 重定向规则,以减少 GSC 报告的 404。

Changes:

  • 重写排行榜生成脚本的文档 URL 生成逻辑:按 locale 段前缀产出 canonical /zh|/en/docs/...,并对 leetcode 做题号→英文 ASCII slug 归一。
  • next.config.mjs 增加 .en/.zh 后缀旧 URL 到新段化 URL 的 301 重定向规则,并补充更老的 /computer-science/* 映射。
  • 调整 prebuild 脚本执行方式并更新生成的 generated/site-leaderboard.json,补充 i18n 路由文档说明。

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
scripts/generate-leaderboard.mts 生成排行榜数据时改用 canonical docs URL,并对 leetcode URL 进行特殊处理与死链过滤
package.json 更新 prebuild 脚本链路以运行新的 leaderboard 生成脚本
next.config.mjs 增加 .en/.zh 旧 URL 301 规则、补充更老路径映射与兜底重定向
generated/site-leaderboard.json 更新生成产物,使 contributedDocs 链接指向新 canonical 路径
dev_docs/i18n_url_routing.md 补充旧 URL 301 与排行榜 canonical 链接策略文档
Comments suppressed due to low confidence (1)

scripts/generate-leaderboard.mts:83

  • filename === "index" 时这里强制返回 /${DEFAULT_LOCALE},会把 index.en.mdx 这类英文目录页也指向 /zh/docs/...,与“按后缀选 locale 前缀”的逻辑不一致,且会导致排行榜/热榜里英文 leetcode 目录页链接落到中文页。建议改为使用解析出来的 locale(即 /${locale}/docs/${dirRoot}),保持 .en/.zh 与 URL 前缀一致。

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread next.config.mjs Outdated
Comment on lines +282 to +291

// ============= 兜底:no-locale /docs 一律送默认 locale =============
// 段化后 canonical 必带 locale 前缀,任何裸 /docs/... 都是旧链接(外链 /
// GSC / 热榜 DB 旧路径)。必须排在所有具体规则之后,让特化规则先命中。
// 目的地带 /zh 前缀,不会再被本规则匹配,无重定向环。
{
source: "/docs/:path*",
destination: "/zh/docs/:path*",
statusCode: 301,
},
CR(Copilot):next.config 的 `/docs/:path*` → `/zh/docs/:path*` 兜底是 301
(永久),抢在 next-intl middleware 之前把所有无前缀 /docs 链接(Hero/Footer
站内链接 + 外链)强制钉到 zh,英文用户再也协商不到 /en 且被浏览器/Google
缓存。删掉,无前缀路径交回 next-intl 按 Accept-Language/cookie 协商(307 单跳)。

dotted 中文 leetcode 旧 URL(46.全排列 / 1234. 替换… 这类带点的)此前被
proxy.ts matcher 的 .*\..* 排除、进不了 slug-map 301,只能硬 404。三处改动:
- matcher 给 leetcode 开口:(?!.*[Ll]eetcode).*\..*,带点的 leetcode 路径放进中间件
- slug-map value 改成完整 canonical URL:按题号优先指向英文页 /en(.en.md 一定
  200),无英文兄弟才退回 zh 拼音;并加 byNumber 兜住「中文文件已改名成英文、
  旧中文 URL 仍在 GSC」的题(46.全排列→46-permutations)
- 题号兜底仅对含中文的 slug 生效,避免英文 canonical slug 自我重定向成环 /
  把合法 /zh 英文命名页打到 /en

leetcode canonical 逻辑抽到 lib/leetcode-slug.ts(buildLeetcodeAsciiSlugByNumber
/ leetcodeCanonicalUrl),proxy.ts、slug-map 脚本、排行榜生成共用同一真源。

dev 实测:46.全排列 / [213]打家劫舍 / 93复原Ip地址 / 1234.替换 / 2894.分类 全部
301→200;/en/46-permutations、/zh/46-permutations 等 canonical 页不被劫持无环;
排行榜仍 153/153;sitemap/robots/普通路由无回归。

Co-authored-by: copilot-pull-request-reviewer[bot] <198982749+Copilot@users.noreply.github.com>
@longsizhuo
Copy link
Copy Markdown
Member Author

回应 CR + 补 dotted 中文 leetcode(commit 0fbd243

CR 修复(@copilot 指出的 no-locale 兜底 301)

确认是真问题,已删。那条 /docs/:path*/zh/docs/:path* 是 301(永久),抢在 next-intl middleware 之前把所有无前缀 /docs 链接强制钉到 zh —— 而 Hero.tsx / Footer.tsx 里确实有硬编码的 /docs/learn/ai 等无前缀链接,英文用户点了会被永久缓存到 /zh 再也协商不到 /en。删掉后交回 next-intl 按 Accept-Language/cookie 协商(307 单跳,行为正确)。实测 /docs/learn/ai → 307 → 协商 locale → 200。

顺带补完 dotted 中文 leetcode 旧 URL

46.全排列1234. 替换… 这类带点的旧 URL 之前被 proxy.ts matcher 的 .*\..* 排除、进不了 slug-map,只能硬 404。三处改动:

  • matcher 开口(?!.*[Ll]eetcode).*\..* —— 带点的 leetcode 路径放进中间件(leetcode 目录无带点静态资源,安全)。
  • slug-map → canonical URL:value 改成完整路径,按题号优先指向英文页 /en.en.md 一定 200),无英文兄弟才退回 zh 拼音;加 byNumber 兜住「中文文件已改名成英文、旧中文 URL 仍在 GSC」的题(46.全排列46-permutations)。
  • 防环:题号兜底仅对含中文的 slug 生效,避免英文 canonical slug 自我重定向成 301 死循环 / 把合法 /zh 英文命名页打到 /en

leetcode canonical 逻辑抽到 lib/leetcode-slug.ts,proxy / slug-map 脚本 / 排行榜生成共用同一真源。

验证

  • dev 实测:46.全排列[213]打家劫舍93复原Ip地址1234.替换2894.分类 全部 301→200;/en/46-permutations/zh/46-permutations 等 canonical 页不被劫持、无环。
  • 排行榜仍 153/153 在 sitemap 内;sitemap / 普通路由 / 静态资源无回归。
  • pnpm build 通过;tests/proxy-matcher.test.ts 18/18 通过。

已知非本 PR 范围

  • robots.txt 在 dev 报 500(public/robots.txtapp/robots.ts 冲突)—— 预先存在,与本 PR 无关,prod build 产出静态正常。

eslint no-control-regex 把 /[^\x00-\x7f]/ 里的 \x00 当非法控制字符报错,CI lint
挂掉。改用等价的 /[^ -~]/(可打印 ASCII 取反),对文件名/slug 语义一致,slug-map
重新生成无 diff。
@longsizhuo longsizhuo merged commit 496b8b4 into main May 24, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants