[Gatsby]MDXでブログ記事に目次をつける

August 11th, 2020

はじめに

MDXプラグインを使って、ブログに目次を付けてみます。gatsby-remark-table-of-contentsも試してみたのですが、あまり融通が効かなかったのでMDXを使うことにしました。

実装

gatsby-plugin-mdxの導入

目次の生成にgatsby-plugin-mdxを利用するので設定していきます。

プラグインのインストール

yarn add gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react

configの修正

pluginsにgatsby-plugin-mdxを追加します。

gatsby-config.js
plugins: [
    'gatsby-plugin-mdx',
.
.
.

確認

この時点で開発環境を立ち上げて、http://localhost:8000/___graphqlへアクセスするとGraphQLでchildMdxが選択できるようになっています。

graphQLの修正

childMdxのtableOfContentsを取得するようにGraphQLクエリを修正します。

blog-post-tsx
    contentfulBlogPost(slug: { eq: $slug }) {
      title
      publishDate(formatString: "MMMM Do, YYYY")
      heroImage {
        fluid(maxWidth: 1180, background: "rgb:000000") {
          ...GatsbyContentfulFluid_tracedSVG
        }
      }
      body {
        childMarkdownRemark {
          html
        }
        childMdx {
          tableOfContents
        }
      }
      slug
    }

目次表示コンポーネントの作成

コンポーネント内で再帰を使えるようなので、ネストした目次の表示は再帰を使って実装します。細かい部分は置いておいてまずは動くものを作ってみます。

table-of-contents.tsx
import React from 'react'

type Props = {
  items: any
}

const Component: React.FC<Props> = props => {
  return (
    <ul>
      {
        props.items.map(item => (
          <li>
            {item.title}
            {item.items && (<Component items={item.items} />)}
          </li>
        ))
      }
    </ul>
  )
}

export default Component

試しにコンポーネントを使ってみます。

blog-post.tsx
<TOC items={data.contentfulBlogPost.body.childMdx.tableOfContents.items} />

見出しが含まれたブログのページを表示すると見出しの構造の通りの目次が表示されました。

見出しにidをつける

目次をクリックした時に見出しへ飛べるようにしたいのでidをつけます。

プラグインのインストール

見出しにidを自動で振ってくれるプラグインをインストールします。

yarn add gatsby-remark-autolink-headers

configの修正

gatsby-transformer-remarkのoptionsのpluginsに設定を追加します。

gatsby-config.js
resolve: 'gatsby-transformer-remark',
options: {
  plugins: [
    `gatsby-remark-autolink-headers`,
.
.
.

確認

yarn ran devで開発環境を立ち上げて確認してみます。見出しのタグを確認すると以下のようにidが設定されていました。

<h1 id="Header1">Header1</h1>

目次を見出しへのリンクにする

パッケージのインストール

react-scrollを使うのでパッケージをインストールします。

yarn add react-scroll

目次コンポーネントの修正

{item.title} を出していた箇所をリンクに書き換えます。

table-of-contents.tsx
const Component: React.FC<Props> = props => {
  return (
    <ul>
      {
        props.items.map(item => (
          <li>
            <Link
              activeClass="active"
              to={item.url.replace('#', '')}
              spy={true}
              smooth={true}
              offset={0}
              duration={100}
            >{item.title}</Link>
            {item.items && (<Component items={item.items} />)}
          </li>
        ))
      }
    </ul>
  )
}

これで目次をクリックすると見出しに飛べるようになりました。

参考