I use Next.js & MDX for my personal website and some documents, I want to display a "table of contents", 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>
</>
);
};
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;
Now, everything works as I expected. I made a Next.js & MDX starter, click here to visit the repo could find the whole code.