DEV Community

loading...
Cover image for Table of Content for MDX with Next.js

Table of Content for MDX with Next.js

CY
Frontend developer. Learning to be a full-stack developer. ♥️ React, GraphQL & Prisma
Originally published at cyishere.dev ・1 min read

I use Next.js & MDX for my personal website and some documents, I want to display a "table of content", a list of headings, for each article.

I've tried the plugin remark-toc but it's too complex (for me). I spend hours still couldn't make the toc work... So, I tried to write it by myself.

Get The List of Headings

Here's the code for getting the list of headings:

// ./components/PostLayout.js

import { renderToString } from "react-dom/server";
import { MDXProvider } from "@mdx-js/react";

import MDXComponents from "./MDXComponents";

const PostLayout = ({ children }) => {
  const contentString = renderToString(children);

  const getHeadings = (source) => {
    const regex = /<h2>(.*?)<\/h2>/g;

    if (source.match(regex)) {
      return source.match(regex).map((heading) => {
        const headingText = heading.replace("<h2>", "").replace("</h2>", "");

        const link = "#" + headingText.replace(/ /g, "_").toLowerCase();

        return {
          text: headingText,
          link,
        };
      });
    }

    return [];
  };

  const headings = getHeadings(contentString);

  return (
    <>
      {/* ... */}

      {headings.length > 0 ? (
        <ol>
          {headings.map((heading) => (
            <li key={heading.text}>
              <a href={heading.link}>{heading.text}</a>
            </li>
          ))}
        </ol>
      ) : null}

      <MDXProvider components={MDXComponents}>
        <Container>{children}</Container>
      </MDXProvider>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

Add IDs to Headings

I use the MDXProvider to render the Markdown content, then customize the components in a separated file.

Here's the customized h2:

// ./components/MDXComponents.js

const Heading2 = ({ children }) => {
  const idText = children.replace(/ /g, "_").toLowerCase();

  return <h2 id={idText}>{children}</h2>;
};

const MDXComponents = {
  h2: Heading2,
  // ...
};

export default MDXComponents;
Enter fullscreen mode Exit fullscreen mode

Now, everything works as I expected. I made a Next.js & MDX starter, click here to visit the repo could find the whole code.

Discussion (0)