Logo
Published on
·9 min read

Next.js 블로그에 Tocbot 추가하기

개요

글을 읽을 때 목차가 있으면 좋습니다. 그리고 목차가 계속 옆에 보이면 더 좋겠다고 생각했습니다.

그래서 Tocbot 라이브러리를 이용하여 목차가 따라다닐 수 있도록 추가했습니다.

참고로 Tailwind CSS Blog Starter 템플릿에는 컨텐츠 내에 보여지는 목차가 있습니다.
이와는 별도로 Tocbot을 이용하여 우측에 고정된 목차를 추가하였습니다.

최종적으로,

  • 1280px 이상의 화면에서는 컨텐츠 내에 보여지는 목차가 숨겨지고, 우측에 고정된 목차가 보여집니다.
  • 1280px 미만의 화면에서는 컨텐츠 내에 보여지는 목차가 보여지고, 우측에 고정된 목차는 숨겨집니다.

아래 이미지에서 좌측에 보여지는 목차는 1280 이상일 때, 우측에는 1280 미만일때입니다.

해상도별 목차

Tocbot 추가

Tocbot Install

npm install --save tocbot

Tocbot 컴포넌트 추가

/components/Tocbot.js 추가: 내용은 아래와 같습니다.

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
  • hidden으로 기본은 숨김이고, lg-block으로 1280px 이상의 화면에서 보여지도록 합니다.
  • lg-block는 아래 tocbot.css에서 정의합니다.

Tocbot CSS 추가

.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-block으로 1280px 이상의 화면에서 보여지도록 합니다.
  • .sm-show으로 1280px 미만의 화면에서 보여지도록 합니다.

Tocbot을 우측에 보여줄 Layout 추가

  • /layouts/PostToc.js 추가: 내용은 아래와 같습니다.
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>
  )
}

Tocbot 적용 위한 수정

TocInline 컴포넌트 수정

Tailwind CSS Blog 템플릿에 있는 기본 TocInline 컴포넌트를 수정합니다.

  • detail 태그에 className="sm-show hidden"을 추가합니다.
    (1280px 미만의 화면에서만 보여지도록 합니다.)
  • asDisclosure 로 사용할 경우 적용되며, 그렇지 않을 경우 ul 태그에 적용하시면 될겁니다.
<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>

css 적용되도록 수정

  • /pages/\_app.jstocbot.css import
import '@/css/tocbot.css'

글 작성시 레이아웃 적용

글 마다 적용하려면 frontMatter에 layout을 추가하고, 해당 layout을 적용하면 됩니다.

---
...생략...
layout: PostToc
---

또는 모든 글에 기본으로 적용하려면 /pages/blog/[...slug].js 파일의 DEFAULT_LAYOUT을 수정합니다.

const DEFAULT_LAYOUT = 'PostToc'
  • layout 파일 추가의 경우 module을 못 찾아 오류가 납니다. 개발 서버 중지 후 재시작 해줍니다 (npm start)
  • 컨텐츠 내에 보여지는 목차는 따로 <TocInline /> 컴포넌트를 내용에 추가해주어야 합니다. 없어도 무방합니다. 다만 1280px 미만의 화면에서는 목차가 보이지 않을 뿐입니다.
<TOCInline toc={props.toc} exclude="Overview" asDisclosure />

마무리

다음글, 이전글, 작성자 정보 등 제 기준에서 불필요 한 부분들은 제거하였습니다. 필요한 내용들은 PostLayout을 참고하여 추가하시면 됩니다.

코드는 add_tocbot 브랜치 또는 commit history로 확인하실 수 있습니다.

모두의 구글 애널리틱스4:GA4로 하는 디지털 마케팅 데이터 분석, 길벗  아무나 쉽게 따라하는 블로그 마케팅:검색 상위노출을 위한 블로그 마케팅의 모든 것, 페이스메이커  트래픽을 쓸어 담는 검색엔진 최적화:검색엔진이 가장 좋아하는 사이트 만들기, e비즈북스  타입스크립트 리액트 Next.js로 배우는 실전 웹 애플리케이션 개발, 위키북스
(위 링크는 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.)