You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Next.js中解析MDX文件并自动生成标题锚点链接

在Next.js中自动为MDX标题添加锚点并生成目录链接

方法一:自定义MDX组件+React上下文实现

适合客户端动态收集标题的场景,步骤如下:

  1. 创建标题收集上下文
// components/TitleContext.js
import { createContext, useContext, useState } from 'react';

const TitleContext = createContext({
  titles: [],
  addTitle: () => {}
});

export function TitleProvider({ children }) {
  const [titles, setTitles] = useState([]);
  const addTitle = (text, id) => setTitles(prev => [...prev, { text, id }]);
  
  return (
    <TitleContext.Provider value={{ titles, addTitle }}>
      {children}
    </TitleContext.Provider>
  );
}

export function useTitles() {
  return useContext(TitleContext);
}
  1. 自定义H2标题组件
    自动生成递增ID,并将标题信息存入上下文:
// components/H2.js
import { useTitles, useRef } from 'react';

export function H2({ children }) {
  const { addTitle } = useTitles();
  const countRef = useRef(1);
  const id = `heading-${countRef.current}`;
  
  countRef.current++;
  addTitle(children, id);
  
  return <h2 id={id}>{children}</h2>;
}
  1. 配置MDXProvider
    在_app.js里注册自定义组件并包裹上下文:
// pages/_app.js
import { MDXProvider } from '@mdx-js/react';
import { TitleProvider } from '../components/TitleContext';
import { H2 } from '../components/H2';

const components = { h2: H2 };

export default function MyApp({ Component, pageProps }) {
  return (
    <MDXProvider components={components}>
      <TitleProvider>
        <Component {...pageProps} />
      </TitleProvider>
    </MDXProvider>
  );
}
  1. 在页面中渲染目录与MDX内容
// pages/posts/[slug].js
import { useTitles } from '../../components/TitleContext';
import MyPost from './my-post.mdx';

export default function PostPage() {
  const { titles } = useTitles();
  
  return (
    <div className="container">
      {/* 自动生成的锚点目录 */}
      <nav className="toc">
        {titles.map((title, idx) => (
          <a key={idx} href={`#${title.id}`}>{title.text}</a>
        ))}
      </nav>
      {/* MDX内容 */}
      <MyPost />
    </div>
  );
}

方法二:Remark插件服务端解析处理

适合静态生成页面,提前在服务端收集标题并生成ID,性能更优:

  1. 编写自定义Remark插件
    同时完成标题ID生成和标题信息收集:
// lib/remarkCollectTitles.js
import { visit } from 'unist-util-visit';

export default function remarkCollectTitles() {
  return (tree, file) => {
    const titles = [];
    let count = 1;
    
    visit(tree, 'heading', (node) => {
      // 仅处理H2标题,可修改depth值适配H1-H6
      if (node.depth === 2) {
        const text = node.children.map(child => child.value).join('');
        const id = `heading-${count}`;
        
        // 给标题节点添加ID属性
        node.data = node.data || {};
        node.data.hProperties = node.data.hProperties || {};
        node.data.hProperties.id = id;
        
        // 收集标题信息
        titles.push({ text, id });
        count++;
      }
    });
    
    // 将标题列表存入文件元数据
    file.data.titles = titles;
  };
}
  1. 配置Next.js与MDX
    在next.config.js中启用MDX并加载插件:
// next.config.js
const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/,
  options: {
    remarkPlugins: [require('./lib/remarkCollectTitles')],
    rehypePlugins: []
  }
});

module.exports = withMDX({
  pageExtensions: ['js', 'jsx', 'md', 'mdx']
});
  1. 在页面中获取标题并渲染
    使用next-mdx-remote在服务端解析MDX并传递标题列表:
// pages/posts/[slug].js
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { MDXRemote } from 'next-mdx-remote';
import { serialize } from 'next-mdx-remote/serialize';
import remarkCollectTitles from '../../lib/remarkCollectTitles';

export async function getStaticProps() {
  // 读取MDX文件
  const filePath = path.join(process.cwd(), 'pages', 'posts', 'my-post.mdx');
  const fileContent = fs.readFileSync(filePath, 'utf8');
  const { content } = matter(fileContent);
  
  // 序列化MDX并获取标题列表
  const mdxSource = await serialize(content, {
    mdxOptions: { remarkPlugins: [remarkCollectTitles] }
  });
  const titles = mdxSource.data.titles;
  
  return { props: { mdxSource, titles } };
}

export default function PostPage({ mdxSource, titles }) {
  return (
    <div className="container">
      <nav className="toc">
        {titles.map((title, idx) => (
          <a key={idx} href={`#${title.id}`}>{title.text}</a>
        ))}
      </nav>
      <MDXRemote {...mdxSource} />
    </div>
  );
}

两种方法均可自动完成:

  • 给所有H2标题添加heading-1、heading-2格式的ID
  • 生成对应锚点链接的目录列表

内容的提问来源于stack exchange,提问作者Evanss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 12:54:21