Skip to content

Commit f2bfb2f

Browse files
fix(seo): prevent multiple h1 via remark heading shift
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 扫描才报出。
1 parent 78799b5 commit f2bfb2f

1 file changed

Lines changed: 39 additions & 2 deletions

File tree

source.config.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,38 @@ function remarkNormalizeCodeLang() {
3636
};
3737
}
3838

39+
/**
40+
* Remark 插件:避免双 h1
41+
*
42+
* 背景:Bing Webmaster 2026-05 报告 4 个页面有多个 <h1>,仓库里其实
43+
* 181 / 324 个 MDX 正文都写了 `# 一级标题`。page.tsx 已经渲染了
44+
* `<h1>{frontmatter.title}</h1>` 作为页面唯一 h1,MDX 正文里再写 `# x`
45+
* 就形成双 h1,对 SEO/无障碍都是反模式。
46+
*
47+
* 不能要求贡献者注意这个约束(markdown 习惯就是 # 当顶级标题),所以
48+
* 在 build 阶段自动 shift:如果 MDX 含 h1,整树 heading 降一级
49+
* (h1→h2 / h2→h3 / ... / h5→h6)。这样:
50+
* - 贡献者照常写 `# 标题`、`## 章节`、`### 子节`
51+
* - 渲染出来变 `<h2>` / `<h3>` / `<h4>`,保持层级关系
52+
* - page.tsx 的 `<h1>` 是页面唯一 h1
53+
*
54+
* 如果 MDX 不含 h1(作者已经从 ## 开始)就不动 —— 这种文档天然合规。
55+
*
56+
* 副作用:MDX 用到 h5 时会被降到 h6;h6 已是 markdown 最大深度无法再降,
57+
* 这里保留为 h6(罕见,CS/AI 技术文档基本不到 5 层)。
58+
*/
59+
function remarkShiftHeadingIfH1() {
60+
return (tree: Root) => {
61+
const hasH1 = tree.children.some(
62+
(n) => n.type === "heading" && n.depth === 1,
63+
);
64+
if (!hasH1) return;
65+
visit(tree, "heading", (node) => {
66+
if (node.depth < 6) node.depth += 1;
67+
});
68+
};
69+
}
70+
3971
/**
4072
* CRITICAL: Fumadocs 图片处理配置
4173
*
@@ -86,13 +118,18 @@ const imageOptions = {
86118
* 包含:
87119
* - remarkMath:启用 Markdown 数学语法支持 ($...$, $$...$$)
88120
* - remarkNormalizeCodeLang:将代码块语言标识符转为小写(解决 Shiki 大小写问题)
121+
* - remarkShiftHeadingIfH1:mdx 含 h1 时整树 heading 降一级,避免与 page.tsx 的 h1 冲突
89122
* - rehypeKatex:使用 KaTeX 将数学公式渲染为 HTML(strict:false 更宽松)
90123
* - remarkImageOptions:图片处理配置(禁用远程尺寸探测,见上方注释)
91124
*/
92125
export default defineConfig({
93126
mdxOptions: {
94-
// 支持 LaTeX 公式 + 规范化代码块语言标识符
95-
remarkPlugins: [remarkMath, remarkNormalizeCodeLang],
127+
// 支持 LaTeX 公式 + 规范化代码块 + h1 降级避免双 h1
128+
remarkPlugins: [
129+
remarkMath,
130+
remarkNormalizeCodeLang,
131+
remarkShiftHeadingIfH1,
132+
],
96133

97134
// 宽松的 KaTeX 渲染,不因轻微语法错误中断
98135
rehypePlugins: (v) => [[rehypeKatex, { strict: false }], ...v],

0 commit comments

Comments
 (0)