Skip to content

Commit 496b8b4

Browse files
authored
Merge pull request #348 from InvolutionHell/fix/leaderboard-canonical-urls-and-legacy-redirects
fix(seo): 修复排行榜链接全 404 + 补段化前 .en/.zh 旧 URL 的 301
2 parents d21f336 + 1f39758 commit 496b8b4

9 files changed

Lines changed: 1906 additions & 999 deletions

dev_docs/i18n_url_routing.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,36 @@ languages:
280280

281281
`disallow` 用 wildcard 形式 `/*/admin/` 等匹配两种 locale 前缀。
282282

283+
### 段化前旧 URL 的 301(`.en` / `.zh` 后缀)
284+
285+
段化前 locale 是**文件后缀**`/docs/foo.en``/docs/bar.zh`),段化后变成
286+
**URL 段前缀**`/en/docs/foo`)。GSC 里还存着上百条 `.en`/`.zh` 旧 URL,是最大
287+
一类 404。重定向规则放在 **`next.config.mjs``redirects()`**,不是 `proxy.ts`
288+
289+
> `proxy.ts` 的 matcher 排除了 `.*\..*`(任何带点路径都不进中间件),而 `.en`/
290+
> `.zh` 后缀全带点 → 中间件根本碰不到。`next.config``redirects()` 跑在路由层,
291+
> 不受该排除影响,是带点旧 URL 唯一能 301 兜住的地方。
292+
293+
规则顺序敏感(首匹配命中):`.en`/`.zh` 后缀剥离 + `/index` 剥离排在 IA wildcard
294+
之前;末尾一条 no-locale `/docs/:path*``/zh/docs/:path*` 兜底(段化后 canonical
295+
必带 locale 前缀,任何裸 `/docs/...` 都是旧链接)。`:slug(.*)\\.en` 用贪婪捕获吃掉
296+
`/` 的多级路径,结尾 `\.en` 做字面锚定。
297+
298+
### 排行榜 / 热榜链接的 canonical URL
299+
300+
`scripts/generate-leaderboard.mts` 从后端 docId 反查 `.source` 文件路径拼 URL。
301+
段化后必须产出 `/<locale>/docs/<slug>`,否则排行榜每条链接都 404。`buildCanonicalDocUrl`
302+
做三件事和路由对齐:① 剥 `.en`/`.zh` 后缀并据此选 locale 前缀;② 去 `/index`
303+
③ leetcode 走特殊处理。
304+
305+
**leetcode 的坑**:同一题常有英文版(`1234-xxx.en.md`)+ 中文翻译版
306+
`1234. 中文_translated.md` / `[121]中文_translated.md`)。中文文件名经 fumadocs
307+
i18n 解析后的真实 slug **不可预测**——带 `. ` 点空格的会塌缩、带方括号的能独立成
308+
拼音页,手搓拼音对不齐(`proxy.ts` 的 leetcode slug-map 也踩同样的坑)。而英文文件的
309+
ASCII slug 一定能解析、且只在 `/en` 渲染(`.en.md`,zh 不回退 en)。所以脚本按**题号**
310+
把任何 leetcode 贡献指向英文版 `/en/docs/.../<ascii-slug>`(保证 200),无英文兄弟时
311+
才退回 zh 拼音。改 leetcode 文件命名后,这套靠 `pnpm build` 重新生成。
312+
283313
## proxy 流程
284314

285315
每个请求 → `proxy.ts`

generated/leetcode-slug-map.json

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,74 @@
11
{
2-
"1234. 替换子串得到平衡字符串_translated": "1234-ti-huan-zi-chuan-de-dao-ping-heng-zi-fu-chuan-translated",
3-
"142.环形链表II_translated": "142-huan-xing-lian-biao-iitranslated",
4-
"1653. 使字符串平衡的最少删除次数_translated": "1653-shi-zi-fu-chuan-ping-heng-de-zui-shao-shan-chu-ci-shu-translated",
5-
"1664生成平衡数组的方案数_translated": "1664-sheng-cheng-ping-heng-shu-zu-de-fang-an-shu-translated",
6-
"1825求出 MK 平均值_translated": "1825-qiu-chu-mk-ping-jun-zhi-translated",
7-
"1828统计一个圆中点的数目_translated": "1828-tong-ji-yi-ge-yuan-zhong-dian-de-shu-mu-translated",
8-
"2299强密码检验器II_translated": "2299-qiang-mi-ma-jian-yan-qi-iitranslated",
9-
"2309兼具大小写的最好英文字母_translated": "2309-jian-ju-da-xiao-xie-de-zui-hao-ying-wen-zi-mu-translated",
10-
"2335. 装满杯子需要的最短总时长_translated": "2335-zhuang-man-bei-zi-xu-yao-de-zui-duan-zong-shi-chang-translated",
11-
"2341. 数组能形成多少数对_translated": "2341-shu-zu-neng-xing-cheng-duo-shao-shu-dui-translated",
12-
"2639. 查询网格图中每一列的宽度_translated": "2639-cha-xun-wang-ge-tu-zhong-mei-yi-lie-de-kuan-du-translated",
13-
"2679.矩阵中的和_translated": "2679-ju-zhen-zhong-de-he-translated",
14-
"2894. 分类求和并作差": "2894-fen-lei-qiu-he-bing-zuo-cha",
15-
"3072. 将元素分配到两个数组中 II_translated": "3072-jiang-yuan-su-fen-pei-dao-liang-ge-shu-zu-zhong-iitranslated",
16-
"345. 反转字符串中的元音字母_translated": "345-fan-zhuan-zi-fu-chuan-zhong-de-yuan-yin-zi-mu-translated",
17-
"538.把二叉搜索树转换为累加树_translated": "538-ba-er-cha-sou-suo-shu-zhuan-huan-wei-lei-jia-shu-translated",
18-
"6323. 将钱分给最多的儿童_translated": "6323-jiang-qian-fen-gei-zui-duo-de-er-tong-translated",
19-
"76最小覆盖子串_translated": "76-zui-xiao-fu-gai-zi-chuan-translated",
20-
"994.腐烂的橘子_translated": "994-fu-lan-de-ju-zi-translated",
21-
"[121]买卖股票的最佳时期_translated": "121-mai-mai-gu-piao-de-zui-jia-shi-qi-translated",
22-
"[1333]餐厅过滤器_translated": "1333-can-ting-guo-l-qi-translated",
23-
"[146]LRU 缓存_translated": "146lru-huan-cun-translated",
24-
"[213]打家劫舍 II_translated": "213-da-jia-jie-she-iitranslated",
25-
"[2490]回环句_translated": "2490-hui-huan-ju-translated",
26-
"[2562]找出数组的串联值_translated": "2562-zhao-chu-shu-zu-de-chuan-lian-zhi-translated",
27-
"[2582]递枕头_translated": "2582-di-zhen-tou-translated",
28-
"brief_alternate 作业帮忙_translated": "briefalternate-zuo-ye-bang-mang-translated",
29-
"剑指 Offer II 021. 删除链表的倒数第 n 个结点_translated": "jian-zhi-offerii021-shan-chu-lian-biao-de-dao-shu-di-n-ge-jie-dian-translated"
2+
"byName": {
3+
"1234. 替换子串得到平衡字符串_translated": "/en/docs/career/interview-prep/leetcode/1234-replace-substring-for-balanced-string",
4+
"142.环形链表II_translated": "/en/docs/career/interview-prep/leetcode/142-linked-list-cycle-ii",
5+
"1653. 使字符串平衡的最少删除次数_translated": "/en/docs/career/interview-prep/leetcode/1653-minimum-deletions-to-make-string-balanced",
6+
"1664生成平衡数组的方案数_translated": "/en/docs/career/interview-prep/leetcode/1664-ways-to-make-a-fair-array",
7+
"1825求出 MK 平均值_translated": "/en/docs/career/interview-prep/leetcode/1825-mk-average",
8+
"1828统计一个圆中点的数目_translated": "/en/docs/career/interview-prep/leetcode/1828-queries-on-number-of-points-inside-a-circle",
9+
"2299强密码检验器II_translated": "/en/docs/career/interview-prep/leetcode/2299-strong-password-checker-ii",
10+
"2309兼具大小写的最好英文字母_translated": "/en/docs/career/interview-prep/leetcode/2309-greatest-english-letter-in-upper-and-lower-case",
11+
"2335. 装满杯子需要的最短总时长_translated": "/en/docs/career/interview-prep/leetcode/2335-minimum-amount-of-time-to-fill-cups",
12+
"2341. 数组能形成多少数对_translated": "/en/docs/career/interview-prep/leetcode/2341-maximum-number-of-pairs-in-array",
13+
"2639. 查询网格图中每一列的宽度_translated": "/en/docs/career/interview-prep/leetcode/2639-find-column-width-of-grid",
14+
"2679.矩阵中的和_translated": "/en/docs/career/interview-prep/leetcode/2679-sum-in-a-matrix",
15+
"2894. 分类求和并作差": "/en/docs/career/interview-prep/leetcode/2894-divisible-and-non-divisible-sums-difference",
16+
"3072. 将元素分配到两个数组中 II_translated": "/en/docs/career/interview-prep/leetcode/3072-distribute-elements-into-two-arrays-ii",
17+
"345. 反转字符串中的元音字母_translated": "/en/docs/career/interview-prep/leetcode/345-reverse-vowels-of-a-string",
18+
"538.把二叉搜索树转换为累加树_translated": "/en/docs/career/interview-prep/leetcode/538-convert-bst-to-greater-sum-tree",
19+
"6323. 将钱分给最多的儿童_translated": "/en/docs/career/interview-prep/leetcode/6323-distribute-money-to-maximum-children",
20+
"76最小覆盖子串_translated": "/en/docs/career/interview-prep/leetcode/76-minimum-window-substring",
21+
"994.腐烂的橘子_translated": "/en/docs/career/interview-prep/leetcode/994-rotting-oranges",
22+
"[121]买卖股票的最佳时期_translated": "/en/docs/career/interview-prep/leetcode/121-best-time-to-buy-and-sell-stock",
23+
"[1333]餐厅过滤器_translated": "/en/docs/career/interview-prep/leetcode/1333-filter-restaurants-by-vegan-friendly-price-and-distance",
24+
"[146]LRU 缓存_translated": "/en/docs/career/interview-prep/leetcode/146-lru-cache",
25+
"[213]打家劫舍 II_translated": "/en/docs/career/interview-prep/leetcode/213-house-robber-ii",
26+
"[2490]回环句_translated": "/en/docs/career/interview-prep/leetcode/2490-circular-sentence",
27+
"[2562]找出数组的串联值_translated": "/en/docs/career/interview-prep/leetcode/2562-find-the-array-concatenation-value",
28+
"[2582]递枕头_translated": "/en/docs/career/interview-prep/leetcode/2582-pass-the-pillow",
29+
"brief_alternate 作业帮忙_translated": "/zh/docs/career/interview-prep/leetcode/briefalternate-zuo-ye-bang-mang-translated",
30+
"剑指 Offer II 021. 删除链表的倒数第 n 个结点_translated": "/en/docs/career/interview-prep/leetcode/sword-offer-ii-021-remove-nth-node-from-end-of-list"
31+
},
32+
"byNumber": {
33+
"42": "/en/docs/career/interview-prep/leetcode/42-trapping-rain-water",
34+
"46": "/en/docs/career/interview-prep/leetcode/46-permutations",
35+
"76": "/en/docs/career/interview-prep/leetcode/76-minimum-window-substring",
36+
"80": "/en/docs/career/interview-prep/leetcode/80-remove-duplicates-from-sorted-array-ii",
37+
"93": "/en/docs/career/interview-prep/leetcode/93-restore-ip-addresses",
38+
"121": "/en/docs/career/interview-prep/leetcode/121-best-time-to-buy-and-sell-stock",
39+
"142": "/en/docs/career/interview-prep/leetcode/142-linked-list-cycle-ii",
40+
"146": "/en/docs/career/interview-prep/leetcode/146-lru-cache",
41+
"213": "/en/docs/career/interview-prep/leetcode/213-house-robber-ii",
42+
"219": "/en/docs/career/interview-prep/leetcode/219-contains-duplicate-ii",
43+
"345": "/en/docs/career/interview-prep/leetcode/345-reverse-vowels-of-a-string",
44+
"538": "/en/docs/career/interview-prep/leetcode/538-convert-bst-to-greater-sum-tree",
45+
"994": "/en/docs/career/interview-prep/leetcode/994-rotting-oranges",
46+
"1004": "/en/docs/career/interview-prep/leetcode/1004-max-consecutive-ones-iii",
47+
"1234": "/en/docs/career/interview-prep/leetcode/1234-replace-substring-for-balanced-string",
48+
"1333": "/en/docs/career/interview-prep/leetcode/1333-filter-restaurants-by-vegan-friendly-price-and-distance",
49+
"1545": "/en/docs/career/interview-prep/leetcode/1545-find-kth-bit-in-nth-binary-string",
50+
"1653": "/en/docs/career/interview-prep/leetcode/1653-minimum-deletions-to-make-string-balanced",
51+
"1664": "/en/docs/career/interview-prep/leetcode/1664-ways-to-make-a-fair-array",
52+
"1825": "/en/docs/career/interview-prep/leetcode/1825-mk-average",
53+
"1828": "/en/docs/career/interview-prep/leetcode/1828-queries-on-number-of-points-inside-a-circle",
54+
"2131": "/en/docs/career/interview-prep/leetcode/2131-longest-palindrome-by-concatenating-two-letter-words",
55+
"2241": "/en/docs/career/interview-prep/leetcode/2241-design-an-atm-machine",
56+
"2270": "/en/docs/career/interview-prep/leetcode/2270-number-of-ways-to-split-array",
57+
"2293": "/en/docs/career/interview-prep/leetcode/2293-min-max-game",
58+
"2299": "/en/docs/career/interview-prep/leetcode/2299-strong-password-checker-ii",
59+
"2309": "/en/docs/career/interview-prep/leetcode/2309-greatest-english-letter-in-upper-and-lower-case",
60+
"2335": "/en/docs/career/interview-prep/leetcode/2335-minimum-amount-of-time-to-fill-cups",
61+
"2341": "/en/docs/career/interview-prep/leetcode/2341-maximum-number-of-pairs-in-array",
62+
"2490": "/en/docs/career/interview-prep/leetcode/2490-circular-sentence",
63+
"2562": "/en/docs/career/interview-prep/leetcode/2562-find-the-array-concatenation-value",
64+
"2582": "/en/docs/career/interview-prep/leetcode/2582-pass-the-pillow",
65+
"2639": "/en/docs/career/interview-prep/leetcode/2639-find-column-width-of-grid",
66+
"2679": "/en/docs/career/interview-prep/leetcode/2679-sum-in-a-matrix",
67+
"2894": "/en/docs/career/interview-prep/leetcode/2894-divisible-and-non-divisible-sums-difference",
68+
"3072": "/en/docs/career/interview-prep/leetcode/3072-distribute-elements-into-two-arrays-ii",
69+
"3138": "/en/docs/career/interview-prep/leetcode/3138-minimum-length-of-anagram-concatenation",
70+
"6323": "/en/docs/career/interview-prep/leetcode/6323-distribute-money-to-maximum-children",
71+
"9021": "/en/docs/career/interview-prep/leetcode/9021-tut-3-25t1",
72+
"021": "/en/docs/career/interview-prep/leetcode/sword-offer-ii-021-remove-nth-node-from-end-of-list"
73+
}
3074
}

0 commit comments

Comments
 (0)