Markdown 目录(TOC)完整教程:手动创建与自动生成方法

写长文档的时候,有个目录(Table of Contents,简称 TOC)能让读者快速跳转到感兴趣的章节。Markdown 本身没有专门生成目录的语法,但通过几种方法完全可以实现,而且有些方法比想象中简单。

Markdown 目录的基本原理

Markdown 目录的本质就是一组内部链接(也叫锚点链接),每个链接指向页面内的某个标题。渲染器会把你的标题自动转换成 HTML 锚点,你只需要用链接语法指向它就行了。

基本格式是这样的:

## 目录

- [简介](#简介)
- [安装](#安装)
- [使用方法](#使用方法)
- [常见问题](#常见问题)

## 简介

这里是简介的内容...

## 安装

安装步骤...

关键在于理解标题怎么变成锚点——不同平台的规则不一样,搞清楚这个,手动写目录就不难了。

不同平台的锚点生成规则

这是很多人容易忽略的地方:同一个标题,在 GitHub、GitLab、Bitbucket 上生成的锚点可能完全不同。我之前帮一个项目写 README 的时候,手动写了目录在本地的 GitLab 上显示正常,推到 GitHub 后目录全部失效了,排查了好一阵才发现是锚点规则不一样。

GitHub 锚点规则

GitHub 的规则最常用,也比较直观:

规则示例
全部转小写## Hello World#hello-world
空格变短横线## Getting Started#getting-started
去掉标点符号## What's New?#whats-new
连续短横线合并## Cost ($) Analysis#cost--analysis
重复标题加编号第二个 ## Example#example-1

GitLab 锚点规则

GitLab 的规则和 GitHub 大体相似,但有个关键区别——GitLab 支持用 [[_TOC_]] 标签直接自动生成目录,不需要手动写:

## 文档标题

[[_TOC_]]

## 第一节
内容...

## 第二节
内容...

[[_TOC_]] 会根据页面的所有标题自动生成一个可点击的目录。这个功能从 GitLab 14.x 开始支持,写 wiki 和 README 都能用。

Bitbucket 锚点规则

Bitbucket 比较特殊,它会给所有锚点加一个 markdown-header- 前缀:

- [简介](#markdown-header-简介)
- [安装步骤](#markdown-header-安装步骤)

如果你的文档只在 Bitbucket 上用,记得加上这个前缀。

中文标题的处理

中文标题在 GitHub 和 GitLab 上一般直接使用 URL 编码后的形式。不过实测下来,直接在链接里写中文也能跳转:

## 目录

- [快速开始](#快速开始)
- [配置说明](#配置说明)

## 快速开始

## 配置说明

大部分现代 Markdown 渲染器都支持中文锚点直接跳转。但如果你发现某些平台不支持,可以用 HTML 的 <a> 标签手动加锚点:

<a name="快速开始"></a>

## 快速开始

然后在目录里链接到这个锚点:

- [快速开始](#快速开始)

手动创建 Markdown 目录

如果你文档不长,或者标题很少变动,手动写目录是最直接的方式。

基本步骤

  1. 先把文档的所有标题写好
  2. 在文档开头(或你想放目录的位置)添加一个"目录"章节
  3. 用列表 + 链接语法,逐个指向各个标题

## 目录

- [功能介绍](#功能介绍)
- [安装](#安装)
  - [npm 安装](#npm-安装)
  - [手动安装](#手动安装)
- [使用](#使用)
- [许可证](#许可证)

## 功能介绍

...

## 安装

...

### npm 安装

...

### 手动安装

...

## 使用

...

## 许可证

...

嵌套的标题用缩进来表示层级关系,这样目录结构和文档结构就能对上。

说实话,手动写目录的问题不在于写,而在于维护——每次改了标题,都得记得去目录里同步更新。所以对于经常变动的长文档,我更建议用自动生成工具。

使用 VS Code 插件自动生成目录

如果你用 VS Code 写 Markdown(很多人都是),装个插件就能一键生成目录,而且还能在保存时自动更新。

Markdown All in One

这是最推荐的 VS Code Markdown 插件,由 Yu Zhang 开发,在 VS Code Marketplace 上安装量超过 600 万。

安装和使用:

  1. 在 VS Code 扩展商店搜索 Markdown All in One 并安装
  2. 打开你的 .md 文件,把光标放在想插入目录的位置
  3. Ctrl+Shift+P 打开命令面板,输入 Markdown: Create Table of Contents
  4. 目录就自动生成了

生成出来的目录就是普通的 Markdown 列表,格式和手动写的一模一样,所以放到 GitHub、GitLab 上都能正常显示。

自动更新功能:

默认情况下,每次保存文件时插件会自动更新目录。如果你不想自动更新(比如目录位置有自定义内容),可以在 VS Code 设置里关掉:

{
  "markdown.extension.toc.updateOnSave": false
}

我用了这个插件快两年了,总体很顺手。有一点要注意:如果你文档里有 HTML 标签混着 Markdown 标题,插件有时候识别会出偏差,这种情况需要手动检查一下生成的目录。

Auto Markdown TOC

另一个选择是 Auto Markdown TOC 插件(作者 huntertran),功能类似但更轻量。安装后右键菜单里会出现 "Markdown TOC" 选项,操作也很直观。

命令行工具自动生成 TOC

如果你需要在 CI/CD 环境里批量处理,或者不想依赖特定编辑器,命令行工具是更好的选择。

doctoc — 最流行的 TOC 生成工具

doctoc 是 Node.js 写的工具,GitHub 上超过 4000 star,是最广泛使用的 Markdown TOC 生成器。

# 安装
npm install -g doctoc

# 为当前目录下所有 Markdown 文件生成 TOC
doctoc .

# 只处理单个文件
doctoc README.md

doctoc 会在文件中插入特殊的注释标记,之后再次运行 doctoc 时会自动更新已有的 TOC,不会重复插入:

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**

- [简介](#简介)
- [安装](#安装)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

这个标记机制很聪明——你不该手动编辑 TOC 内容,改了标题直接重新跑一遍 doctoc 就行。

markdown-toc — 灵活的 Node.js 工具

另一个 Node.js 工具,由 jonschlinkert 开发,被 NASA、Prisma、Mocha、Prettier 等知名项目使用。

# 安装
npm install -g markdown-toc

# 生成 TOC 并输出到终端
markdown-toc README.md

# 保存到文件
markdown-toc README.md > TOC.md

和 doctoc 不同,markdown-toc 默认输出到标准输出,你可以灵活决定怎么使用它——粘贴到文件里也好,管道到其他工具也好。

gh-md-toc — 专为 GitHub 优化的 Shell 工具

如果你不想装 Node.js,这个纯 Shell 脚本就够用了。

# 下载(Linux)
wget https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc
chmod a+x gh-md-toc

# 下载(macOS)
curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc
chmod a+x gh-md-toc

# 使用
./gh-md-toc README.md

它还能处理远程 GitHub 上的文件,甚至 GitHub Wiki 页面:

./gh-md-toc https://github.com/user/repo/blob/main/README.md

自动插入模式下,在文件里放上标记:

<!--ts-->
<!--te-->

然后运行 ./gh-md-toc --insert README.md,TOC 就会被插入到这两个标记之间。之后每次改了标题,再跑一次命令就行。

md-toc — Python 用户的工具

如果你更习惯 Python 生态,md-toc 是一个功能完善的选择:

pip install md-toc
md_toc --in-place github --header-levels 6 README.md

它支持 GitHub 和 GitLab 两种锚点生成模式,还能控制显示的标题层级。

在线 TOC 生成工具

不想装任何工具的话,在线生成器最方便:

  • BitDownToc(bitdowntoc.derlin.ch):支持 GitHub、GitLab、dev.to、hashnode 等多种平台 profile,粘贴 Markdown 内容就能生成对应平台的 TOC。开源免费。

工具对比:选哪个?

工具类型支持平台自动更新依赖
手动编写全部
Markdown All in OneVS Code 插件全部保存时自动更新VS Code
Auto Markdown TOCVS Code 插件全部手动触发VS Code
doctocCLIGitHub/GitLab重新运行即更新Node.js
markdown-tocCLIGitHub输出到 stdoutNode.js
gh-md-tocShell 脚本GitHub标记间自动更新curl/wget
md-tocCLIGitHub/GitLab标记间自动更新Python
BitDownToc在线工具多平台手动复制浏览器
GitLab [[_TOC_]]平台原生仅 GitLab自动

选工具的建议:

  • 写 GitHub README,用 VS Code 插件或者 doctoc 就够了
  • 只用 GitLab,直接用 [[_TOC_]],最省事
  • 需要在 CI 里自动化,用命令行工具配合脚本
  • 偶尔用一次,在线工具最方便

用 GitHub Actions 自动维护 TOC

如果你有一个文档仓库,不想每次改标题后手动跑工具更新 TOC,可以用 GitHub Actions 实现全自动化。

下面这个配置会在你推送 Markdown 文件时自动更新 TOC:

name: Update TOC

on:
  push:
    branches: [main]
    paths: ['**/*.md']

jobs:
  toc:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v4
      - run: |
          curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc
          chmod a+x gh-md-toc
          ./gh-md-toc --insert --no-backup README.md
          rm gh-md-toc
      - uses: stefanzweifel/git-auto-commit-action@v4
        with:
          commit_message: "Auto update markdown TOC"

这样每次你 push 代码,Actions 会自动检查 Markdown 文件的 TOC 是否需要更新,如果需要就自动 commit 新版本。

重复标题和特殊字符的处理

实际写文档时,经常会遇到一些边缘情况。

重复标题

如果一个文档里有两个相同的标题,GitHub 会自动给第二个加上编号:

## 示例

这是第一个示例...

## 示例

这是第二个示例...

对应的锚点分别是 #示例#示例-1。在目录里链接时要注意这点:

- [示例(第一个)](#示例)
- [示例(第二个)](#示例-1)

特殊字符

标题里有 $()? 等特殊字符时,GitHub 会把它们去掉:

## 版本 2.0 (重大更新)

锚点是 #版本-20-重大更新,注意点号被保留了,但括号没了。

重复标题在不同平台的表现

平台重复标题处理
GitHub第二个加 -1,第三个加 -2,以此类推
GitLab同样加编号后缀
Bitbucket同样加编号后缀,但前缀是 markdown-header-

如果不确定某个标题生成的锚点是什么,GitHub 上有个小技巧:把鼠标悬停在标题上,会出现一个链接图标,点一下就能看到实际的锚点了。

常见问题

GitHub README 支持 [TOC] 标签吗?

不支持。GitHub 不认识 [TOC] 这个标记(不像一些 Markdown 编辑器那样)。在 GitHub 上你只能用上面介绍的方法来创建目录——要么手动写,要么用工具生成。

为什么我的目录链接跳转不到对应标题?

最常见的原因是锚点格式不对。检查一下:

  1. 锚点是不是全小写了
  2. 空格是不是换成了短横线
  3. 有没有多余的标点符号没去掉
  4. 是不是 Bitbucket 环境忘了加 markdown-header- 前缀

怎么让目录只显示特定层级的标题?

用 VS Code 的 Markdown All in One 插件,可以在设置里控制:

{
  "markdown.extension.toc.levels": "2..4"
}

这表示只显示 H2 到 H4 的标题。用命令行工具的话,doctoc 用 --maxlevel 参数,md-toc 用 --header-levels 参数。

GitHub 自动生成的目录和手写的有什么区别?

2021 年 4 月起,GitHub 在 Markdown 文件的头部自动显示一个"大纲"(Outline)菜单,列出所有标题。这个功能是 GitHub 前端自动生成的,不需要你在文件里写任何东西。但它只出现在 GitHub 网页上,不影响你的 Markdown 文件内容。

所以说,如果你在 GitHub 上写文档,文件里手动或工具生成的 TOC 是给所有场景用的(包括 clone 到本地看),GitHub 自带的 Outline 是只在网页上多一层导航。

参考来源