Gatsbyにタグ機能、カテゴリ機能をつける（応用編）

<head>

<link rel="canonical" href="https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-add-tags-application" />

</head>

# はじめに

> 本記事は https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-add-tags-application のクロスポスト記事になります。

この記事は[Gatsby](https://www.gatsbyjs.org/)というヘッドレスCMS技術で構成されています。今回は「エンジニア初心者でもできる」を前提に以下の構成で記事を作成していこうと思います。

* [Gatsby始めるまで](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog/)
* [GatsbyにShare機能、OGPタグをつける](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-share/)
* [タグ機能、カテゴリ機能をつける（基礎編）](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-add-tags)
* [タグ機能、カテゴリ機能をつける（応用編）](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-add-tags-application)
* [GatsbyにTableOfContents（目次）をつける](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-tableofcontent)
* [DarkModeをつける](https://tech-blog.yoshikiohashi.dev/posts/start-gatsby-blog-darkmode)

# 内容

前回はGraphQLを使用して、データを取得したJsonデータをTemplateに当てはめて画面に表示させるところまで行いました。 データの流れを理解できたでしょうか？この流れを理解するとあとは簡単です。

今回は応用編です。

「実際にはタグだけのページって必要でしょうか？」私は必要ないと思います。できれば、ユーザとしては選んだタグと記事の一覧が見れたら使いやすいはずです。

こんなタグ一覧＋記事一覧ページを作るところをゴールに目指しましょう。

![](https://ucarecdn.com/698a1a04-4692-4389-8cdc-878ab2f39ed9/)

## まずはタグ一覧＋記事一覧のテンプレートファイルの作成

### 最初にGraphQLのクエリーを完成させよう

ポイントになってくるのが**query TagsListTemplate($tag: String!)**と**tags: { glob: $tag }**のTemplateの引数の指定の仕方と使い方です。**glob**はglobalの略でいわゆる何でも検索ができます。*を入れたらすべてのタグが見つかるようになります。

タグ一覧（**/tags**）では全タグを表示させたいので*が入ってきますね。

```graphql
query TagsListTemplate($tag: String!) {
    allMarkdownRemark(
        filter: { 
            frontmatter: { 
                template: { eq: "post" },
                draft: { ne: true }, 
                tags: { glob: $tag } } 
        },
        sort: { order: DESC, fields: [frontmatter___date] }
    ){
        group(field: frontmatter___tags) {
            fieldValue
            totalCount
        }
        edges {
            node {
                fields {
                    slug
                    categorySlug
                }
                frontmatter {
                    title
                    date
                    category
                    description
                    socialImage
                }
                excerpt
            }
        }
    }
}
```

あとは、記事を取得するクエリーとタグを取得するクエリーを記述しましょう。

```graphql
group(field: frontmatter___tags) {
    fieldValue
    totalCount
}
edges {
    node {
        fields {
            slug
            categorySlug
        }
        frontmatter {
            title
            date
            category
            description
            socialImage
        }
        excerpt
    }
}
```

GraphQLを実際に `http://localhost:8000/___graphql` で試してJsonが取得できたら次へ進みましょう。

次は、取得したJsonをComponentに当てはめていきます。

```js
const TagsListTemplate = ({ data, pageContext }) => {
  const tags = data.allMarkdownRemark.group;
  const posts = data.allMarkdownRemark.edges;

  return (
    <div className={TagsList}>
      <Tags tags={tags} selectedTag={pageContext.tag}/>
      <Post posts={posts} /> // それぞれの記事を表示するコンポーネントに合わせて差し替えてください
    </div>
  );
};

export const query = graphql`
    query TagsListTemplate($tag: String!) {
        allMarkdownRemark(
            filter: { frontmatter: { template: { eq: "post" }, draft: { ne: true }, tags: { glob: $tag } } },
            sort: { order: DESC, fields: [frontmatter___date] }
        ){
            group(field: frontmatter___tags) {
                fieldValue
                totalCount
            }
            edges {
                node {
                    fields {
                        slug
                        categorySlug
                    }
                    frontmatter {
                        title
                        date
                        category
                        description
                        socialImage
                    }
                    excerpt
                }
            }
        }
    }
`;

export default TagsListTemplate;
```

ここまででGraphQLクエリーでタグと記事の一覧を取得するクエリーを書き、Componentに当てはめることまでしました。次はタグのコンポーネントの作成です。

## タグ一覧を表示するコンポーネントパーツの作成

先程作成したTemplateから呼び出されるタグコンポーネントです。こちらの例ではカウント順にソートしていますが、してもしなくてもOKです。

また、引数に`selectedTag`がありますが、選択している状態を表現したかったので表示しています。好みで変更してOKです。

```js
const sortTotalCount = (tags) => orderBy(tags, ['totalCount', 'fieldValue'], ['desc']);

const Tags = ({ tags, selectedTag }: Props) => (
  <div className={styles["Tags"]}>
    {sortTotalCount(tags).map(tag => (
      <li key={tag.fieldValue}>
        <Link to={`/tags/${kebabCase(tag.fieldValue)}/`} className={selectedTag === tag.fieldValue ? styles['Tags--Selected'] : '' }>
          {tag.fieldValue}
          <span>{tag.totalCount}</span>
        </Link>
      </li>
    ))}
  </div>
);

export default Tags;
```

## gatsby-node.js に **/tags/** と **/tags/{tags}** のルーティングを作成する

**gatsby-node.js**

まずはタグ一覧ページを登録します。

すべてのタグを表示するのでcontextに**{ tag: "*" }**を指定しています。

```js
  // Tags list
  createPage({
    path: '/tags',
    component: path.resolve('./src/templates/tags-list-template.js'),
    context: { tag: "*" }
  });
```

タグが選ばれた場合のURLを登録していきます。

ポイントはcontextの**{ tag: tag.fieldValue }**を指定している箇所です。これでURLが**/tags/{tags}**だったときにタグ名がTemplateに引数として受け渡しされることになります。

```js
  const result = await graphql(`
    {
      allMarkdownRemark(
        filter: { frontmatter: { template: { eq: "post" }, draft: { ne: true } } }
      ) {
        group(field: frontmatter___tags) {
          fieldValue
          totalCount
        }
      }
    }
  `);

  _.each(result.data.allMarkdownRemark.group, (tag) => {
    const numPages = Math.ceil(tag.totalCount / postsPerPage);
    const tagSlug = `/tags/${_.kebabCase(tag.fieldValue)}`;

    for (let i = 0; i < numPages; i += 1) {
      createPage({
        path: i === 0 ? tagSlug : `${tagSlug}/page/${i}`,
        component: path.resolve('./src/templates/tags-list-template.js'),
        context: {
          tag: tag.fieldValue
        }
      });
    }
  }
```

処理の流れを表すとこのような図になります。

![](https://ucarecdn.com/bc8da85e-697a-4b9a-8b33-b10ebf5349df/)

## gatsby build を再度実行

**/tags** のページに遷移してみましょう。タグ一覧とその記事が表示されましたか？

# まとめ

いかがだったでしょうか？うまく設定できたでしょうか？ここまで理解できたら他の実装にも応用できますね。それでは次回の記事で。