Gatsbyブログのページネーションの作り方

blog-hero-img本記事では最初にGatsbyサイトにブログ機能を追加し、次にNPMパッケージなどは使わずにページネーションを実装していきます。

pen-icon2021.11.27

Profile Pic

この記事の筆者:三好アキ(エンジニア)


ウェブデザイナーから『エンジニア』『プログラマー』へ成長したい人、独学で進んでいきたい人を応援しています。 HTMLとCSSの知識だけでアプリ開発を始められる入門書を多数執筆中📕📗👇


ウェブ制作の教本『はじめてつくるReactアプリ』など複数冊を執筆。



React、Next.js、TypeScriptなどのお役立ち情報や実践的コンテンツを、ビギナー向けにかみ砕いて配信中。登録はこちらから → 無料メルマガ登録

ページネーションとは?

記事数が増えたときに、1ページあたりに表示する記事の数を5つや10つなどに制限したいときに使うものがページネーションです。

1 2 3 4 5

Jamstackサイト制作でよく使われるGatsbyにページネーションを追加します。

本記事は、まず最初にGatsbyサイトに最もベーシックなブログ機能を実装し、そこにページネーションを追加するという流れです。

ロジック部分の説明だけなので、スタイルは各自お好みで適用してください。

なお、本記事ではGatsbyの最新バージョン(2021年11月時点)であるv4を使いますが、v2でもv3でも本記事と同じコードでページネーションの設置が可能です。

なお前後の記事へと移動する仕組みは、こちらの記事を参考にしてください。

ベーシックなブログ機能の作成

まずGatsbyのスターターサイトに、マークダウンのブログ機能を追加しましょう。

最初にスターターサイトをダウンロードするので、ターミナルで以下のコマンドを実行します。

npx gatsby new gatsby-starter-default https://github.com/gatsbyjs/gatsby-starter-default

次にブログ記事を一覧表示するblog.js、個別記事を表示するsingleBlog.jsを作ります。

まずはblog.jsから始めます。


├──pages
│       ├── 404.js   
│       ├── blog.js    ←追加
│       ├── index.js  
│       ├── pages-2.js   
│       ├── using-ssr.js  
│       └── using-typescript.tsx 

次のコードを書きます。

// blog.js

import * as React from "react"
import { graphql, Link } from "gatsby"
 
const Blog = (props) => {
    return (
        <div>    
            <h1>ブログページ</h1>
            {props.data.allMarkdownRemark.edges.map((singleBlog, index) => 
                <div key={index}>              
                    <h2><Link to={`/blog${singleBlog.node.fields.slug}`}>{singleBlog.node.frontmatter.title}</Link></h2>       
                    <p>{singleBlog.node.frontmatter.date}</p>      
                </div>              
            )}  
        </div>  
    )
}

export default Blog

export const query = graphql`
    query BlogQuery { 
        allMarkdownRemark(
            sort: {fields: frontmatter___id, order: DESC}     
        ) {
            edges {
                node {
                    fields {
                        slug
                    }
                    frontmatter {
                        date
                        summary
                        id
                        title
                    }
                }
            }
        }
    }
`

singleBlog.jstemplatesフォルダ内に作ります。


├──templates 
│       ├── singleBlog.js    ←追加  
│       └── using-dsg.js

次のコードを書きます。

// singleBlog.js

import * as React from "react"
import { graphql } from "gatsby"
 
const SingleBlog = (props) => {
    return (
        <div>                       
            <h1>{props.data.markdownRemark.frontmatter.title}</h1> 
            <p>{props.data.markdownRemark.frontmatter.date}</p> 
            <div dangerouslySetInnerHTML={{ __html: props.data.markdownRemark.html }} />  
        </div>     
    )
}

export default SingleBlog

export const query = graphql`
    query SingleBlogQuery($slug: String!) {
        markdownRemark(fields: { slug: { eq: $slug } }) {
            frontmatter {
                date
                title
                summary
                id
            }
            html
        }
    }
`

次に、ブログ記事のマークダウンファイルを収納するフォルダをsrc内に作ります。


├──src
│  ├── components    
│  ├── data    ←追加
│  ├── images  
│  ├── pages   
│  └── templates    

data内には6つのマークダウンファイルを作ります。


├── data
│      ├── first.md    
│      ├── second.md  
│      ├── third.md   
│      ├── fourth.md  
│      ├── fifth.md   
│      └── sixth.md    

それぞれのマークダウンファイルには、下のように基本的な情報を書きます。

// first.md

---
id: "1"
title: "記事1"
date: "2021-06-21"
summary: "記事1の要約"
---

1つ目の記事。
// second.md

---
id: "2"
title: "記事2"
date: "2021-06-22"
summary: "記事2の要約"
---

2つ目の記事。
// third.md

---
id: "3"
title: "記事3"
date: "2021-06-23"
summary: "記事3の要約"
---

3つ目の記事。
// fourth.md

---
id: "4"
title: "記事4"
date: "2021-06-24"
summary: "記事4の要約"
---

4つ目の記事。
// fifth.md

---
id: "5"
title: "記事5"
date: "2021-06-25"
summary: "記事5の要約"
---

5つ目の記事。
// sixth.md

---
id: "6"
title: "記事6"
date: "2021-06-26"
summary: "記事6の要約"
---

6つ目の記事。

これでブログのデータと、それを表示する部分ができました。

次は表示するための仕組みづくりです。

ブログ機能に必要なプラグインをインストールします。

npm install gatsby-transformer-remark

これと、デフォルトですでに入っているgatsby-source-filesystemgatsby-config.jsに追加します。

// gatsby-config.js

module.exports = {
  siteMetadata: {
     
      ...

  plugins: [
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-image`,
    // ⬇ 追加
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `data`,
        path: `${__dirname}/src/data`,
      },
    },
    `gatsby-transformer-remark`,
    // ⬆ 追加
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    `gatsby-transformer-sharp`,

    ...

gatsby-node.jsに、上で作ったコンポーネントにマークダウンファイルを流し込むためのコードを追加します。

// gatsby-node.js

// ⬇ 削除
exports.createPages = async ({ actions }) => {
  const { createPage } = actions
  createPage({
    path: "/using-dsg",
    component: require.resolve("./src/templates/using-dsg.js"),
    context: {},
    defer: true,
  })
}
// ⬆ 削除

const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode, actions }) => {
    const { createNodeField } = actions
  
    if (node.internal.type === `MarkdownRemark`) {
        const slug = createFilePath({ node, getNode })   
        createNodeField({         
            node,                   
            name: `slug`,           
            value: slug,            
        })
    }
}

exports.createPages = async ({ graphql, actions }) => {  
    const { createPage } = actions   

    const result = await graphql(`              
        query {
            allMarkdownRemark {
                edges {
                    node {
                        fields {
                            slug
                        }
                    }
                }
            }
        }
    `)
  
    result.data.allMarkdownRemark.edges.forEach(({ node }) => {
        createPage({
            path: `blog${node.fields.slug}`,
            component: path.resolve(`./src/templates/singleBlog.js`),
            context: {
                slug: node.fields.slug,
            },
        })
    })
}

ここまで加えた変更をすべて保存します。

gatsby developでGatsbyを起動し、http://localhost:8000/blogをブラウザで開くと、ブログ機能が追加されているのがわかります。

これ以降、本題のページネーションを追加していきます。

ページネーションに必要なもの

ページネーションには次の3つが必要です。


1 gatsby-node.jsblog.jsに表示する記事数を制限する仕組み

2 blog.jsファイルの移動

3 paginationコンポーネント


それでは順番通りに作っていきます。

gatsby-node.jsで記事数を制限する

まず1つ目の、表示する記事数を制限するコードをgatsby-node.jsに追加します。

ここではblog.jsに表示される記事の数は2つとし、3つ目以降は次のページへと表示させます。

// gatsby-node.js

...

    result.data.allMarkdownRemark.edges.forEach(({ node }) => {
            createPage({
                path: `blog${node.fields.slug}`,
                component: path.resolve(`./src/templates/singleBlog.js`),
                context: {
                    slug: node.fields.slug,
                },
            })
        })

    ⬇追加
    const blogs = result.data.allMarkdownRemark.edges
    const blogsPerPage = 2
    const numberPages = Math.ceil(blogs.length / blogsPerPage)
    Array.from({ length: numberPages }).forEach((_, i) => {
        createPage({
            path: i === 0 ? `/blog` : `/blog/${i + 1}`,
            component: path.resolve(`./src/templates/blog.js`),
            context: {
                limit: blogsPerPage,
                skip: i * blogsPerPage,
                numberPages,
                currentPage: i + 1,
            },
        })
    })
    ⬆追加

}

これにより、2記事ごとに新しいページが作られます。

limitskipがあるのは、blog.jsのGraphQLクエリで使うためで、contextの中に書いてblog.jsへと渡します。

blog.jsを移動する

ページネーション用に新しいblog-list.jsのようなコンポーネントを作り、そこに記事を表示することも可能ですが、今回はすでにあるblog.jsを使います。

しかしそうすると、いま打ったcreatePageのコードで作った/blogのURLと、pagesフォルダにあるblog.jsのURL/blogが衝突するので、blog.jstemplatesフォルダへと移します。

src
.
.
├── templates 
│           ├── blog.js    ←pagesから移動 
│           ├── singleBlog.js 
│           └── using-dsg.js   
.
.

そしてblog.jsには次のコードを追加します。

// blog.js

import * as React from "react"
import { graphql, Link } from "gatsby"
 
const Blog = (props) => {
    return (
        <div>    
            <h1>ブログページ</h1>
            {props.data.allMarkdownRemark.edges.map((singleBlog, index) => 
                <div key={index}>              
                    <h2><Link to={`/blog${singleBlog.node.fields.slug}`}>{singleBlog.node.frontmatter.title}</Link></h2>       
                    <p>{singleBlog.node.frontmatter.date}</p>      
                </div>              
            )}  
        </div>  
    )
}

export default Blog

export const query = graphql`
    query BlogQuery ($skip: Int!, $limit: Int!) {       // 追加
        allMarkdownRemark(
            sort: {fields: frontmatter___id, order: DESC}
            limit: $limit                               // 追加
            skip: $skip                                 // 追加
        ) {
            edges {
                node {
                    fields {
                        slug
                    }
                    frontmatter {
                        date
                        summary
                        id
                        title
                    }
                }
            }
        }
    }
`

paginationコンポーネントの作成

paginationコンポーネントをcomponentsフォルダに作ります。

src
.
.
├── components    
│      ├── header.js
│      ├── layout.css
│      ├── layout.js  
│      ├── pagination.js    ←追加    
│      └── seo.js
.
.

そこに次のコードを書きます。

// pagination.js

import * as React from "react"
import { Link } from 'gatsby'

const Pagination = ({ pageContext }) => {
    const { numberPages } = pageContext
    return (
        <h2>
            {Array.from({ length: numberPages }, (_, i) => (
                <Link key={`pagination-number${i + 1}`} to={`/blog/${i === 0 ? "" : i + 1}`}>
                {i + 1}
                </Link>
            ))}
        </h2>
    )
}

export default Pagination

そしてblog.jsに、いま作ったpaginationコンポーネントを読み込みます。

// blog.js

import * as React from "react"
import { graphql, Link } from "gatsby"
import Pagination from "../components/pagination"   // 追加
 
const Blog = (props) => {
    const { pageContext } = props   // 追加
    return (
        <div>    
            <h1>ブログページ</h1>
            {props.data.allMarkdownRemark.edges.map((singleBlog, index) => 
                <div key={index}>              
                    <h2><Link to={`/blog${singleBlog.node.fields.slug}`}>{singleBlog.node.frontmatter.title}</Link></h2>       
                    <p>{singleBlog.node.frontmatter.date}</p>      
                </div>              
            )} 
            <Pagination pageContext={pageContext} />    // 追加
        </div>  
    )
}

export default Blog

export const query = graphql`
    query BlogQuery ($skip: Int!, $limit: Int!) {      
        allMarkdownRemark(
            sort: {fields: frontmatter___id, order: DESC}
            limit: $limit                              
            skip: $skip                               
        ) {
            edges {
                node {
                    fields {
                        slug
                    }
                    frontmatter {
                        date
                        summary
                        id
                        title
                    }
                }
            }
        }
    }
`

変更を保存してgatsby developでGatsbyを起動させ、ブラウザでhttp://localhost:8000/blogを開くと、ページネーションが追加されているのがわかります。

本記事では細かい説明を省略して足早に解説しましたが、Gatsbyについてよりくわしく知りたい方は、拙著「はじめてつくるGatsbyサイト」を参考にしてください。

なお、Gatsbyブログの記事ページ(singleBlog.js)に前後の記事へのリンクを追加する方法は、こちらの記事を参考にしてください。

その他、Gatsbyに興味のある人は次の記事も参考にしてください。