How to Generate Table of Contents From Markdown/Html Text in Next.js

In this tutorial, I'll teach you about a programmatic way to generate and extract 'Table of Contents' HTML snippet from existing markdown text in Next.js

Share:

Jul 27, 2023 552 Words

Read Time: 3 Minutes

How to Generate Table of Contents From Markdown/Html Text in Next.js"

Introduction

I wanted a programmatic way to generate and extract ‘Table of Contents’ HTML snippet from existing markdown text for my Next.js blogging website www.notionworkspaces.com .1

- These are the benefits of this approach:

  1. You don’t have to have a Table of Contents section in all your markdown files or HTML files
  2. You can cut-out/extract (or) keep the table of contents after generating them in the content HTML using cheerio

In this tutorial, I’ll teach you about a programmatic way to generate and extract ‘Table of Contents’ HTML snippet from existing markdown text in Next.js

TLDR; working code snippet here

Original Snippet before modification

I already had a function that converted markdown text to html text using remark2 library.

export async function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`);
  const fileContents = fs.readFileSync(fullPath, 'utf8');

  // Use gray-matter to parse the post metadata section
  const matterResult = matter(fileContents);

  // Use remark to convert markdown into HTML string
  const processedContent = await remark()
    .use(html)
    .process(matterResult.content);
  const contentHtml = processedContent.toString();

  // Combine the data with the id and contentHtml
  return {
    id,
    contentHtml,
    ...matterResult.data,
  };
}

This above snippet was taken from the Next.js’s official getting started tutorial3.

- What I wanted exactly:

But, it didn’t do everything that I wanted. It didn’t generate table of contents based on the structure of markdown data.

I googled around and found an existing library called remark-toc4 but it didn’t do exactly what I wanted.

It required a few conditions that I didn’t want to entertain.

I later stumbled upon rehype5 library a more recent take on processing html (also markdown) in Next.js.

The Working Code Snippet

This is final code I use to generate and extract table of contents from my markdown content.

export async function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`);
  const fileContents = fs.readFileSync(fullPath, 'utf8');

  // Use gray-matter to parse the post metadata section
  const matterResult = matter(fileContents);

  const file = await unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeSlug)
  .use(rehypeDocument)
  .use(rehypeFormat)
  .use(rehypeTOC)
  .use(rehypeStringify)
  .process(matterResult.content)

  // Extract TOC dynamically
  const $ = cheerio.load(String(file));
  const contentTOC = $("nav.toc").html();
  $("nav.toc").remove();
  const contentHtml = $.html();

  // Combine the data with the id and contentHtml
  return {
    id,
    contentHtml,
    contentTOC,
    ...matterResult.data,
  };
}

I used the following imports to get it all working seamlessly,

The import requirements

import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeDocument from 'rehype-document'
import rehypeFormat from 'rehype-format'
import rehypeStringify from 'rehype-stringify'
import rehypeSlug from 'rehype-slug'
import rehypeTOC from "@jsdevtools/rehype-toc";
import * as cheerio from 'cheerio';

I used cheerio6 to build an DOM tree from html text for me to extract the TOC div component using the name nav.tov and use it as a Table of Contents snippet I used in my react components.

This is a screenshot of how I used this piece of code on www.notionworkspaces.com .

A screenshot of https://notionworkspaces.com/ website

The dynamic table of contents section, in left section in the above screenshot.

I hope you found this useful!

👋 – @TnvMadhav

References

Find more posts from following topics

accurate-requests
api-development
api-testing
api-testing-tools
array
automated-testing
bad-habits
base64-decoder
base64-encoder
binding
blog
blogging
bulma-css
bulma.io
button-swiftui
chatgpt
chrome
clipboard
code
code-block
code-snippet
comparison
compile
configuring-debugger-for-django-in-vs-code
configuring-launch.json-for-python-debugger
copy
copy-to-clipboard
copy-to-clipboard-neovim
css
current-date
current-time
current-timestamp
debugger-setup-in-visual-studio-code
debugging-django-app-in-visual-studio-code
debugging-python-code-in-visual-studio-code
debugging-python-programs-with-visual-studio-code
debugging-python-with-virtual-environment-in-vs-code
developer-productivity
developers
development-workflow
dom
dynamic-sitemap-in-nextjs
engineering-dashboard
flowcharts
git
git-diff
github
global-keyboard-shorcut
global-shortcut
go
go-hugo
go-programming
go-to-line
golang
golang-development
good-habits
gorilla-websocket
gpt
gpt-3.5
gpt-4
gpt-4-api
guide
gumroad
habits
habits-tracker-notion-template
hamburger-menu
hotkeys
html
hugo
ide
image
image-sharing
image-tool-for-ios
imagerenderer
include-timestamp
integrated-development-environment
ios
ios-16
ios16
javascript
keyboard-shortcut
linux
macos
map
markdown
markdown-code
mental-programming
menu
menubarextra
mergesort
mermaid-syntax
mistake-tracker-notion
mobile-view
modifier
navbar
navigationlink
navigationstack
neovim
next.js
nextjs
nextjs-markdown
nextjs-sitemap
nextjs-sitemaps
nice-shot
nice-shot-pro
notion
notion-api
notion-api-python
notion-budget
notion-budget-template
notion-budget-tracker
notion-bug-report-tracker
notion-dashboard
notion-expense-manager
notion-habits
notion-habits-dashboard
notion-habits-template
notion-habits-tracker
notion-habits-tracker-template
notion-issue-tracker
notion-mistake-tracker
notion-product
notion-product-dashboard
notion-product-roadmap
notion-product-roadmap-dashboard
notion-tasks
notion-tasks-dashboard
notion-tasks-template
notion-tasks-tracker
notion-template
notionworkspaces
openai
osx
personal-ifttt-framework
photospicker
photospickeritem
phpickerfilter
postman-capabilities
postman-request
pre-request-script
product-roadmap-notion-template
product-roadmap-template
productivity
programming
python
python-api
python-debugger-tutorial-for-vs-code
python-debugging-mode-in-vs-code
python-notion-api
python3
real-time-communication
rehype
remark
request-data
running-debugger-in-visual-studio-code
running-django-app-in-debugging-mode
running-program-in-debugging-mode-in-vs-code
running-python-code-in-debugging-mode
safari
screenshot-app-for-ios
screenshot-app-ios
screenshot-ios
screenshot-tool-for-ios
set-current-timestamp
setting-up-debugger-in-vs-code-for-python
share-extension
sharelink
sharepreview
sharesheet
simple-websocket-server
sitemap
slice
slider
sorting
space-complexity
step-by-step-guide
stocks-profits-tracker
stocks-profits-tracker-template
stocks-tracker
struct
sustained-vigilance
swift
swiftui
swiftui-button
swiftui-button-action
swiftui-button-style
table-of-contents
tasks-tracker-notion-template
textfield-swiftui
til
tim-sort
time-complexity
timeliness
timestamp-integration
timsort
transferable
triggers-and-actions
tutorial
us-stocks
usa-stocks
useful-ios-features
using-breakpoints-in-python-debugger
using-virtual-environment-with-python-debugger
vanilla-javascript
variable
vim
visual-mode
visual-studio-code
vs-code
vscode
vscode-go-to-line
web-sockets-in-go
websocket-client
websocket-programming
websocket-server
xcode