ブログにページングを追加しました

なにをしたのか

Gatsby + Contentfulの構成でブログを運営していますが 少し前まで記事が全件一覧に表示されるようになっていました。 Wordpressとは違い自作テンプレなので、これも自分で作るのが大変ですが 仕組みがわかれば流用できるので大丈夫です

やり方

  1. 記事の総数を取得
  2. 記事の総数 ÷ 1ページに表示したい記事数 = 総ページ数
  3. 記事ページ分の表示するページを作成

流れとしては上記になります

それではやってみましょう

gatsby-node.jsに下記を追加します なお、記事の全取得のクエリはすでにあるものとします

  // 記事一覧
  // 記事表示件数
  const postPerpage = 1
  const pagesCount = Math.ceil(posts.length / postPerpage) //ページ総数

  Array.from({ length: pagesCount }).forEach((_, i) => {
    createPage({
      path: i === 0 ? "/posts/" : `/posts/${i + 1}`,
      component: path.resolve(`./src/templates/posts.js`),
      context: {
        skip: postPerpage * i,
        limit: postPerpage,
        currentPage: i + 1, // 現在のページ
        isFirstPage: i + 1 === 1, // 最初のページか
        isLastPage: i + 1 === pagesCount, // 最後のページか
      },
    })
  })
}

ここで指定したディレクトリにテンプレートを作成します component: path.resolve(./src/templates/posts.js)

contextで必要なデータをテンプレートに渡し、 下記のクエリで所定の順から記事を取得します

query($skip: Int!, $limit: Int!) {
    allContentfulBlogPost(
      sort: { fields: [publishDate], order: DESC }
      skip: $skip
      limit: $limit
    )

あとは、ページングを表示し 全体として下記のようになりました

import React from "react"
import { Link, graphql } from "gatsby"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons"

import Layout from "../components/layout"
import SEO from "../components/seo"
import BlogCard from "../components/blog-card"

const Posts = ({ data, location, pageContext }) => {
  // const siteTitle = data.site.siteMetadata.title
  const posts = data.allContentfulBlogPost.edges

  return (
    <Layout>
      {posts.map(({ node }) => {
        return (
          <BlogCard
            key={node.id}
            title={node.title}
            excerpt={node.content.childMarkdownRemark.excerpt}
            publishDate={node.publishedDateJP}
            categories={node.category}
            readMore={node.slug}
            eyecatch={node.eyecatch.fluid}
            eyecatchDescription={node.eyecatch.description}
          />
        )
      })}
      <ul className="d-flex justify-content-around">
        <li>
          {!pageContext.isFirstPage && (
            <Link
              className="btn_orange"
              to={
                pageContext.currentPage === 2
                  ? `/posts/`
                  : `/posts/${pageContext.currentPage - 1}/`
              }
              rel="prev"
            >
              <FontAwesomeIcon icon={faArrowLeft} />
              前のページへ
            </Link>
          )}
        </li>
        <li>
          {!pageContext.isLastPage && (
            <Link
              className="btn_orange"
              to={`/posts/${pageContext.currentPage + 1}`}
              rel="prev"
            >
              <FontAwesomeIcon icon={faArrowRight} />
              次のページへ
            </Link>
          )}
        </li>
      </ul>
    </Layout>
  )
}

export default Posts

export const pageQuery = graphql`
  query($skip: Int!, $limit: Int!) {
    allContentfulBlogPost(
      sort: { fields: [publishDate], order: DESC }
      skip: $skip
      limit: $limit
    ) {
      edges {
        node {
          id
          slug
          title
          publishedDateJP: publishDate(formatString: "Y年MM月DD日")
          eyecatch {
            fluid(maxWidth: 500) {
              ...GatsbyContentfulFluid_withWebp
            }
            description
          }
          category {
            id
            title
            slug
          }
          content {
            childMarkdownRemark {
              excerpt(format: PLAIN, pruneLength: 200)
              html
              htmlAst
            }
          }
        }
      }
    }
  }
`

参考程度に

About

現役フリーランスエンジニアの勉強・備忘録。
バックエンドがメイン。フロントからインフラまで興味があり、色々やっています