コツコツと

[GatsbyJS]タグ機能を実装する

July 28, 2020

GatsbyJS で生成したサイトにタグ機能を実装してみます。
タグ機能とは、記事にタグを付与することで、タグごとに分類した記事を一覧で表示する機能です。
(下図は、本ブログに実装しているタグ機能です。)

タグ機能

GatsbyJS でタグ機能を実装する方法は次の通りです。

  • Markdown ファイルの Flontmatter(メタ情報)にタグを追加する
  • タグで分類した一覧ページを createPages API で生成する
  • template ファイルを作成する

※ 環境をgatsby-starter-blog で構築しており、記事は Markdown で記述していることを前提にしています。
ソースコードはこちらです。

以下、順に説明していきます。

Markdown ファイルの Flontmatter(メタ情報)にタグを追加する

Markdown ファイルを直接編集する方法

Flontmatter とは Markdown ファイルの上部に記述されている破線(---)で囲まれた領域の情報のことです。
下図のように Flontmatter にタグ(tags)を追加します。タグはリスト形式で記述していきます。
下図の例では、「Node.js」、「npm」をタグとして設定しています。

タグ情報

NetlifyCMS を利用している場合

NetlifyCMS を利用している場合は、static/admin/config.yml ファイルに設定を追加することで、タグ情報を追加可能になります。
config.yml に下記(下から 2 行目)の設定を追加すると、NetlifyCMS の記事を編集するページにタグを入力する項目が追加されます。カンマ区切りで入力することで複数のタグが設定できます。
config.yml の詳細はこちらを参照ください。

collections:
  - name: blog
    label: Blog
    folder: content/blog
    create: true
    slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
    fields:
      - { label: Publish Date, name: date, widget: datetime }
      - { label: Title, name: title }
      - { label: "Description", name: "description", widget: "text"}
      - { label: "Featured Image", name: "thumbnail", widget: "image", required: false}
      - { label: "Tags", name: "tags", widget: "list"}
      - { label: Body, name: body, widget: markdown }

下図は、NetlifyCMS の記事を編集するページです。左下にタグを入力する項目があります。

NetlifyCMS

タグで分類した一覧ページを createPages API で生成する

gatsby-starter-blog で環境を構築していると、gatsby-node.js がすでに存在しています。
そのファイル中に、下記のように createPages の実装箇所がありますので、 そこにタグで分類した一覧ページを生成する処理を追加します。
ページ生成の実装方法は、公式サイトのこちらが参考になります。

// gatsby-node.js
exports.createPages = async ({ graphql, actions }) => {
  // ページ生成処理を実装する
}

実装の流れは次の通りです。

  • GraphQL を利用して、タグを抽出する
  • 抽出したタグの種類分、createPage を実行する
GraphQL を利用して、タグを抽出する

GraphQL は GatsbyJS のデータ層から、所望のデータを取得するために利用されています。
今回 GraphQL を利用してやることは、Markdown ファイルの Frontmatter に設定したタグを重複なしで抽出することです。
次の GraphQL のクエリを実行すると、タグが重複なしで取得できます。

exports.createPages = async ({ graphql, actions }) => {
    const tagResult = await graphql(
        `
            {
                allMarkdownRemark {
                    distinct(field: frontmatter___tags)
                }
            }
        `
    );
    ...
};

ちなみに、GraphQL のクエリの構築は、GraphiQLを利用すると便利です。
$ gatsby developで開発サーバーを立ち上げた後に、http://localhost:8000/___graphqlにアクセスすると利用できます。
詳細はこちらを参照ください。

抽出したタグの種類分、createPage を実行する

createPage は、ページを生成するメソッドです。 引数の actions から取得して利用します。

exports.createPages = async ({ graphql, actions }) => {
    ...
    const { createPage } = actions
    ...
};

createPage は、次のように利用します。

createPage({
  path: `ページのパス(URL)`,
  component: `テンプレートファイル(Reactコンポーネント)のパス`,
  context: {
    tag: `タグ`,
  },
})

上記のように createPage を実行すると、URL が path のページが生成されます。
そして、そのページに表示する内容は、 component に設定した React コンポーネントとなります。
また、context は、component の props として渡されます。

今回は、存在するタグ分、createPage を実行し、タグごとの一覧ページを生成します。

exports.createPages = async ({ graphql, actions }) => {
    const tagResult = await graphql(...);
    ...
    const tags = tagResult.data.allMarkdownRemark.distinct
    tags.forEach(tag => {
      createPage({
        path: `tag/${tag}`,
        component: path.resolve(`./src/templates/tag-page.js`),
        context: {
          tag: tag,
        },
      })
    })
};

タグで分類した一覧ページを表示するための React コンポーネントを実装する

ここでは、createPage の component に設定する React コンポーネントの実装について説明します。
実装のポイントは、同じタグが設定された記事(Markdown ファイル)を GraphQL を利用して抽出するところです。

GraphQL をページの実装で利用する場合、下記のように同じ JavaScript ファイルに React コンポーネントと GraphQL のクエリを実装します。 すると、GraphQL の実行結果が、data にセットされます。
また、creataPage の context に設定した tag は pageContext から取得できます。

// example.js

// Reactコンポーネント
const Template = ({ data, pageContext, location }) => {
    // creataPage の context は、pageContextから参照できる
    // 例えば、前章で設定した tag は次のように参照できる
    const tag = pageContext.tag;
    ...
}
export default Template

// GraphQLのクエリの定義
export const pageQuery = graphql`任意のクエリ`

続いて、同じタグが設定された記事(Markdown ファイル)を GraphQL を利用して抽出する実装について説明します。 下記のようなクエリを実装します。

export const pageQuery = graphql`
  query TagPageBySlug($tag: String!) {
    allMarkdownRemark(
      sort: { fields: frontmatter___date, order: DESC }
      filter: { frontmatter: { tags: { eq: $tag } } }
    ) {
      edges {
        node {
          fields {
            slug
          }
          frontmatter {
            date(formatString: "MMMM DD, YYYY")
            title
            description
            tags
          }
        }
      }
    }
  }
`

クエリについて説明します。
ポイントは、次の 2 箇所です。

  • 2 行目: (query TagPageBySlug($tag: String!) {
  • 5 行目: (filter: { frontmatter: { tags: { eq: $tag } } }

2 行目は、createPage で設定した tag を引数に設定しています。
GatsbyJS では、createPage の context に設定した値を GraphQL のクエリの引数として利用できます。

5 行目は、抽出する記事の条件設定部です。
ここでは、引数で渡されてきた tag が Markdown の Frontmatter に設定されているものでフィルタリングしています。

クエリのedges以降は、抽出した記事から、一覧ページで利用するデータを選択しています。
ここは、ページに表示するコンテンツに応じて任意のクエリを組めば良いです。

例えば、上記の GraphQL の結果は次のように利用できます。 下記は、同じタグが設定された記事の一覧を表示する React コンポーネントの実装例です。

// Reactコンポーネントの実装例
const TagPageTemplate = ({ data, pageContext, location }) => {
  // creataPage の context に設定した tag を参照する
  const tag = pageContext.tag

  // GraphQLの実行結果を参照する
  const posts = data.allMarkdownRemark.edges

  return (
    <div>
      {/* タグの表示 */}
      <p>tag: {pageContext.tag}</p>
      {/* 同じタグが設定された記事のタイトルを表示する */}
      <ul>
        {posts.map(({ node }) => {
          return (
            <li
              dangerouslySetInnerHTML={{
                __html: node.frontmatter.title,
              }}
            />
          )
        })}
      </ul>
    </div>
  )
}

以上、今回は GatsbyJS で生成したサイトにタグ機能を実装する方法について解説しました。


© 2020 jiri3