Next.js中如何生成独立的Markdown格式目录?
独立生成Markdown目录的优化方案
你的现有函数可以正常工作,但基于Markdown AST(抽象语法树)的实现会更稳定、易维护,且能避免依赖渲染组件的结构细节。以下是两种利用remark生态工具的优化方案:
方案一:快速提取标准目录
借助unified、remark-parse、remark-slug和mdast-util-toc工具,直接解析Markdown并提取目录结构,无需手动处理层级和ID生成。
步骤1:安装依赖
npm install unified remark-parse remark-slug mdast-util-toc mdast-util-to-markdown
步骤2:实现提取函数
import { unified } from 'unified' import remarkParse from 'remark-parse' import remarkSlug from 'remark-slug' import { toc } from 'mdast-util-toc' import { toMarkdown } from 'mdast-util-to-markdown' import fs from 'fs' import matter from 'gray-matter' const getMarkdownTOC = (slug) => { const folder = process.env.PATH_ARTICULOS const file = `${folder}${slug}.md` const content = fs.readFileSync(file, 'utf8') const matterResult = matter(content) // 解析Markdown为AST,并自动生成标题ID const tree = unified() .use(remarkParse) .use(remarkSlug) .parse(matterResult.content) // 提取目录结构,不生成目录标题 const tocResult = toc(tree, { heading: false, maxDepth: 6 // 可按需调整要包含的标题层级 }) // 无标题时返回空字符串 if (!tocResult.map) return '' // 将目录AST转换为Markdown字符串 return toMarkdown(tocResult.map) }
方案二:自定义目录格式
如果需要完全自定义目录的列表符号、缩进规则或处理富文本标题,可以用unist-util-visit手动遍历AST节点:
步骤1:安装额外依赖
npm install unified remark-parse remark-slug unist-util-visit
步骤2:实现自定义提取函数
import { unified } from 'unified' import remarkParse from 'remark-parse' import remarkSlug from 'remark-slug' import { visit } from 'unist-util-visit' import fs from 'fs' import matter from 'gray-matter' const getMarkdownTOC = (slug) => { const folder = process.env.PATH_ARTICULOS const file = `${folder}${slug}.md` const content = fs.readFileSync(file, 'utf8') const matterResult = matter(content) const tree = unified() .use(remarkParse) .use(remarkSlug) .parse(matterResult.content) const tocItems = [] // 遍历所有标题节点,收集层级、文本和ID visit(tree, 'heading', (node) => { const level = node.depth // 处理标题中的富文本节点(如加粗、斜体),提取纯文本 const title = node.children.map(child => { if (child.type === 'text') return child.value if (['strong', 'emphasis'].includes(child.type)) return child.children[0].value // 可根据需求扩展其他节点类型的处理 return '' }).join('') // 获取由remark-slug生成的标题ID const id = node.data?.hProperties?.id if (id) tocItems.push({ level, title, id }) }) // 生成带缩进的Markdown列表 let toc = '' const indent = ' ' tocItems.forEach(item => { toc += `${indent.repeat(item.level - 1)}- [${item.title}](#${item.id})\n` }) return toc.trim() }
方案优势
- 相比你原有的遍历渲染组件结构的方式,基于AST的实现更稳定,不依赖编译器的输出结构,维护性更强。
- 利用remark生态工具,无需手动处理层级缩进、ID生成等细节,减少重复代码。
- 两种方案都能独立生成目录内容,完全脱离原文档,满足你在任意位置渲染的需求。
内容的提问来源于stack exchange,提问作者Joan Lara
相关产品推荐
相关产品推荐

