Add Table of Contents to Blog with Sanity and Next.js

14 replies
Last updated: Aug 12, 2021
help 👌Hello, please does anyone have an idea about how I can add ✔️`table of content`✔️ to my blog with
sanity
and
NextJs
.Something like this.
AI Update

Creating a table of contents for your blog posts with Sanity and Next.js involves extracting heading blocks from your Portable Text content and rendering them as navigation links. Here's how to approach it:

The Basic Approach

The key is to process your Portable Text content twice: once to extract headings for the table of contents, and once to render the actual content. There's a helpful CodeSandbox example shared in the Sanity community that demonstrates this pattern.

Implementation Steps

1. Extract headings from your Portable Text:

Create a function that filters through your Portable Text blocks to find heading blocks (h2, h3, etc.):

function getHeadings(portableTextContent) {
  return portableTextContent
    .filter(block => block.style && block.style.match(/^h\d/))
    .map(heading => ({
      style: heading.style,
      text: heading.children[0].text,
      _key: heading._key
    }));
}

2. Render the table of contents:

Use the extracted headings to create your navigation:

function TableOfContents({ headings }) {
  return (
    <nav>
      <ul>
        {headings.map(heading => (
          <li key={heading._key}>
            <a href={`#${heading._key}`}>
              {heading.text}
            </a>
          </li>
        ))}
      </ul>
    </nav>
  );
}

3. Add IDs to your rendered headings:

When rendering your main content with @portabletext/react, customize the heading serializers to include IDs that match your table of contents links:

import { PortableText } from '@portabletext/react';

const components = {
  block: {
    h2: ({value, children}) => (
      <h2 id={value._key}>{children}</h2>
    ),
    h3: ({value, children}) => (
      <h3 id={value._key}>{children}</h3>
    ),
    // Add other heading levels as needed
  }
};

<PortableText value={post.body} components={components} />

Complete Example

export default function BlogPost({ post }) {
  const headings = getHeadings(post.body);
  
  return (
    <article>
      <TableOfContents headings={headings} />
      <PortableText 
        value={post.body} 
        components={components} 
      />
    </article>
  );
}

Styling Tips

You can enhance your table of contents with indentation based on heading levels:

nav ul {
  list-style: none;
}

nav li[data-level="h3"] {
  padding-left: 1rem;
}

nav li[data-level="h4"] {
  padding-left: 2rem;
}

The beauty of this approach is that it leverages Portable Text's structured nature - each block has a unique _key that you can use for linking, and the heading styles are explicitly defined in the data structure, making extraction straightforward. No additional plugins needed!

Show original thread
14 replies
Thanks so much
user Y
Please how do I exclude the bullet points from the table of content
I think you can filter on
level == 1
Or maybe I’m misunderstanding? Maybe you want to wrap it in
<ol>
 instead?
No I have these bullet points I created in sanity studio and they are also appearing below the
You might like this
No, I created some bullet points in sanity studio and they are also showing. That's below the
You might like this
I don’t have enough context to be helpful here. How does the JSON document of this post look like?

    {
      "_key": "a0392cd68b8d",
      "_type": "block",
      "children": [
        {
          "_key": "917c006759dc",
          "_type": "span",
          "marks": [
            "d84dd0a78456"
          ],
          "text": "Sidebar Menu with html css"
        }
      ],
      "level": 1,
      "listItem": "bullet",
      "markDefs": [
        {
          "_key": "d84dd0a78456",
          "_type": "internalLink",
          "reference": {
            "_ref": "4ae667f2-5f9d-4b3d-9616-429aa1616867",
            "_type": "reference"
          }
        }
      ],
      "style": "normal"
    },
Sorry I could only copy some part because the whole json is too long and slack won't allow me to send it
I see that the bullets have level set to 1 on them. But I can't find the level property on the node
@knut (he/him) Hi I added some ordered list to your codeSandBox and it was also included in the table of content. Please how do I exclude the ordered list?
Maybe you could fork my codesandbox and insert your JSON document there?
user Y
No, It seems you don't understand what I meant. Your code works correctly but in your case, there weren't any bullet points in your content. Am trying to say that when there is a bullet point in the content it also gets added to the table of content which is not what I wanted. I only want the h1,h2,h3,h4 to show excluding the bullet points.
user Y
Please don't worry I have figured it out. Thanks for helping👌

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?