NextJS, MDX and ToC
January 23, 2023For one of my NextJS projects, I wanted to create a nice FAQ page. As I planned to have quite a lot of content there, I thought it would be a good idea to have a Table of Contents (TOC) section to make the page easier to navigate, and that's what this post is about.
I will focus on NextJS but a very similar approach should be possible wherever you use MDX or directly remark/rehype.
Technologies used:
- NextJS 13
- MDX and @next/mdx for markdown support
- remark/rehype plugins for generating the Table of Contents
You can find the full source code at https://github.com/OndrejNepozitek/nextjs-mdx-toc and the website is also live at https://nextjs-mdx-toc.netlify.app/.
Table of Contents
- Basic project setup
- next.config, remark and ES modules
- TOC with
remark-toc
- Heading ids with
rehype-slug
- Heading links with
rehype-autolink-headings
- Conclusion
Basic project setup
I created an empty NextJS project with Typescript support based on this guide. Then, I used this guide to add MDX support. According to the guide, you have to:
- Install the required packages:
npm install @next/mdx @mdx-js/loader @mdx-js/react
- Modify the
next.config.js
so it looks like this:
After that, you should be able to have .mdx
pages in your pages
directory. For example, if you create a example.mdx
file and add some markdown inside, you should be able to navigate to <root url>/example
and see the markdown converted to HTML.
next.config, remark and ES modules
In order to generate the Table of Contents section, we need to register a remark plugin in the @next/mdx
library. remark and rehype are tools that make it possible to transform markdown to HTML with the use of various plugins.
The problem is that there remark and rehype plugins now mostly use ES modules (or ECMAScript modules, ESM) while the next.config.js
file uses CommonJS modules. Fortunately, it is relatively easy to switch from CommonJS to ESM. First, start by renaming the next.config.js
to next.config.mjs
. Next, change the config like this:
Note how we use import()
instead of require()
and the module.exports
line was changed to export default
.
If you now restart the project, you shouldn't see any errors and the website should work exactly the same as previously.
TOC with remark-toc
remark-toc is a remark plugin that can generate the Table of Contents section. It can take a markdown file like the following one:
# Alpha## Table of contents## Bravo### Charlie## Delta
and transforms the Markdown like this:
# Alpha## Table of contents* [Bravo](#bravo) * [Charlie](#charlie)* [Delta](#delta)## Bravo### Charlie## Delta
First, install the remark-toc plugin.
npm install remark-toc
Then, register the plugin in @next/mdx
in the remarkPlugins
config section:
If you restart your project now, you should see the Table of Contents being generated. If it doesn't generate, make sure that you have a heading called Table of contents somewhere in your markdown as that's what the remark-toc plugin looks for when generating the TOC.
Heading ids with rehype-slug
We can now generate the Table of Contents automatically, but if you try clicking on the links, they actually don't work. The reason is the remark-toc
plugin only generates the TOC but we also need to add ids to the headings. The rehype-slug plugin can do exactly that.
First, install the plugin:
npm install rehype-slug
Next, add the plugin to the rehypePlugins
section of the @next/mdx
configuration:
Without the plugin, the HTML output would look like this:
<h1>Alpha</h1><h2>Table of contents</h2><ul> <li> <p> <a href="#bravo">Bravo</a> </p> <ul> <li> <a href="#charlie">Charlie</a> </li> </ul> </li> <li> <p> <a href="#delta">Delta</a> </p> </li></ul><h2>Bravo</h2><h3>Charlie</h3><h2>Delta</h2>
With the plugin, the outputs looks like this and the links should be fully functional:
<h1 id="alpha">Alpha</h1><h2 id="table-of-contents">Table of contents</h2><ul> <li> <p> <a href="#bravo">Bravo</a> </p> <ul> <li> <a href="#charlie">Charlie</a> </li> </ul> </li> <li> <p> <a href="#delta">Delta</a> </p> </li></ul><h2 id="bravo">Bravo</h2><h3 id="charlie">Charlie</h3><h2 id="delta">Delta</h2>
Heading links with rehype-autolink-headings
The last thing I want to show you is how to add links from headings back to themselves. This features comes in handy if you have a lot of headings on your page and want to share a link to a specific heading or section of the page. The rehype-autolink-headings plugin does exactly that.
First, install the plugin:
npm install rehype-autolink-headings
Next, register the plugin in the @next/mdx
configuration:
Note that I used a slightly different syntax for registering the plugin. That's because each of these remark/rehype plugins comes with some configuration options and this is how you can override the default settings.
If you now restart the website, you won't see any difference. That's because the links are empty by default. You can fix it with a few lines of css:
.hash-link::before { content: '#';}
Conclusion
That's it. Next time you want to generate Table of Contents for your markdown content, you should know how.
You can find the full source code at https://github.com/OndrejNepozitek/nextjs-mdx-toc and the website is also live at https://nextjs-mdx-toc.netlify.app/.
Want to get in touch? See the About for contacts or leave a comment below.