
【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
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
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
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は使用しないので削除して頂いても問題ないです)
・下記の記事で紹介されていたものをそのまま使用しています。
解説も大変分かりやすく参考になりました。
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.tsx
でTableOfContentsSideBar
が使用されているので全記事ページで目次が表示されます。
参考にさせて頂いた記事
私は下記の記事を参考に目次を作りました。大変分かりやすく助かったので皆さんも参考にしてみて下さい。
Tocbot を使用し Next.js で構築したブログに目次を追加する
本記事は以上になります。
ご一読頂きありがとうございました。
目次