Gatsby にページネーションを導入する

本サイトは gatsby-starter-default をベースに構築している。そのためページネーションのような気の利いた機能は元からついていない。そこまで多くの記事を書く見通しは経っていないが、記事一覧が延々と縦に長くなっていくのはどうにも落ち着かないので早々にページネーション機能は整えておきたかった。

ページネーションの自作

ページネーションみたいな世の中一般的な機能がプラグインとして存在しないわけはない。それは疑うまでもないことだが、チュートリアルをこなし Gatsby の拡張方法がなんとなくわかってきたこのタイミングをみすみす逃すよりはこのくらいの機能であれば自作することでより Gatsby と親密になれるだろうと思った。

何より「こうやったら実現できそう」という設計が頭の中にあったので試さずにはいられなかった。

大まかに必要な事は下記

  • ページネーションの各ページ用のレイアウトファイルを作成する。
  • gatsby-node.js でページネーションの各ページ用の URL を作成する。

ページネーション用のレイアウト

レイアウトファイルは下記のようにした。「ページネーションの数字がどのように切り替わるのか」というロジックは MaterialUI 先生に丸投げした。そのため PostsPagination コンポーネントは @material-ui/lab/PaginationPagination コンポーネントを薄くラップしてあるだけだ。PostExcerpt は記事見出しのコンポーネント。

src/layouts/pagination-layout.js
import React from "react"
import { graphql } from "gatsby"

import GlobalLayout from "./global-layout"
import SEO from "../components/seo"
import PostsPagination from "../components/posts-pagination"
import PostExcerpt from "../components/post-excerpt"

export default function PaginationLayout ({ data, pageContext }) {
  const {pageNum, lastPageNum} = pageContext
  return (
    <GlobalLayout>
      <SEO title="All Posts" />
      {data.allFile.edges.map(({node}, idx) => {
        return (
          <PostExcerpt
            key={idx}
            createdDate={node.childMdx.frontmatter.date}
            name={node.name}
            title={node.childMdx.frontmatter.title}
            excerpt={node.childMdx.excerpt}
          />
        )
      })}
      <PostsPagination currentPageNum={pageNum} lastPageNum={lastPageNum} />
    </GlobalLayout>
  );
}

export const query = graphql`
query PaginationLayoutQuery($maxPostsInPage: Int, $skipCount: Int) {
  allFile(
    filter: {sourceInstanceName: {eq: "posts"}, internal: {mediaType: {eq: "text/mdx"}}}
    limit: $maxPostsInPage
    skip: $skipCount
    sort: {fields: childMdx___frontmatter___date, order: DESC}
  ) {
    edges {
      node {
        name
        childMdx {
          frontmatter {
            title
            date
          }
          excerpt
        }
      }
    }
  }
}
`

ページネーションの各ページ

レイアウトができたので、ページネーションの各ページの URL を作る。一部省略だが gatsby-node.js を下記のように編集した。こうすることで /page/1//page/2/ のような URL が Gatsby に登録される。

gatsby-node.js
exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  await createPaginationPages(graphql, createPage)
}

async function createPaginationPages(graphql, createPage) {
  const result = await graphql(`
    {
      allMdx {
        totalCount
      }
      site {
        siteMetadata {
          maxPostsInPage
        }
      }
    }
  `)
  const totalPostsCount = result.data.allMdx.totalCount
  const maxPostsInPage = result.data.site.siteMetadata.maxPostsInPage
  const lastPageNum = Math.ceil(totalPostsCount/maxPostsInPage)
  for (let start = 1; start <= lastPageNum; start++) {
    const slug = `/page/${start}/`
    createPage({
      path: slug,
      component: path.resolve(`./src/layouts/pagination-layout.js`),
      context: {
        pageNum: start,
        maxPostsInPage: maxPostsInPage,
        lastPageNum: lastPageNum,
        skipCount: (start - 1) * maxPostsInPage,
      }
    })
  }
}