Gatsby.jsで作ったブログにArchiveページを作成する

#動機
静的サイトジェネレーターのGatsby.jsでブログを開発するにあたり、Archiveを作ろうと思い挑戦。
私が調べた（対して調べてない・・・）範囲ではArchive作る便利なpluginとかないないみたい。なので、実作。ベストプラクティスではない、と思う。

#作り
記事を作成した月ごとにまとめるよくある感じにする。
まだ7、８月しか記事を書いてないので2ヶ月分しかない。もっと書かなきゃ。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148800/19bd28d7-10be-0972-d13f-6d28a4420dff.png" width=40%>

#環境
・OS: macOS 10.15.5
・gatsby: 2.24.14
・ヘッドレスCMS: Contentful
・FW: React Bootstrap

#手順
1.templateフォルダにarchiveページ用のテンプレートを作成する。
2.gatsby-node.jsで、archiveページを生成する
3.archiveページのComponentを作成する。

まずはarchiveページ用のテンプレートページを作っていきます。テンプレートはページの構成が一緒で文章と画像を差し替えるだけでいいページをたくさん作る時に使います。便利。

```js:archiveTemplate.js
import React from "react"
import Layout from "../components/layout"
import { graphql, Link } from "gatsby"
import { Container, Row, Col, Card, Button } from "react-bootstrap"
import Img from "gatsby-image"
import Archive from "../components/archive"

export const query = graphql`
  query($skip: Int!, $limit: Int!, $currentYearMonth: Date!, $nextYearMonth: Date!){
    allContentfulCode(
      sort: {fields: createdDate, order: DESC},
      limit: $limit,
      skip: $skip,
      filter:  {createdDate :
        {
          lt: $nextYearMonth,
          gte: $currentYearMonth
        }
      }
      ){
      edges {
        node {
          title
          slug
          category {
            category
            categorySlug
            id
          }
          description {
            content {
              content {
                value
              }
            }
          }
          createdDate(formatString: "YYYY/MM/DD")
          thumbnail{
            fluid(maxWidth: 500) {
              src
              ...GatsbyContentfulFluid_withWebp
            }
            description
          }
        }
      }
    }
  }
`

function ArchiveTemplate(props) {
  return (
    <Layout>
      <Container fluid >
        <Row className="justify-content-md-center">
          <Col xs={7} className="mt-3" style={{ borderBottom: "3px solid grey" }} >
            <h1 className="m-3">{`${props.pageContext.currentYearMonth}`}</h1>
          </Col>
          <Col xs={3} >
          </Col>
        </Row>
        <Row className="justify-content-md-center">
          <Col xs={7} style={{ padding: "20px", textAlign: "center" }} className="mt-3" >
            {props.data.allContentfulCode.edges.map((edge, index) => (
              <Card className="m-5">
                <Card.Body>
                  <Card.Text>
                    <Link to={`/code/${edge.node.category[0].categorySlug}/${edge.node.slug}`}>
                      <h3>{edge.node.title}</h3>
                    </Link>
                    {edge.node.category.map(x => (
                      <li className={x.categorySlug} key={x.id} style={{ listStyleType: "none" }}>
                        <Link to={`/code/${x.categorySlug}/`}>＃{x.category}</Link>
                      </li>
                    ))}
                  </Card.Text>
                </Card.Body>
                <figure>
                  <Link to={`/code/${edge.node.category[0].categorySlug}/${edge.node.slug}`}>
                    <Img
                      fluid={edge.node.thumbnail.fluid}
                      alt={edge.node.thumbnail.description}
                      style={{ height: "300px" }}
                    />
                  </Link>
                </figure>
                <Card.Body>
                  <Card.Text>
                    {edge.node.description.content[0].content[0].value}
                  </Card.Text>
                  <Button variant="outline-secondary" href={`/code/${edge.node.category[0].categorySlug}/${edge.node.slug}`}>See More</Button>
                </Card.Body>
                <Card.Footer className="text-muted">作成日 {edge.node.createdDate}</Card.Footer>
              </Card>
            ))}
            <div>
              {!props.pageContext.isFirst && (
                <div className="text-left" style={{ display: "inline" }}>
                  <Link
                    to={
                      props.pageContext.currentPage === 2
                        ? `/${props.pageContext.currentYear}/${props.pageContext.currentMonth}/`
                        : `/${props.pageContext.currentYear}/${props.pageContext.currentMonth}/${props.pageContext.currentPage - 1}`
                    }
                    rel='prev'>
                    <span className="m-5">前のページ </span>
                  </Link>
                </div>
              )}
              {!props.pageContext.isLast && (
                <div className="text-right" style={{ display: "inline" }}>
                  <Link to={`/${props.pageContext.currentYear}/${props.pageContext.currentMonth}/${props.pageContext.currentPage + 1}/`} rel='next'>
                    <span className="m-5">次のページ</span>
                  </Link>
                </div>
              )}
            </div>
          </Col>
          <Col xs={3}>
            <Archive />
          </Col>
        </Row>
      </Container>
    </Layout >
  )
}

export default ArchiveTemplate
```
##ポイント
GraphQLのfilterを使って、データの絞り込みを行う。今回は対象の月のデータを、対象の月以上、翌月未満で絞り込んで取得。
```filter:  {createdDate :
{
  lt: $nextYearMonth,
  gte: $currentYearMonth
}
}```


次にarchiveページを生成します。生成場所は、gatsby-node.jsです。
まずはqraphqlを使って、全データを取得します。項目は記事生成日のみ。

```js:gatsby-node.js
const path = require('path')

module.exports.createPages = async ({ graphql, actions}) => {
  const { createPage } = actions
  const archiveTemplate = path.resolve('./src/template/archiveTemplate.js')
  const res = await graphql(`
    query{
      allContentfulData(sort: {fields: createdDate, order: DESC}) {
        edges {
          node {
            createdDate
          }
        }
      }
    }
  `)
```
次にarchiveページを生成していきます。

```js:gatsby-node.js
// archive //
  const archivePerpage = 5 //１ページに表示する記事の数
  const yearMonth = ['2020-07', '2020-08', '2020-09', '2020-10', '2020-11', '2020-12'];
  const archiveByMonth = yearMonth.map(x => {
    const obj = {};
    obj[x] = (res.data.allContentfulData.edges.filter(y => y.node.createdDate.match(x))).length;
    return obj;
  });

  archiveByMonth.forEach((x) => {
    const archivePosts = x[Object.keys(x)] //記事の総数 
    if (archivePosts === 0) return;
    const archivePages = Math.ceil(archivePosts / archivePerpage) //記事一覧ページの総数
    const currentYear = Object.keys(x)[0].split('-')[0];
    const currentMonth = Object.keys(x)[0].split('-')[1];
    const nextMonth = Number(currentMonth) + 1 === 13 ? '01' : ('00' + String(Number(currentMonth) + 1)).slice(-2);
    const nextYear = (nextMonth === '01') ? String(Number(nextMonth) + 1) : currentYear;

    Array.from({ length: archivePages }).forEach((_, i) => {
      createPage({
        path:
          i === 0
            ? `/${currentYear}/${currentMonth}`
            : `/${currentYear}/${currentMonth}/${i + 1}/`,
        component: archiveTemplate,
        context: {
          currentYearMonth: currentYear + '-' + currentMonth,
          nextYearMonth: nextYear + '-' + nextMonth,
          currentYear,
          currentMonth,
          skip: archivePerpage * i,
          limit: archivePerpage,
          currentPage: i + 1, // 現在のページ番号
          isFirst: i + 1 === 1, //最初のページ
          isLast: i + 1 === archivePages, // 最後のページ
        }
      })
    })
  })

```

##ポイント
月ごとにデータ数をカウントする。
``` const yearMonth = ['2020-07', '2020-08', '2020-09', '2020-10', '2020-11', '2020-12'];
　　const archiveByMonth = yearMonth.map(x => {
    const obj = {};
    obj[x] = (res.data.allContentfulData.edges.filter(y => y.node.createdDate.match(x))).length;
    return obj;
```

URLは/YYYY/MM/ページ番号にする。
``` ? `/${currentYear}/${currentMonth}`
    : `/${currentYear}/${currentMonth}/${i + 1}/`,
```

最後にarchiveのCompenentを作成する。

```js:archive.js
import React from 'react';
import { useStaticQuery, graphql, Link } from 'gatsby'
import { Container, Row, Col } from 'react-bootstrap';
import Style from './layout.module.scss'

const Archive = (props) => {
    const datas = useStaticQuery(graphql`
    query{
        allContentfulCode {
            edges {
              node {
                createdDate(formatString:"YYYY/MM/DD")
               }
            }
        }
    }
`)
    const date = ['2020/07', '2020/08', '2020/09', '2020/10', '2020/11', '2020/12'];
    const count = date.map(x => {
        const obj = {};
        obj[x] = (datas.allContentfulCode.edges.filter(y => y.node.createdDate.match(x))).length;
        return obj;
    });
    return (
        <div className={Style.archive_wrap}>
            <Container className={Style.content}>
                <Row>
                    <Col>
                        <h3>Archive</h3>
                        <ui>
                            {count.map((x, index) => {
                                if (x[Object.keys(x)] === 0) return false;
                                return <li key={index}><Link to={`/${Object.keys(x)}`}>{`${Object.keys(x) + ' (' + x[Object.keys(x)] + ')'}`} </Link> </li>
                            }
                            )}
                        </ui>
                    </Col>
                </Row>
            </Container>
        </div>
    );
}

export default Archive
```
##ポイント
記事数が0の月はarchiveリストに表示しない。
```if (x[Object.keys(x)] === 0) return false;```

#完成
https://sakublog.netlify.app/