Gatsbyブログサイト移行物語~プラグインナシで一覧にページネーション実装~

Gatsbyブログサイト移行物語~プラグインナシで一覧にページネーション実装~

GatsbyReact

記事数が増えると一覧にペーネーションが欲しくなりますよね?

複数ページネーションを実装するためのプラグインがあるにもかかわらず自作して実装してしまいました。
プラグインに頼らず実装したい人のために、やり方をシェアします。

※ 2021年12月v4バージョンアップに伴いリライトしました。

この記事を書いた人

かみーゆ/フロントエンドエンジニア

資金ゼロからフィリピンで起業した海外ノマドエンジニア。IT業界10年以上でテクニカルディレクター(技術責任者)・エンジニア講師・ブリッジSEを経てLenzTechnologies Inc.を設立し、代表を務める。CMS concreteCMSエバンジェリスト。テックブログ以外も「磨耗しない人生」や「海外ノマド」のライフスタイルについて発信。好きなものは肉とハイボール。

今までのGatsbyの記事と注意点

現在ここまで記載しています。
制作するまでを目標にUPしていくので順を追ったらGatsbyサイトが作れると思います。

  1. インストールからNetlifyデプロイまで
  2. ヘッダーとフッターを追加する
  3. 投稿テンプレにカテゴリやらメインビジュアル(アイキャッチ)追加
  4. ブログ記事、カテゴリ、タグ一覧の出力
  5. プラグインを利用して目次出力
  6. プラグインナシで一覧にページネーション実装(←イマココ)
  7. 個別ページテンプレート作成
  8. プラグインHelmetでSEO調整
  9. CSSコンポーネントでオリジナルページを作ろう!!
  10. 関連記事一覧出力
  11. タグクラウドコンポーネントを作成する
  12. パンくずリストを追加する
  13. 記事内で独自タグ(コンポーネント)を使えるようにする
v5へのアップグレード方法はこちら。
Gatsby をアップグレード(v4→v5)して Netlify にデプロイ

Gatsbyのv4からv5へアップグレードしたのでそのやり方をメモしておきます。Netlify の Nodeバージョンの...

このシリーズはGithub・gatsby-blogに各内容ブランチごとで分けて格納しています。

今回のソースはpaginationブランチにあります。

このシリーズではテーマGatsby Starter Blogを改造

この記事は一番メジャーなテンプレート、「Gatsby Starter Blog」を改造しています。同じテーマでないと動かない可能性があります。

早速ページネーションを実装しよう!

ページネーションを実装するためには一覧を分割しページを生成します。
さらにページネーションを出力するコンポーネントを作成します。

WordPressなど、その他のCMSに慣れていると一覧を分割しページを生成するってところがちょっと不思議です。

ページネーションを実装するためにページを分割する

まずはgatsby-node.jsにページを分割するためのコードを書いていきます。

一覧ページの実装についてはこちらを参考にしてください。

Gatsbyブログサイト移行物語~ブログ記事、カテゴリ、タグ一覧の出力~

gatsbyのブログ用記事を抽出し一覧を作りました。カテゴリ、タグ一覧もぞれぞれ用意したのでだいぶ使い勝手がよくなりまし...

/ (プロジェクトディレクトリー)
  ├ gatsby-node.js(ページを生成するところ)
  ├ src/
  |  └ templates/
  |    └ blogs.js(一覧を出力するところ)
  └ components/
    └ pagination.js(新規作成)


gatsby-node.jsのブログ詳細ページを生成しているコードを利用して、数量を数えます。
変数countにページ数を格納します。

frontmatterのpagetypeがblogのみカウントします。

gatsby-node.js
// ~ 省略 ~

exports.createPages = async ({ graphql, actions, reporter }) => {
  // ~ 省略 ~
  if (posts.length > 0) {
    const blogPosts = posts.filter(post => post.frontmatter.pagetype === "blog")

    // 省略
    let count = blogPosts.length    console.log(count)//デバッグ
    // 一覧を出力するコードを追加
    createPage({
      path: "/blogs/",
      component: blogList,
      context: {},
    })

  // ~ 省略 ~

  }

  // ~ 省略 ~

}


続けて以下のコードを追記します。今回は1ページに12記事を表示します。

たとえば45記事あったら 4つのページに分割される、といった形になります。

  • /blogs/
  • /blogs/page/2/
  • /blogs/page/3/
  • /blogs/page/4/

どこからどこまでの記事を出力するか、テンプレートblog-list.js側に値を渡します。

  • limit … ループ数
  • skip … オフセット(どこからループを始めるか)数
  • current … 現在何番目
  • page … トータルのページ数
gatsby-node.js
  const postsPerPage = 12 //1ページに表示する記事の最大数

  // 一覧を出力するコードを追加
  let count = blogPosts.length //記事の長さ
  let numPages = Math.ceil(count / postsPerPage) //分割されるページの数
  for (let index = 0; index < numPages; index++) {
    const withPrefix = pageNumber =>
      pageNumber === 1 ? `/blogs/` : `/blogs/page/${pageNumber}/`
    const pageNumber = index + 1
    createPage({
      path: withPrefix(pageNumber), //出力されるパス
      component: blogList,
      context: {
        limit: postsPerPage, //1ページに表示される最大記事数
        skip: index * postsPerPage, //追加
        current: pageNumber, //追加
        page: numPages, //追加
      },
    })
  }

ページが出力されたか、カンタンに調べる方法があります。

404ページにアクセスすると、出力されているページ一覧が確認できます。

entry413 1

blog-list.jsで値を受け取る

次にblog-list.js側で値を受け取ります。

query blosQyery()$limit: Int!$skip: Int!を追加します。

Int!は型が数字で空の値はダメですよーって意味です。空でもいい場合は!を省きます。

Int!が原因でエラーを吐いている場合は、gatyby-node.js側で間違ったコードを書いている可能性があるのでよく確かめてみましょう。

blog-list.js
// ~ 省略 ~
// pageContextを追加
const BlogList = ({ pageContext, data, location }) => {// ~ 省略 ~
}
export default blogs
export const pageQuery = graphql`
# $limit、$skip追加
query ($limit: Int!, $skip: Int!) {  site {
    siteMetadata {
      title
    }
  }
  allMarkdownRemark(
    # $limit、$skip追加
    limit: $limit    skip: $skip    sort: { fields: [frontmatter___date], order: DESC }
    filter: { frontmatter: { pagetype: { eq: "blog" } } }
  ) {
    # 記事総数取得
    totalCount
    nodes {
      # 省略
    }
  }
}
`


1ページに表示する記事の数は$limit、オフセット値は$skipに格納されるので、受け取った値をallMarkdownRemark()で絞り込みます。

allMarkdownRemark(
  limit: $limit
  skip: $skip
  ...
)


createPageから投げられた値は引数pageContextに格納されるので、それを利用します。

const blogs = ({ pageContext, data, location }) => {
}

ページネーションを出力するコンポーネントを作る

最初と最後、前と次へ移動する簡易的なページネーションの実装の仕方をご紹介します。

entry413 2
/ (プロジェクトディレクトリー)
  └ components/
    └ pagination.js(新規作成)

pagination.jsを作成し、次のコードを記述します。

pagination.js
import { Link } from "gatsby"
import React from "react"
import styled from "styled-components" //追加
const Pagination = ({ num, current, type }) => {
  let first
  let prev
  let next
  let last

  if (current === 1) {
    first = (
      <li className="not-work" key="pagination0">
        <span>最新</span>
      </li>
    )
  } else {
    first = (
      <li key="pagination0">
        <Link to={`/blogs/${type}${type ? "/" : ""}`}>最新</Link>
      </li>
    )
  }

  if (current === 1) {
    prev = (
      <li className="not-work" key="pagination1">
        <span>次へ</span>
      </li>
    )
  } else if (current === 2) {
    prev = (
      <li key="pagination1">
        <Link to={`/blogs/${type}${type ? "/" : ""}`}>次へ</Link>
      </li>
    )
  } else {
    prev = (
      <li key="pagination1">
        <Link to={`/blogs/${type}${type ? "/" : ""}page/${current - 1}/`}>
          次へ
        </Link>
      </li>
    )
  }

  if (current === num) {
    next = (
      <li className="not-work" key="pagination3">
        <span>前へ</span>
      </li>
    )
  } else if (current === "") {
    next = (
      <li key="pagination3">
        <Link to={`/blogs/${type}${type ? "/" : ""}page/2/`}>前へ</Link>
      </li>
    )
  } else {
    next = (
      <li key="pagination3">
        <Link to={`/blogs/${type}${type ? "/" : ""}page/${current + 1}/`}>
          前へ
        </Link>
      </li>
    )
  }

  if (current === num) {
    last = (
      <li className="not-work" key="paginatio4">
        <span>最後</span>
      </li>
    )
  } else {
    last = (
      <li key="pagination4">
        <Link to={`/blogs/${type}${type ? "/" : ""}page/${num}/`}>最後</Link>
      </li>
    )
  }
  console.log(num)
  if (num > 1) {
    return (
      <PaginationWrapper>
        <ul>
          {first}
          {prev}
          <li key="pagination2">
            page {current}/{num}
          </li>
          {next}
          {last}
        </ul>
      </PaginationWrapper>
    )
  } else {
    return ""
  }
}

export default Pagination

const PaginationWrapper = styled.nav`
  ul {
    display: flex;
    list-style: none;
    justify-content: center;

    li {
      padding: 0 10px;

      &.not-work span {
        background: rgb(41, 46, 114);
        color: #fff;
        opacity: 0.5;
      }

      span,
      a {
        text-decoration: none;
        display: flex;
        align-items: center;
        font-weight: 700;
        color: rgb(41, 46, 114);
        border-radius: 8px;
        border: 1px solid rgb(41, 46, 114);
        padding: 0 10px;
      }
    }
  }
`


あとは表示したいところにコードを追記してください。

blog-list.js
// ~ 省略 ~
import Pagination from "../components/blogList/pagination"// ~ 省略 ~

const blogs = ({ pageContext, data, location }) => {
	const { current, page } = pageContext
	// ~ 省略 ~

	return (
		<Layout location={location} title="記事一覧">
			{/* ~ 省略 ~*/}
			<Pagination num={page} current={current} type="" />			{/* ~ 省略 ~*/}
		</Layout>
	)
})


カテゴリやタグ一覧でもページネーションを実装したいときはtypeを追加してください。

<Pagination num={page} current={current} type={cateSlug} />

カテゴリやタグもページネーションを実装したい方はGitHubのpaginationブランチのコードを参考にしてください。

もっと複雑なページネーションを実装したい方へ

記事数が多くなるとより詳細なページネーションが欲しいですよね。

ページネーション

詳細なページネーションのソースはGitHubにあげています。
めちゃくちゃコードが長くなったのでこの記事ではコードは紹介しません。

こちらを参考にしてください。

pagination.js | GitHub

なぜプラグインナシで実装したのか?

今回プラグインナシで実装した理由です。

Gatsbyはプラグインが豊富でいくらでもプラグインで実装できます。プラグインがうまく動かず、やむなく勉強にもなるし自力で実装することにしました。

後々よくコードを見てみたら私の書き方が間違っていただけでした。
はじめて触るGatsbyに疲れていたのもありますが、人間が一番バグります。

実装が面倒な人はgatsby-awesome-paginationあたりが人気なので、プラグインを利用するといいかもしません。

まとめ

ページネーションが実装され、安心してたくさん記事が書けるようになりました。

次の記事は「個別ページテンプレート作成」です。

皆さんのコーディングライフの一助となれば幸いです。

最後までお読みいただきありがとうございました。