Skip to content

fix(seo): prevent multi-h1 + merge leetcode duplicates#342

Merged
longsizhuo merged 3 commits into
mainfrom
fix/seo-double-h1
May 11, 2026
Merged

fix(seo): prevent multi-h1 + merge leetcode duplicates#342
longsizhuo merged 3 commits into
mainfrom
fix/seo-double-h1

Conversation

@longsizhuo
Copy link
Copy Markdown
Member

Summary

Bing Webmaster 报 4 个页面有多个 `

` 标签(High severity)。仓库 324 个 MDX 中 181 个 正文写了 `# 一级标题`,叠加 `page.tsx` 渲染的 `

{frontmatter.title}

` 形成双 h1,是 SEO/无障碍反模式。

Root cause

  • `app/[locale]/docs/[...slug]/page.tsx` 自 2025-09-19 (commit `7b270d50`) 起渲染 frontmatter title 作为页面 h1
  • 社区贡献者按 markdown 习惯在 mdx 正文写 `# 一级标题`,没人意识到双 h1
  • 隐患存在 8 个月,2026-05 Bing 扫描才暴露

Fix

不能要求贡献者改写 markdown 习惯,所以在 build 阶段加 remark 插件自动 shift:

```ts
function remarkShiftHeadingIfH1(tree) {
if (tree 含 h1) {
所有 heading.depth += 1 (h1→h2 / h2→h3 / ... / h5→h6)
}
}
```

  • 贡献者照常写 `# 标题` / `## 章节` / `### 子节`
  • 渲染变 `

    ` / `

    ` / `

    `,保持层级关系

  • page.tsx 的 `

    {title}` 是页面唯一 h1

  • MDX 不含 h1 时不动(作者已从 ## 起,天然合规)

验证(本地 prerendered HTML)

页面 修前 修后
/zh/docs/career/interview-prep/bq 2 个 h1 1 个 h1 ✅
/zh/docs/learn/cs (3 个 # 混排) 3 个 h1 1 个 h1 ✅
leetcode 抽样 2-3 个 h1 1 个 h1 ✅

`pnpm build` / `pnpm typecheck` / `pnpm test` 全过。

副作用

mdx 用到 h5 时被降到 h6;h6 已是 markdown 最大深度无法再降,保留为 h6。CS/AI 技术文档基本不到 5 层,影响极小。

Test plan

  • CI: content-check / build / CodeQL 跑过
  • Vercel preview 部署成功
  • 抽样 review 渲染后 h1 数量
  • merge 后 IndexNow 触发 Bing 重抓

Bing Webmaster 报 4 个页面有多个 <h1>。仓库 324 个 MDX 里 181 个正文
都写了 # 一级标题(markdown 习惯),叠加 page.tsx 渲染的 <h1>{title}</h1>
形成双 h1,是 SEO/无障碍反模式。

不能要求贡献者改写习惯,所以在 build 阶段加 remark 插件自动处理:

  function remarkShiftHeadingIfH1(tree) {
    if (tree 含 h1) {
      所有 heading.depth += 1 (h1→h2 / h2→h3 / ... / h5→h6)
    }
  }

效果:
- 贡献者照常写 # 标题 / ## 章节 / ### 子节
- 渲染变 <h2> / <h3> / <h4>,保持层级关系
- page.tsx 的 <h1>{title}</h1> 是页面唯一 h1
- MDX 不含 h1 时不动 (作者已从 ## 起,天然合规)

本地验证:
- bq.html: 之前 2 h1 → 1 h1 ✅
- cs/index.html: 之前 3 h1 (含两个 # 中文混排) → 1 h1 ✅
- leetcode 抽样: 1 h1 ✅

历史背景:page.tsx 渲染 h1 是 2025-09-19 commit 7b270d5 加的,
社区贡献的 mdx 早就在写 # 一级标题,双 h1 隐患存在 8 个月直到 Bing
2026-05 扫描才报出。
Copilot AI review requested due to automatic review settings May 11, 2026 16:03
@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 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 11, 2026 6:51pm
website-preview Ready Ready Preview, Comment May 11, 2026 6:51pm

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

This PR addresses an SEO/accessibility issue where docs pages can render multiple <h1> tags by automatically shifting MDX heading levels at build time when an h1 is present, ensuring the page-level <h1>{frontmatter.title}</h1> remains the only H1.

Changes:

  • Adds a new remark plugin to shift all headings down by one level when an MDX document contains an h1.
  • Wires the new plugin into the global MDX remarkPlugins pipeline.

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

Comment thread source.config.ts Outdated
Comment on lines +61 to +63
const hasH1 = tree.children.some(
(n) => n.type === "heading" && n.depth === 1,
);
Bing 报 89 个重复 title。逐组分析后:

A) 8 组 leetcode 同题多文件版本("维护漂移"产物,命名规范迭代时没清理):

  5 组 zh 有双版本,docId 完全相同,是同一笔记不同阶段:
    - xxx.md(早期命名,含中文/特殊字符,部分 title 还有 ".md" 后缀 bug)
    - xxx-slug.zh.md(slug 规范化版本)
  → 删除 .md + 对应 .en.md(10 个文件),保留规范化版本。涉及题号:
    2270 / 93 / 2241 / 46 / 3138

  3 组 zh 是孤立中文版(仓库里早就有对应的 slug 化 .en.md 但没配对 .zh.md):
    - 42.md / 2131. xxx.md / [1545]xxx.md
  → 重命名为 slug 化(建立 zh+en hreflang 配对),删 translator 这次生成的
    .en.md(保留早就存在的 slug 化 .en.md)

  顺手修复 42 / 2131 的 title 残留 ".md" 后缀(之前 bug)

B) 2 组跨分区同名 "参考资料" / "References"(probability-statistics/resources
   和 linear-algebra/resources 都用通用 title):
   → 改成"概率论与统计学参考资料" / "Probability & Statistics References"
     和"线性代数参考资料" / "Linear Algebra References"

效果:
  重复 title 组数:30 → 20(剩 20 组是 zh/en 同字面,论文/项目名通用
  英文专有名词,有 hreflang 配对,Google/Bing 一般不视为重复)

注:删除的文件可以从 git 历史恢复(保留 docId 一致追溯)。
@longsizhuo longsizhuo changed the title fix(seo): prevent multiple h1 via remark heading shift fix(seo): prevent multi-h1 + merge leetcode duplicates May 11, 2026
Copilot CR: `tree.children.some(...)` 只看顶级子节点,h1 嵌套在
blockquote / list 里(markdown 允许 \`> # title\`)会漏判。

改用 visit 整树扫描检测 h1。代价是多一次 traversal(约几十微秒每文档),
确保所有位置的 h1 都被识别并触发整树 demote。
@longsizhuo longsizhuo merged commit 1b70f2c into main May 11, 2026
8 checks passed
longsizhuo pushed a commit that referenced this pull request May 12, 2026
用户 review 后指出"丢西瓜捡芝麻"风险,复盘 Vercel 30 天 dashboard 后修订:

## 真实根因(不是 /_not-found)

dashboard 30 天曲线显示 5/11 CPU 峰值 80-90min(基线 5-15min/day),完美
对应 SEO PR 落地时间:
- 5/11 15:01-15:39 UTC: PR #341 (253 MDX descriptions + 32 新 EN 翻译)
- 5/11 16:02-18:41 UTC: PR #342 (remark heading shift + leetcode dedup)
- 5/11 19:01-19:27 UTC: PR #343 + #340,4 小时 4 次 deploy 清空 ISR

加上 deploy.yml 里 IndexNow 主动告诉 Bing 重抓 → 5/10-5/12 crawler 风暴。
**这是 SEO 工作 successful 的代价,不是 bug。** 真实流量。

/_not-found 静态化 + bot blocklist 是真实 waste 清理(保留),但不能独立
解释 4× 激增。

## 撤回的两条 hack

1. Sentry tracesSampleRate 0.1 → 0.02:撤回,保持 10%
   observability 不能为这点 CPU 让步,10% 是行业标准,client/server/edge
   三处必须一致才能跨 runtime 串联 trace。

2. fetchEvents 失败一律返空:改成只在 NEXT_PHASE === phase-production-build
   时返空,运行时仍 throw 让 Sentry 抓真故障。否则 prod backend 挂了会被
   误显示成"暂无活动",掩盖故障。

## 保留的修复(best practice,不是 hack)

- /_not-found ƒ → ○:根 404 本就不需要 i18n
- proxy.ts bot blocklist:扫描器不该烧 Fluid
- /[locale]/docs /events /login 缺 setRequestLocale → 补:SSG/ISR 本就该工作
- /editor /share cascade ●:纯 client component,安全

## Build 验证

pnpm build 重跑:
- /[locale]/events 仍是 ● ISR 5m 1y
- [events] fetch failed at build, rendering empty shell(NEXT_PHASE guard 工作)
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