【Gatsby.js】自作ブログのタグ一覧を取得してカテゴリで分類をする

#概要
テンプレートを自身でアレンジする記事の第二弾となります。
前回の、[GoogleAnalyticsからビュー数を取得して使用する記事](https://qiita.com/Hidesato_Nakamura/items/a9ac8cebfce894c5c624)に続きまして、
今回はCMSから投稿されたマークダウンファイルからタグデータを取得して
コンポーネント内に一覧で表示させていきたいと思います。


#前提
今回のプロジェクトでは`Gatsby.js` + `Github` + `NetlifyCMS`でプロジェクトを組んでいます。

#手順
以下の手順で説明をしていきます。

1.実装したいもの
2.マークダウン内のデータ構造の説明
3.GraphQLでクエリを実装
4.createPageで指定のパスにページを作る
5.コンポーネントに静的なクエリを持たせる方法



#1.実装したいもの
ホーム画面に投稿された記事に含まれるタグ一覧を配置し、そこからそのタグを含むページ一覧を見れるような
形にしたいと思います。

ワードプレスで生成されたブログなどでよく見かける、サイドにタグがズラっと並んでいるあの形ですね！


#2.マークダウン内のデータ構造の説明

説明で用いるマークダウンのディレクトリ構成とマークダウンファイルの内容は以下になります。

```
-content
  ├ assets
  └ blog //ここに.mdを保存
```


```markdown:ABC.md
---
templateKey: blog-post
title: ABC
description: 概要
tags:
  - A
  - B
  - C
date: 2020-06-02T09:16:39.445Z
---

ABC
```

```markdown:BCD.md
---
templateKey: blog-post
title: ABC
description: 概要
tags:
  - B
  - C
  - D
date: 2020-06-02T09:16:39.445Z
---

BCD
```

Gatsby.jsプロジェクトではマークダウンで記述されたをJSで処理することができるため、
ファイルの先頭部分に処理に必要なデータを含ませておくことができます。

上記にあるように、A、B、Cのタグをもった`ABC.md`とB、C、Dのタグをもった`BCD.md`の二つを使っていきます。

#3.GraphQLでクエリを実装
Gatsby.jsが早い理由として、ビルドで最適化されたpublicフォルダ内の静的なページを読み込むことが挙げられますが
その際に用いられるGraphQLについて使用方法を軽く説明します。

`gatsby develop`で立ち上げたモックサーバー`localhost:8000`もしくは`127.0.0.1:8000`
のルートの直後に`/__graphQL`を追加すると、そのプロジェクトで利用できるクエリが検索できます。

左の`Explorerタブ`で取得したい情報をクリックしていけば自動的にクエリが生成され、▶︎を押すとクエリのレスポンスを確認することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649495/fc1c0bce-4e51-9bfb-7887-0c4b3ff6310f.png)

今回取得したいのは全記事の情報ですので`allDownRemark`をクリックしてください。これは各記事の情報を配列に格納して返してくれるクエリです。
記事を上から新しい順にならべたいので、
`sort` > `fields`に`frontmatter__date`、`sort` > `order`に`DESC`を設定してください。

また、各記事のパス、タイトル、タグが必要ですので
`edges` > `node`と進んでいただき
`fields` の `slug`と
`frontmatter` の `tags` , `title`にチェックを入れてください。
そうすると、右側にクエリが自動生成されているのがわかると思います。


#4.createPageで指定のパスにページを作る
クエリで取得した記事内のデータを使って検索結果のページを生成してみましょう。

まずは、生成するページにどのタグの検索結果を表示してほしいのかの情報を渡して
生成してもらう追記をしていきます。`gatsby-node.js`を開いてクエリを以下のように編集します。

```javascript:gatsby-node.js
exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions

  const blogPost = path.resolve(`./src/templates/blog-post.js`)
  const result = await graphql(
    `
      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: DESC }
          limit: 1000
        ) {
          edges {
            node {
              fields {
                slug
              }
              frontmatter {
                title
                tags  
              }
            }
          }
        }
      }
    `
  )

  if (result.errors) {
    throw result.errors
  }
  const posts = result.data.allMarkdownRemark.edges

  //追記
  let tags = []
  posts.forEach(edge => {
    if (_.get(edge, `node.frontmatter.tags`)) {
      tags = tags.concat(edge.node.frontmatter.tags)
    }
  })
  tags = _.uniq(tags)

  tags.forEach(tag => {
    const tagPath = path.resolve(`/tags/${tag}`)
    createPage({
      path: tagPath,
      component: path.resolve(`./src/templates/tags.js`),
      context: {
        tag: tag,
      },
    })
  })
}
```

createPageの**path**に生成するページのルートからの相対パスを、
**component**に後述するテンプレートのパスを、
**context**に生成する際の情報としてtagを入れています。




次に取得したデータを貼り付ける用のテンプレートを記述していきます。

```
-src
  ├ components
  ├ pages
  └ templates //ここに保存してください。
```

```jsx:tags.js
import React from "react"
import { Link, graphql } from "gatsby"
import Layout from "../components/layout"
import SEO from "../components/seo"

const TestTemplate = ({ data, pageContext, location }) => {
  const posts = data.allMarkdownRemark.edges
  const title = data.site.siteMetadata.title
  const tag = pageContext.tag
  const postLinks = posts.map(post => (
    <li key={post.node.fields.slug}>
      <Link to={post.node.fields.slug}>
        <h2 className="is-size-2">{post.node.frontmatter.title}</h2>
      </Link>
    </li>
  ))
  console.log(data)
  return (
    <Layout location={location} title={`Tags | ${title}`}>
      <SEO title={tag} />
      <h1>
        {tag}：検索結果 {data.allMarkdownRemark.totalCount} 件
      </h1>
      <ul>{postLinks}</ul>
      <p>
        <Link to="/tags/">Browse all tags</Link>
      </p>
    </Layout>
  )
}

export default TestTemplate

export const tagPageQuery = graphql`
  query tagPageQuery($tag: String) {
    site {
      siteMetadata {
        title
      }
    }
    allMarkdownRemark(
      limit: 1000
      sort: { fields: [frontmatter___date], order: DESC }
      filter: { frontmatter: { tags: { in: [$tag] } } }
    ) {
      totalCount
      edges {
        node {
          fields {
            slug
          }
          frontmatter {
            title
          }
        }
      }
    }
  }
`
```
`gatsby-node.js`で渡されたcontextの情報を受け取り、クエリで記事を再検索しています。
リザルトは`TestTemplate`にあるdataに格納されているのでそれを必要な形で配置しておいてください。

これでモックサーバーで該当するURL(この説明の場合は`localhost:8000/tags/A`)に飛ぶと
検索結果が表示されているはずです。


#5.コンポーネントに静的なクエリを持たせる方法
これまでだけですと、検索結果(/tags/〇〇)のリンクを一々手動で追加しないといけないので
記事からタグ一覧をリンクとして表示するコンポーネントを制作していきます。

ですが、Pages以下のファイルではexportでクエリを使用できるのに対し、
コンポーネントではJSXの中にクエリを埋め込まなければ使えないという仕様があります。

それを踏まえて`/src/components/`に新しいファイルを作り、以下を記述してください。


```jsx:category.js
import React from "react"
import { kebabCase } from "lodash"
import _ from "lodash"
import { Link, graphql, StaticQuery } from "gatsby"

const categoryQuery = graphql`
  query categoryQuery {
    allMarkdownRemark(
      sort: { fields: [frontmatter___date], order: DESC }
      limit: 1000
    ) {
      edges {
        node {
          fields {
            slug
          }
          frontmatter {
            title
            tags
          }
        }
      }
    }
  }
`



const CategoryContents = ({ data }) => {
  const posts = data.allMarkdownRemark.edges
  let tags = []
  posts.forEach(edge => {
    if (_.get(edge, `node.frontmatter.tags`)) {
      tags = tags.concat(edge.node.frontmatter.tags)
    }
  })
  tags = _.uniq(tags)
  let tagsList = []

  tags.forEach(tag => {
    let count = 0
    posts.forEach(edge => {
      edge.node.frontmatter.tags.forEach(_tag => {
        if (_tag == tag) {
          ++count
        }
      })
    })
    tagsList.push({
      tag: tag,
      totalCount: count,
    })
  })

  const categoryContents = (
    <section>
      <p
        style={{
          textAlign: `center`,
          marginLeft: `auto`,
          marginRight: `auto`,
        }}
      >
        カテゴリー
      </p>
      <div>tags</div>
      <ul>
        {tagsList.map(
          list => (          
            <li key={list.tag + `tag`}>
              <Link to={`/tags/${kebabCase(list.tag)}/`}>
                {list.tag}({`${list.totalCount}`})
              </Link>
            </li>
          )
        )}
      </ul>
    </section>
  )

  return categoryContents
}

export default function Category() {
  const category = (
    <StaticQuery
      query={categoryQuery}
      render={data => <CategoryContents data={data} />}
    />
  )
  return category
}

```

クエリとテンプレートを別々に記述し、最後に`<StaticQuery />`内で各々のプロパティに代入して
exportしてあげれば、
このコンポーネントを`Layout.js`などでimportするだけで一覧のリンクが表示されます。


これはリスト形式で表示させるだけですので、スタイルなどは各自でいい感じに仕上げていただけると幸いです。