From 1e99a3139725993ec565d1074fafbc7f50141336 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 15:38:23 +0000 Subject: [PATCH] =?UTF-8?q?chore(seo):=20redirects=20=E5=8D=95=E8=B7=B3=20?= =?UTF-8?q?+=20root=20metadata=20hreflang=EF=BC=8Ci18n=20=E6=AE=B5?= =?UTF-8?q?=E5=8C=96=E6=94=B6=E5=B0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i18n PR (#330) 之后存在两个 SEO 残留问题,本 PR 一并修: 1. next.config.mjs redirects 全部 destination 加 /zh/ 前缀 现状:destination 都是 /docs/...(不带 locale),用户访问老 URL 流程: /docs/CommunityShare/RAG/rag → 301 /docs/learn/ai/foundation-models/rag/rag → 308 /zh/docs/learn/ai/foundation-models/rag/rag (next-intl middleware) 两跳 redirect 对 SEO PageRank 不友好(Google 推荐单跳)。把 destination 直接指向带 /zh/ 前缀的 canonical URL,搜索引擎单跳到位。 改 46 条规则的 destination,source 保持不带 locale(匹配老外链)。 英语用户由站内 切到 /en/...,cookie 同步偏好。 2. root layout metadata hreflang alternates.canonical 之前写死 /,i18n 段化后根路径会被 middleware redirect,搜索引擎拿到的 canonical 是个 redirect 目标,不准。 改为: - canonical: /zh(默认 locale 首页) - languages: zh-CN / en-US / x-default 三向声明 注:每个具体 [locale]/page 的 generateMetadata 会覆盖 root metadata 的 languages,给到该 page 各 locale 的具体 URL。这里 root 只作 fallback。 --- app/layout.tsx | 12 +++++++- next.config.mjs | 75 +++++++++++++++++++++++++++---------------------- 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 3dfe554..b8b3dfe 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -53,8 +53,18 @@ export const metadata: Metadata = { creator: "longsizhuo", publisher: "Involution Hell", category: "Technology", + // alternates 是 fallback,被 [locale] 段下的 generateMetadata 覆盖。 + // 默认 canonical 指 /zh(默认 locale 首页),不指 / 因为根路径会被 + // next-intl middleware 308 redirect,搜索引擎索引到 /zh 更直接。 + // languages 同时声明 hreflang,让 root metadata 应用到不在 [locale] + // 下的路径(如 /sitemap.xml 详情页 fallback 时)也能正确给出语言关系。 alternates: { - canonical: "/", + canonical: "/zh", + languages: { + "zh-CN": "/zh", + "en-US": "/en", + "x-default": "/zh", + }, }, robots: { index: true, diff --git a/next.config.mjs b/next.config.mjs index 79863fe..c7d8e31 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -37,124 +37,131 @@ const config = { async redirects() { // Option C IA 大重组:按读者意图分 learn / career / community / projects 四大顶层区。 // 顺序敏感——Next.js 首匹配命中,特殊文件级 + cpp_backend 老名字必须排在 wildcard 前。 + // + // 2026-05 i18n URL 段化(PR #330):所有 destination 都带 /zh/ 前缀 + // (默认 locale)。不带前缀的话会先 301 到 /docs/...,next-intl + // middleware 再 308 到 /zh/docs/...,对 SEO 是两跳 redirect。直接 + // destination 写带前缀的 canonical URL 让搜索引擎和用户都单跳到位。 + // 英语用户由站内 切到 /en/docs/...,cookie 同步偏好。 + // source 保持不带 locale 形式(匹配老 URL)。 return [ // ============= 特殊路径(必须在 wildcard 之前) ============= // CommunityShare/RAG → learn/ai/foundation-models/rag (RAG 文件归 ai 主题) { source: "/docs/CommunityShare/RAG/rag", - destination: "/docs/learn/ai/foundation-models/rag/rag", + destination: "/zh/docs/learn/ai/foundation-models/rag/rag", statusCode: 301, }, { source: "/docs/CommunityShare/RAG/embedding", - destination: "/docs/learn/ai/foundation-models/rag/embedding", + destination: "/zh/docs/learn/ai/foundation-models/rag/embedding", statusCode: 301, }, { // 文件名顺手规范化:context_engineering_intro → context-engineering-intro source: "/docs/CommunityShare/RAG/context_engineering_intro", destination: - "/docs/learn/ai/foundation-models/rag/context-engineering-intro", + "/zh/docs/learn/ai/foundation-models/rag/context-engineering-intro", statusCode: 301, }, // CommunityShare/Geek/leworldmodel → community/papers(paper summary 归社区论文) { source: "/docs/CommunityShare/Geek/leworldmodel", - destination: "/docs/community/papers/leworldmodel", + destination: "/zh/docs/community/papers/leworldmodel", statusCode: 301, }, // CommunityShare/Amazing-AI-Tools 下两篇分家:tool review 归 tools,paper 归 papers { source: "/docs/CommunityShare/Amazing-AI-Tools/perplexity-comet", - destination: "/docs/community/tools/perplexity-comet", + destination: "/zh/docs/community/tools/perplexity-comet", statusCode: 301, }, { source: - "/docs/CommunityShare/Amazing-AI-Tools/prompt-repetition-improves-non-reasoning-llms", + "/zh/docs/CommunityShare/Amazing-AI-Tools/prompt-repetition-improves-non-reasoning-llms", destination: - "/docs/community/papers/prompt-repetition-improves-non-reasoning-llms", + "/zh/docs/community/papers/prompt-repetition-improves-non-reasoning-llms", statusCode: 301, }, // PPO 强化学习主题 → learn/ai/reinforcement-learning { source: - "/docs/CommunityShare/Personal-Study-Notes/Reinforcement-Learning/ppo", - destination: "/docs/learn/ai/reinforcement-learning/ppo", + "/zh/docs/CommunityShare/Personal-Study-Notes/Reinforcement-Learning/ppo", + destination: "/zh/docs/learn/ai/reinforcement-learning/ppo", statusCode: 301, }, // swanlab 之前 test run 已移到 ai/misc-tools/(main commit d6d0a3d),现改到 community/tools { source: "/docs/ai/misc-tools/swanlab", - destination: "/docs/community/tools/swanlab", + destination: "/zh/docs/community/tools/swanlab", statusCode: 301, }, // cpp_backend 老命名(下划线 / 大驼峰)→ learn/cs/cpp-backend/ (kebab-case) { source: "/docs/computer-science/cpp_backend/mempool_simple", - destination: "/docs/learn/cs/cpp-backend/mempool-simple", + destination: "/zh/docs/learn/cs/cpp-backend/mempool-simple", statusCode: 301, }, { source: - "/docs/computer-science/cpp_backend/Handwritten_pool_components/1_Handwritten_threadpool", + "/zh/docs/computer-science/cpp_backend/Handwritten_pool_components/1_Handwritten_threadpool", destination: - "/docs/learn/cs/cpp-backend/handwritten-pool-components/1-handwritten-threadpool", + "/zh/docs/learn/cs/cpp-backend/handwritten-pool-components/1-handwritten-threadpool", statusCode: 301, }, { source: - "/docs/computer-science/cpp_backend/Handwritten_pool_components/2_Handwritten_mempool1", + "/zh/docs/computer-science/cpp_backend/Handwritten_pool_components/2_Handwritten_mempool1", destination: - "/docs/learn/cs/cpp-backend/handwritten-pool-components/2-handwritten-mempool1", + "/zh/docs/learn/cs/cpp-backend/handwritten-pool-components/2-handwritten-mempool1", statusCode: 301, }, { source: "/docs/computer-science/cpp_backend/easy_compile/1_cpp_libs", - destination: "/docs/learn/cs/cpp-backend/easy-compile/1-cpp-libs", + destination: "/zh/docs/learn/cs/cpp-backend/easy-compile/1-cpp-libs", statusCode: 301, }, { source: "/docs/computer-science/cpp_backend/easy_compile/2_base_gcc", - destination: "/docs/learn/cs/cpp-backend/easy-compile/2-base-gcc", + destination: "/zh/docs/learn/cs/cpp-backend/easy-compile/2-base-gcc", statusCode: 301, }, { source: "/docs/computer-science/cpp_backend/easy_compile/3_Make", - destination: "/docs/learn/cs/cpp-backend/easy-compile/3-make", + destination: "/zh/docs/learn/cs/cpp-backend/easy-compile/3-make", statusCode: 301, }, { source: "/docs/computer-science/cpp_backend/easy_compile/4_CMake", - destination: "/docs/learn/cs/cpp-backend/easy-compile/4-cmake", + destination: "/zh/docs/learn/cs/cpp-backend/easy-compile/4-cmake", statusCode: 301, }, { source: "/docs/computer-science/cpp_backend/easy_compile/5_vcpkg", - destination: "/docs/learn/cs/cpp-backend/easy-compile/5-vcpkg", + destination: "/zh/docs/learn/cs/cpp-backend/easy-compile/5-vcpkg", statusCode: 301, }, // all-projects/ai-town → projects/ai-town (顶层化) { source: "/docs/all-projects/ai-town", - destination: "/docs/projects/ai-town", + destination: "/zh/docs/projects/ai-town", statusCode: 301, }, // all-projects 裸路径本身也要兜底,防止指 /docs/all-projects 直接 404 { source: "/docs/all-projects", - destination: "/docs/projects", + destination: "/zh/docs/projects", statusCode: 301, }, // CommunityShare / Amazing-AI-Tools index 顶层 { source: "/docs/CommunityShare", - destination: "/docs/community", + destination: "/zh/docs/community", statusCode: 301, }, { source: "/docs/CommunityShare/Amazing-AI-Tools", - destination: "/docs/community/tools", + destination: "/zh/docs/community/tools", statusCode: 301, }, @@ -162,29 +169,29 @@ const config = { // 学科主目录:ai → learn/ai, computer-science → learn/cs { source: "/docs/ai/:path*", - destination: "/docs/learn/ai/:path*", + destination: "/zh/docs/learn/ai/:path*", statusCode: 301, }, { source: "/docs/computer-science/:path*", - destination: "/docs/learn/cs/:path*", + destination: "/zh/docs/learn/cs/:path*", statusCode: 301, }, // 求职场景 jobs/{interview-prep,event-keynote} → career/{interview-prep,events} { source: "/docs/jobs/interview-prep/:path*", - destination: "/docs/career/interview-prep/:path*", + destination: "/zh/docs/career/interview-prep/:path*", statusCode: 301, }, { source: "/docs/jobs/event-keynote/:path*", - destination: "/docs/career/events/:path*", + destination: "/zh/docs/career/events/:path*", statusCode: 301, }, // 项目 all-projects → projects { source: "/docs/all-projects/:path*", - destination: "/docs/projects/:path*", + destination: "/zh/docs/projects/:path*", statusCode: 301, }, // CommunityShare 分家:其他按主题归 community/* @@ -193,27 +200,27 @@ const config = { // 生成的 slug 映射做字面匹配,单跳 301 到正确拼音 URL。 { source: "/docs/CommunityShare/Language/:path*", - destination: "/docs/community/language/:path*", + destination: "/zh/docs/community/language/:path*", statusCode: 301, }, { source: "/docs/CommunityShare/Life/:path*", - destination: "/docs/community/life/:path*", + destination: "/zh/docs/community/life/:path*", statusCode: 301, }, { source: "/docs/CommunityShare/MentalHealth/:path*", - destination: "/docs/community/mental-health/:path*", + destination: "/zh/docs/community/mental-health/:path*", statusCode: 301, }, { source: "/docs/CommunityShare/Geek/:path*", - destination: "/docs/community/dev-tips/:path*", + destination: "/zh/docs/community/dev-tips/:path*", statusCode: 301, }, { source: "/docs/CommunityShare/Amazing-AI-Tools/:path*", - destination: "/docs/community/tools/:path*", + destination: "/zh/docs/community/tools/:path*", statusCode: 301, }, ];