- Published on
 - ยท10 min read
 
Adding Tocbot to a Next.js Blog
I apologize in advance for any awkward expressions in English. ๐
English is not my native language, and I have relied on ChatGPT's assistance to proceed with the translation.
Overview
When reading articles, having a table of contents is quite useful. Moreover, it would be even more convenient if the table of contents remained visible as I scrolled down. To achieve this, I utilized the Tocbot library.
It's worth mentioning that the Tailwind CSS Blog Starter template already includes a table of contents within the content. In addition to this, I used Tocbot to add a fixed table of contents to the right side of the page.
In summary:
- On screens with a width of 
1280px or more, the table of contents within the content is hidden, and a fixed table of contents appears on the right side. - On screens with a width of 
less than 1280px, the table of contents within the content is visible, while the fixed table of contents on the right side is hidden. 
The image below illustrates the left side showing the table of contents for screens 1280 pixels or more and the right side displaying the screen less than 1280 pixels.
Add Tocbot
Tocbot Install
npm install --save tocbot
Adding the Tocbot Component
Add /components/Tocbot.js with the following content:
import { useEffect } from 'react'
import tocbot from 'tocbot'
const TocSide = () => {
  useEffect(() => {
    tocbot.init({
      tocSelector: '.toc',
      contentSelector: 'article',
      headingSelector: 'h2, h3',
      ignoreSelector: '.toc-ignore',
    })
    return () => tocbot.destroy()
  }, [])
  return (
    <div>
      <div className="lg-block hidden pt-6 pb-10 text-gray-500 dark:text-gray-400 xl:border-b xl:border-gray-200 xl:pt-11 xl:dark:border-gray-700">
        <span className="font-bold text-gray-600  dark:text-gray-300">Table of Contents</span>
        <div className="toc"></div>
      </div>
    </div>
  )
}
export default TocSide
- Set it to 
hiddenby default and uselg:blockto make it visible on screens larger than 1280px. - Define 
lg:blockin the tocbot.css file below. 
Adding Tocbot CSS
I have imported the basic CSS from https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.18.2/tocbot.css. In addition to that, I made a few modifications.
Add
/css/tocbot.csswith the following content:
.toc {
  overflow-y: auto;
}
.toc > .toc-list {
  overflow: hidden;
  position: relative;
  font-size: 0.9em;
  margin-top: 16px;
}
.toc > .toc-list li {
  list-style: none;
  line-height: 2em;
}
.js-toc {
  overflow-y: hidden;
}
.toc-list {
  margin: 0;
  padding-left: 10px;
}
a.toc-link {
  /*color:currentColor;*/
  height: 100%;
}
.is-collapsible {
  max-height: 1000px;
  overflow: hidden;
  transition: all 300ms ease-in-out;
}
.is-collapsed {
  max-height: 0;
}
.is-position-fixed {
  position: fixed !important;
  top: 0;
}
.is-active-link {
  font-weight: 700;
  color: #37c3b3;
}
.toc-link::before {
  background-color: #e5e5e5;
  content: ' ';
  display: inline-block;
  height: inherit;
  left: 0;
  margin-top: -1px;
  position: absolute;
  width: 2px;
}
.is-active-link::before {
  background-color: #37c3b3;
}
/*
.toc-link:hover {
  color: white;
}
*/
@media (min-width: 1280px) {
  .lg-block {
    display: block;
  }
}
@media (max-width: 1280px) {
  .sm-show {
    display: block;
  }
}
.lg-blockclass to display it on screens with a width of 1280px or more..sm-showclass to display it on screens with a width of less than 1280px.
Adding a Layout to Display Tocbot on the Right Side
- Added 
/layouts/PostToc.jswith the following content: 
import PageTitle from '@/components/PageTitle'
import SectionContainer from '@/components/SectionContainer'
import { BlogSEO } from '@/components/SEO'
import Tag from '@/components/Tag'
import siteMetadata from '@/data/siteMetadata'
import Comments from '@/components/comments'
import ScrollTopAndComment from '@/components/ScrollTopAndComment'
import TocSide from '@/components/Tocbot'
const postDateTemplate = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }
export default function PostLayout({ frontMatter, authorDetails, children }) {
  const { slug, date, title, tags } = frontMatter
  return (
    <SectionContainer>
      <BlogSEO
        url={`${siteMetadata.siteUrl}/blog/${slug}`}
        authorDetails={authorDetails}
        {...frontMatter}
      />
      <ScrollTopAndComment />
      <article>
        <div className="xl:divide-y xl:divide-gray-200 xl:dark:divide-gray-700">
          <header className="pt-6 xl:pb-6">
            <div className="space-y-1 text-center">
              <dl className="space-y-10">
                <div>
                  <dt className="sr-only">Published on</dt>
                  <dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400">
                    <time dateTime={date}>
                      {new Date(date).toLocaleDateString(siteMetadata.locale, postDateTemplate)}
                    </time>
                  </dd>
                </div>
              </dl>
              <div>
                <PageTitle>{title}</PageTitle>
              </div>
            </div>
          </header>
          <div
            className="divide-y divide-gray-200 pb-8 dark:divide-gray-700 xl:grid xl:grid-cols-4 xl:gap-x-6 xl:divide-y-0"
            style={{ gridTemplateRows: 'auto 1fr' }}
          >
            <div className="divide-y divide-gray-200 dark:divide-gray-700 xl:col-span-3 xl:row-span-2 xl:pb-0">
              <div className="prose max-w-none pt-10 pb-8 dark:prose-dark">{children}</div>
              <Comments frontMatter={frontMatter} />
            </div>
            <footer style={{ position: 'sticky', top: '32px' }}>
              <TocSide />
              <div className="divide-gray-200 text-sm font-medium leading-5 dark:divide-gray-700 xl:col-start-1 xl:row-start-2 xl:divide-y">
                {tags && (
                  <div className="py-4 xl:py-8">
                    <h2 className="toc-ignore text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
                      Tags
                    </h2>
                    <div className="flex flex-wrap">
                      {tags.map((tag) => (
                        <Tag key={tag} text={tag} />
                      ))}
                    </div>
                  </div>
                )}
              </div>
            </footer>
          </div>
        </div>
      </article>
    </SectionContainer>
  )
}
Modifications for Applying Tocbot
Editing the TocInline Component
Modify the default TocInline component found in the Tailwind CSS Blog template.
- Add 
className="sm-show hidden"to thedetailstag.
(This ensures it's only visible on screens smaller than 1280px.) - This change will apply when using 
asDisclosure. Otherwise, you can apply it to the ul tag as needed. 
<details open className="sm-show hidden">
  <summary className="ml-6 pt-2 pb-2 text-xl font-bold">Table of Contents</summary>
  <div className="ml-6">{tocList}</div>
</details>
Apply CSS Modifications
- Import 
tocbot.cssin/pages/_app.jsto ensure the CSS modifications are applied. 
import '@/css/tocbot.css'
Apply Layout When Writing Posts
To apply the layout to individual posts, add the layout property to the front matter of each post's markdown file:
---
...omitted...
layout: PostToc
---
If you want to apply this layout as the default for all posts, you can modify the DEFAULT_LAYOUT in the /pages/blog/[...slug].js file.
const DEFAULT_LAYOUT = 'PostToc'
When adding a layout file, if you encounter module not found errors, try stopping and restarting the development server (
npm start).To display the table of contents within the content, you need to add the
<TocInline />component separately in the content. It's not mandatory, but if it's not added, the table of contents won't be visible on screens smaller than 1280px.
<TOCInline toc={props.toc} exclude="Overview" asDisclosure />
Conclusion
I have removed sections such as next/previous posts and author information, which I considered unnecessary.
You can refer to PostLayout to add any necessary content.
You can review the code on the add_tocbot branch or by checking the commit history on GitHub.