アイキャッチの画像

【Next.js 15 & Tailwind CSS】 Tocbot でブログに目次を作る

公開日:

更新日:

デモ 完成イメージ

本サイトで使用している目次機能を紹介します。↓

ポイント

・スクロールで目次部分まで来たら 画面上部に固定されます。(Tailwindのsticky機能)

・URLを読み取り、記事ページでない場合は非表示になります。

・h1,h2などのタブを自動で読み取り目次を作ります。

Tocbotというライブラリを使用しています。

ディレクトリ構成&開発環境

フォルダ構成は下記の形になります。↓

フォルダ構成図

1src
2└ app
3  ├ components
4  │ ├ TableOfContentsSideBar.tsx
5  │ ├ TableOfContentsSideBar.css
6  │ └ GetWindowSize.tsx
7  ├ layout.tsx
8  └ ページフォルダ
9    └ page.tsx

layout.tsxに目次コンポーネント(TableOfContentsSideBar.tsx)を入れて全ての記事ページで使用する形です。

開発環境

・言語: Typescript


・ライブラリ: React


・フレームワーク: Next.js (バージョン:15.0.3 App Router形式)


・CSSフレームワーク: Tailwind CSS

コード実例

TableOfContentsSideBar.tsx

TableOfContentsSideBar.tsx

1/* eslint-disable */
2"use client";
3
4import { useEffect } from "react";
5import tocbot from "tocbot";
6import "./TableOfContentsSideBar.css";
7import { usePathname } from "next/navigation";
8import { GetWindowSize } from "./GetWindowSize";
9
10const TableOfContentsSideBar = () => {
11  const { height, width } = GetWindowSize();
12  const path = usePathname();
13  const checkPath =
14    path == "/" ||
15    path == "/contact" ||
16    path == "/privacy-policy" ||
17    width <= 768;
18
19  useEffect(() => {
20    tocbot.init({
21      // 目次を表示させる先のクラス名
22      tocSelector: ".js-toc",
23      // 目次を抽出したい要素のクラス名
24      contentSelector: ".js-toc-content",
25      // 目次として抽出する見出しタグ
26      headingSelector: "h2",
27      linkClass: "toc-link",
28    });
29
30    // 不要になったインスタンスを削除
31    return () => tocbot.destroy();
32  }, []);
33
34  useEffect(() => {
35    tocbot.refresh();
36  }, [path]);
37
38  return (
39    <>
40      <div
41        className={
42          checkPath
43            ? "hidden"
44            : "visible  sticky top-5 w-full p-3  bg-white shadow-md rounded text-center  "
45        }
46      >
47        <p className="text-md font-bold">目次</p>
48        <nav className="js-toc text-md text-center p-3" />
49      </div>
50    </>
51  );
52};
53
54export default TableOfContentsSideBar;
55
56

・今回のメインとなる目次のコンポーネントです。

checkPath変数で目次を表示するか判断しています。(一覧ページやお問い合わせページ、スマホ画面では非表示)

GetWindowSizeは画面の横幅を取得するコンポーネントです。(768px以下だとスマホ画面と判断し非表示になります)

useEffectでpathの変更を検知して記事ページが変わるごとに目次内容がリフレッシュされる仕組みです。(34行目部分)

headingSelector: "h2",でh2を指定しているので今回はh2タグのみが目次項目として抽出されます。

TableOfContentsSideBar.css

TableOfContentsSideBar.css

1.js-toc {
2  @apply py-2 pr-3;
3
4  .toc-list .toc-list {
5    @apply border-l-2 border-muted ml-5;
6  }
7
8  .toc-link {
9    @apply flex text-left text-sm w-[100%] py-1.5  relative pl-9 hover:text-foreground text-muted-foreground;
10    &:before {
11      @apply content-[''] w-[10px] h-[10px] border-2 border-card rounded-full top-3 left-4 bg-card absolute;
12    }
13  }
14
15  .is-active-link {
16    @apply font-bold text-foreground;
17    &:before {
18      @apply bg-primary outline-primary shadow-[0_0_0_2px_rgb(var(--primary))];
19    }
20  }
21}
22
23

・Tocbotの見た目を調整しているCSSファイルです。

GetWindowSize.tsx

GetWindowSize.tsx

1import { useEffect, useState } from "react";
2
3export const GetWindowSize = () => {
4  const [windowSize, setWindowSize] = useState({
5    width: 0,
6    height: 0,
7  });
8
9  useEffect(() => {
10    if (typeof window !== "undefined") {
11      const handleResize = () => {
12        setWindowSize({
13          width: window.innerWidth,
14          height: window.innerHeight,
15        });
16      };
17
18      window.addEventListener("resize", handleResize);
19      handleResize();
20      return () => window.removeEventListener("resize", handleResize);
21    } else {
22      return;
23    }
24  }, []);
25  return windowSize;
26};
27
28

・画面幅を取得するコンポーネントです。(今回の実装ではheightは使用しないので削除して頂いても問題ないです)

・下記の記事で紹介されていたものをそのまま使用しています。

Next.js で画面サイズを取得する方法

解説も大変分かりやすく参考になりました。

layout.tsx

layout.tsx

1import Aside from "./components/Aside";
2import TableOfContentsSideBar from "./components/TableOfContentsSideBar";
3import Footer from "./Footer";
4import "./globals.css";
5import Header from "./Header";
6
7export default function RootLayout({
8  children,
9}: Readonly<{
10  children: React.ReactNode;
11}>) {
12  return (
13    <html lang="js">
14      <body>
15        <div className="flex flex-col min-h-screen ">
16          <Header />
17
18          <main className="flex-grow  bg-slate-100 ">
19            <div className="md:flex h-full">
20              <section className="js-toc-content w-full md:w-2/3 flex flex-col items-center px-2 mt-4 pb-5">
21                {children}
22              </section>
23              <div className="md:w-1/3 flex flex-col items-center px-2 md:pl-1">
24                <Aside />
25                <TableOfContentsSideBar />
26              </div>
27            </div>
28          </main>
29
30          <Footer />
31        </div>
32      </body>
33    </html>
34  );
35}
36
37

layout.tsxTableOfContentsSideBarが使用されているので全記事ページで目次が表示されます。

参考にさせて頂いた記事

私は下記の記事を参考に目次を作りました。大変分かりやすく助かったので皆さんも参考にしてみて下さい。

Tocbot を使用し Next.js で構築したブログに目次を追加する

tocbotでZennとかQiitaみたいな目次を作る

Zennみたいな目次を作りたい!

本記事は以上になります。

ご一読頂きありがとうございました。