Astro.jsで作ったMarkdownブログに目次を導入する

公開日:
目次

Astro.jsは、静的サイト生成(SSG)に特化したフレームワークとして、Markdownを使用して簡単にブログを構築することができます。

今回は、Astro.jsで作成したMarkdownブログに目次(Table of Contents, TOC)を導入する方法を解説します。

前提条件

  • Node.jsがインストールされている
  • Astro.jsプロジェクトが作成済み
  • Markdown形式でブログ記事が書かれている

Astro.jsに目次を導入する方法

Astro.jsに目次を導入する方法は大きく分けて2種類あります。

  1. rehypeまたはremarkプラグインを利用する
  2. AstroのIntegrationsを利用する
  3. Astro.jsのrender()メソッドを利用する

rehypeまたはremarkプラグインを利用する

Astro.jsはデフォルトでMarkdown から AST への変換でremarkを、AST から html への変換でrehypeを使っており、それぞれのプラグインを使うことができます。

  • rehype-toc
  • remark-toc

rehype-toc

rehype-tocはASTからhtmlにパースする際に自動的に目次を生成してくれます。

使い方もシンプルです。

目次生成プラグイン(rehype-toc)のインストール

目次を生成するためのプラグインとして、rehype-tocを使用します。

このプラグインは、Markdownファイルから目次を生成してくれます。

まず、必要なパッケージをインストールします。

npm i rehype-toc

Astro設定ファイルの更新

次に、Astroの設定ファイルを更新して、rehype-tocを使用するように設定します。

astro.config.mjs ファイルを開き、以下のように設定を追加します。

import { defineConfig } from 'astro/config';
import rehypeToc from 'rehype-toc';

// https://astro.build/config
export default defineConfig({
  markdown: {
    rehypePlugins: [
      rehypeToc,
    ],
  },
});

これだけでMarkdownのコンテンツの上に目次が自動で追加されます。

htmlに変換された目次はclassにtocが追加されているので、見た目を修正したい場合は.tocでcssを当ててください。

例えば、src/pages/blog/[slug].astroのようなファイルに以下のようにstyleを追加します。

---
---
<article>
  {Astro.content}
</article>
<style>
.toc {
  background: #f9f9f9;
  padding: 1rem;
  border-radius: 0.5rem;
  margin-bottom: 2rem;
}
</style>

remark-toc

remark-tocはMarkdownからパースする際に自動的に目次を生成してくれます。

目次を生成するためのプラグインとして、remark-tocを使用します。

目次生成プラグイン(remark-toc)のインストール

まず、必要なパッケージをインストールします。

npm i remark-toc

Astro設定ファイルの更新

次に、Astroの設定ファイルを更新して、rehype-tocを使用するように設定します。

astro.config.mjs ファイルを開き、以下のように設定を追加します。

import { defineConfig } from 'astro/config';
import remarkToc from 'remark-toc';

// https://astro.build/config
export default defineConfig({
  markdown: {
    remarkPlugins: [
      remarkToc,
    ],
  },
});

Markdownファイルで目次を生成

Markdownファイル内で目次を生成したい位置にを記述します。例えば、src/pages/blog/my-article.mdのようなMarkdownファイルに以下のように記述します。

# My Article

<!-- toc -->

## 見出し1

見出し1の中身...

## 見出し2

見出し2の中身...

これにより、目次が自動的に生成され、指定した位置に挿入されます。

これだけでMarkdownのコンテンツの上に目次が自動で追加されます。

Astroのインテグレーションを利用する

remarkやrehypeのプラグイン以外にも、目次用のAstroインテグレーションがあるので紹介します。

astro-custom-toc

astro-custom-tocはAstroのintegrationです。

rehype-tocやremark-tocよりも細かいカスタマイズができるみたいです。

作者の方が使い方を紹介しているので、そちらを参考にしてください。

Astro.jsのrender()メソッドを利用する

プラグインを利用せず、自分で色々カスタマイズしたい場合は、Astro.jsのrender()メソッドを利用して目次に使える見出し情報を取得します。

この場合、動的ルーティング(getStaticPaths())でMarkdownで記事を表示している前提です。

TOCコンポーネントを作成する

例えば、src/components/TableOfContents.astroという名前でファイルを作成します。

---
const { headings } = Astro.props;
---

<nav class="toc">
  <h2>目次</h2>
  <ul>
    {headings.map(heading => (
      <li>
        <a href={`#${heading.slug}`}>{heading.text}</a>
      </li>
    ))}
  </ul>
</nav>

<style>
.toc {
  background: #f9f9f9;
  padding: 1rem;
  border-radius: 0.5rem;
  margin-bottom: 2rem;
}
.toc h2 {
  margin-top: 0;
}
.toc ul {
  list-style: none;
  padding-left: 0;
}
.toc li {
  margin: 0.5rem 0;
}
.toc a {
  text-decoration: none;
  color: #007acc;
}
.toc a:hover {
  text-decoration: underline;
}
</style>

render()メソッドからMarkdownHeading[]プロパティを呼び出す

ブログ記事のMarkdownファイルに目次を追加します。

src/pages/blog/[slug].astroのようなファイルに以下のように目次コンポーネントを追加します。

---
import TableOfContents from '../../components/TableOfContents.astro';

export async function getStaticPaths() {
    const posts = await getCollection('posts');
    return posts.map(post => ({
        params: { slug: post.slug }, 
        props: { post },
    }));
}
const { post } = Astro.props;
const { Content, headings } = await post.render();
---
<TableOfContents headings={headings} />
<article>
  <Content />
</article>

render()メソッドで取得しているheadingsというプロパティが見出しに関する情報を持っています。

[
  { "depth": 2, "slug": "見出し2のID", "text": "見出し2の文" },
  { "depth": 3, "slug": "見出し3のID", "text": "見出し3の文" },
  { "depth": 4, "slug": "見出し4のID", "text": "見出し4の文" }
]

depthは見出しの階層の深さ、slugは見出しに振られているID、textは見出しの文です。

目次を作る方法の比較

上記で書いた目次を作る方法をメリット・デメリットで表にしました。

方法・プラグイン メリット デメリット
rehype-toc - シンプルで使いやすい
- 簡単なカスタマイズも可能
- 他のrehypeプラグインとの相性が良い
- カスタマイズ性が低い
- 目次の位置が固定される
remark-toc - シンプルで使いやすい
- 簡単なカスタマイズも可能
- カスタマイズ性が低い
- markdownに目次を毎回書く必要がある
astro-custom-toc - 使いやすい
- カスタマイズもそれなりにできる
- 情報が少ない
- 公式サポートの方法ではないため、メンテナンスされない可能性もある
render()メソッド - 高いカスタマイズ性
- プラグインを使用せずに目次を生成できる
- 完全な制御が可能
- 実装が複雑になる可能性がある
- メンテナンス性が低下する可能性がある

個人的には色々いじりたいのでrender()メソッドにすることにしました。

最初はrehype-tocやremark-tocを使ってみて、やりたいことが増えたら徐々にカスタマイズ性の高いものへとステップアップしていくのがいいかもしれませんね。

参考