microCMS

Astroのpaginate() × microCMSを用いてページネーションを実装する

かわにし

はじめに

株式会社メンバーズの川西です。
普段はフロントエンドエンジニアとして、主にmicroCMSとAstroを組み合わせたJamstack構成のWebサイト開発や、技術的なディレクションを担当しています。

この記事では、Astro の機能の一つであるpaginate()関数を活用して、microCMSから取得した記事データをページネーション表示する方法をご紹介します。

paginate() 関数の活用パターンとしては、ページ番号ごとに遷移する「ページ送り」と、続きを表示するタイプの「もっと見る」の実装が可能です。今回は「ページ送り」の実装方法をご紹介します。

完成イメージは次の通りです。

準備

AstroとmicroCMSのセットアップ

この記事では、以下の環境がすでに構築済みであることを前提として説明を進めます。
まだ準備がお済みでない方は、まず「AstroとmicroCMSでつくるブログサイト」を参考に、開発環境の立ち上げとmicroCMSの設定を行ってください。

  • Astroの開発環境立ち上げ
  • microCMSアカウントとブログ記事のサンプルデータ作成
  • AstroとmicroCMSの接続設定

ソフトウェアバージョン

この記事で使用しているパッケージのバージョンは以下の通りです。

  • astro:v5.5.4
  • microcms-js-sdk:v3.2.0
  • node.js:v20.15.0

実装手順

事前準備:microCMSのサンプル記事データ

ページネーションの動作を確認するために、microCMSにある程度の記事データが必要です。
「AstroとmicroCMSでつくるブログサイト」で作成したブログ記事を追加し、合計9記事程度ご用意ください。

1. ページネーション用の動的ルートページ([page].astro)を作成

Astroの動的ルーティングと同様に、ブラケット構文とgetStaticPaths()関数を使って、ファイル名に動的URLパラメータを指定してページネーションのページを作成します。

ページネーションページは[page].astroというファイル名で作成します。
[page] 部分が生成されるページ番号に対応する動的なパラメータです。/page/[page].astroというページの場合、URLのパスは/page/1/page/2…となります。

通常の動的ルーティングはブラケット部分の名前を[id][tag]など別名にすることができますが、paginate関数の場合は、[page]部分を任意の名前に設定することはできませんのでご注意ください。

今回はsrc/pages/page配下に[page].astroファイルを作成します。
各ページにはそれぞれ記事タイトルを3件ずつ表示するようにし、タイトルには詳細ページへのリンクを設定します。

---
import Layout from '../../layouts/Layout.astro';
import { getBlogs } from '../../library/microcms';
import type { GetStaticPathsOptions } from 'astro';
import type { MicroCMSListResponse } from 'microcms-js-sdk';
import { type Blog } from '../../library/microcms';

export const getStaticPaths = async ({ paginate }: GetStaticPathsOptions) => {
 const response: MicroCMSListResponse<Pick<Blog, 'title'>> = await getBlogs({ fields: ['id', 'title'] });
 return paginate(response.contents, { pageSize: 3 });
};

const { page } = Astro.props;
---

<Layout title={`${page.currentPage}ページ目`}>
 <main>
   <h1>{page.currentPage}ページ</h1>
   <ul>
     {
       page.data.map(({ title, id }) => (
         <li>
           <a href={`/${id}/`}>{title}</a>
         </li>
       ))
     }
   </ul>
 </main>
</Layout>

<style>
 main {
   margin: auto;
   padding: 1em;
   max-width: 60ch;
 }
</style>


  • getStaticPaths : Astroの静的サイト生成時に実行される関数です。
  • getBlogs : microCMSからブログ記事データ一覧を取得する非同期関数です。ここでは、記事のidとtitleのみを取得しています。
  • paginate(response.contents, { pageSize: 3 }) : Astroのページネーション関数(paginate())です。ページネーション関数に取得した記事データ(response.contents)を渡します。pageSizeオプションで、1ページに何件の記事を表示するかを指定します。
  • const { page } = Astro.props : Propsの値を受け取ります。ここで受け取ったpageプロパティには、現在のページ数や分割された現在のページに表示するための記事データなどが含まれています。
  • page.data : 現在のページに表示する記事データの配列です。
  • page.currentPage : 現在のページ番号です。


この状態でブラウザ(http://localhost:4321/page/1)を確認すると、microCMSから取得した記事の一覧が3記事ずつ表示されていることが確認できます。

2. ページ移動ボタンを作成

続いて、ページを移動するための「前のページ」「次のページ」ボタンと、ページ番号を表示する要素を追加します。

src/pages/page/[page].astroファイルの </ul> タグと </ main> タグの間にページ移動ボタン要素を追加します。
ページ番号は全て表示し、現在のページ番号にはアクティブ時のスタイルを設定します。
そして、前のページまたは次のページが存在しない場合は、クリックができないように設定します。

// src/pages/page/[page].astro
{/* 省略... */}
<Layout title={`${page.currentPage}ページ目`}>
 <main>
   <h1>{page.currentPage}ページ</h1>
   <ul>
     {
       page.data.map(({ title, id }) => (
         <li>
           <a href={`/${id}/`}>{title}</a>
         </li>
       ))
     }
   </ul>

   {/* ここからページ移動ボタン要素を追加 */}
   <div>
     <a href={page.url.prev} class:list={['setPage', { invalid: !page.url.prev }]}>{'< 前のページ'}</a>
     {
       [...Array(page.lastPage)].map((_, i) => (
         <a class:list={['pageNumber', { currentPage: page.currentPage === i + 1 }]} href={`/page/${i + 1}/`}>
           {i + 1}
         </a>
       ))
     }
     <a href={page.url.next} class:list={['setPage', { invalid: !page.url.next }]}>{'次のページ >'}</a>
   </div>
   {/* ここまで */}
  
 </main>
</Layout>
{/* 省略... */}


pageプロパティは、前述の通り現在のページ数や分割された現在のページに表示するための記事データに加え、ページボタンに必要なデータも含まれています。

  • page.url.prev : 前のページのURLです。存在しない場合は undefined になります。
  • page.url.next : 次のページのURLです。存在しない場合は undefined になります。
  • page.lastPage:ページネーションの最終ページ番号です。


src/pages/page/ [page].astroファイルの <style> タグにCSSを追加します。

<style>
 main {
   margin: auto;
   padding: 1em;
   max-width: 60ch;
 }

 {/* ここからページ移動ボタン要素を追加 */}
 .pageNumber {
   color: black;
   text-decoration: none;
   padding: 4px 8px;
   display: inline-block;
   text-align: center;
 }

 .currentPage {
   font-weight: 700;
   border-bottom: 1px solid black;
 }

 .setPage {
   color: black;
   text-decoration: none;
   border: 1px solid black;
   padding: 8px;
 }

 .invalid {
   color: gray;
   border: 1px solid gray;
 }
 {/* ここまで */}

</style>


作成した「前のページ」「次のページ」「ページ番号」ボタンをクリックして、ページが意図した通りに切り替わることを確認できれば、ページネーションの実装は成功です。

最終的にコードはこのようになります。

// src/pages/page/[page].astro
---
import Layout from '../../layouts/Layout.astro';
import { getBlogs } from '../../library/microcms';
import type { GetStaticPathsOptions } from 'astro';
import type { MicroCMSListResponse } from 'microcms-js-sdk';
import { type Blog } from '../../library/microcms';

export const getStaticPaths = async ({ paginate }: GetStaticPathsOptions) => {
 const response: MicroCMSListResponse<Pick<Blog, 'title'>> = await getBlogs({ fields: ['id', 'title'] });
 return paginate(response.contents, { pageSize: 3 });
};

const { page } = Astro.props;
---

<Layout title={`${page.currentPage}ページ目`}>
 <main>
   <h1>{page.currentPage}ページ</h1>
   <ul>
     {
       page.data.map(({ title, id }) => (
         <li>
           <a href={`/${id}/`}>{title}</a>
         </li>
       ))
     }
   </ul>
   <div>
     <a href={page.url.prev} class:list={['setPage', { invalid: !page.url.prev }]}>{'< 前のページ'}</a>
     {
       [...Array(page.lastPage)].map((_, i) => (
         <a class:list={['pageNumber', { currentPage: page.currentPage === i + 1 }]} href={`/page/${i + 1}/`}>
           {i + 1}
         </a>
       ))
     }
     <a href={page.url.next} class:list={['setPage', { invalid: !page.url.next }]}>{'次のページ >'}</a>
   </div>
 </main>
</Layout>

<style>
 main {
   margin: auto;
   padding: 1em;
   max-width: 60ch;
 }

 .pageNumber {
   color: black;
   text-decoration: none;
   padding: 4px 8px;
   display: inline-block;
   text-align: center;
 }

 .currentPage {
   font-weight: 700;
   border-bottom: 1px solid black;
 }

 .setPage {
   color: black;
   text-decoration: none;
   border: 1px solid black;
   padding: 8px;
 }

 .invalid {
   color: gray;
   border: 1px solid gray;
 }
</style>


展開:indexページにページネーション1ページ目を表示する

デフォルトの実装では、ページネーションの最初のページはhttp://localhost:4321/page/1というURLになります。
しかし、多くのブログサイトでは、最初のページをトップページ(http://localhost:4321/)に表示することが一般的です。(microCMSブログと同様のイメージです。)
そのための応用的な実装方法をご紹介します。

実装手順

1. ページネーションのコンポーネント化

まずは、[page].astroのページネーションのUI部分を、再利用可能なコンポーネントとして作成します。
src/components/配下にPagination.astroというファイルを作成します。

--
import type { MicroCMSContentId, MicroCMSDate } from 'microcms-js-sdk';
import { type Blog } from '../library/microcms';

interface Props {
 data: (Pick<Blog, 'title'> & {
   title: string;
 } & MicroCMSContentId &
   MicroCMSDate)[];
 currentPage: number;
 lastPage: number;
 urlPrev: string | undefined;
 urlNext: string | undefined;
}

const { data, currentPage, lastPage, urlPrev, urlNext } = Astro.props;
---

<h1>{currentPage}ページ</h1>
<ul>
 {
   data.map(({ title, id }) => (
     <li>
       <a href={`/${id}/`}>{title}</a>
     </li>
   ))
 }
</ul>
<div>
 <a href={urlPrev} class:list={['setPage', { invalid: !urlPrev }]}>{'< 前のページ'}</a>
 {
   [...Array(lastPage)].map((_, id) => (
     <a class:list={['pageNumber', { active: currentPage === id + 1 }]} href={id === 0 ? '/' : `/page/${id + 1}/`}>
       {id + 1}
     </a>
   ))
 }
 <a href={urlNext} class:list={['setPage', { invalid: !urlNext }]}>{'次のページ >'}</a>
</div>

<style>
 .pageNumber {
   color: black;
   text-decoration: none;
   padding: 4px 8px;
   display: inline-block;
   text-align: center;
 }

 .active {
   font-weight: 700;
   border-bottom: 1px solid black;
 }

 .setPage {
   color: black;
   text-decoration: none;
   border: 1px solid black;
   padding: 8px;
 }

 .invalid {
   color: gray;
   border: 1px solid gray;
 }
</style>


2. コンポーネントの組み込み

続いて、作成したPaginationコンポーネントを、既存ファイルのsrc/pages/page/[page].astroとsrc/pages/index.astroに組み込みます。
これにより、http://localhost:4321/page/1とhttp://localhost:4321/のページで同じ内容が表示されるようになります。

そして、ページネーションの1ページ目は必ずhttp://localhost:4321/(src/pages/index.astroファイル)を表示させたいので、
ページ遷移ボタンの「前のページ」と「次のページ」とページ番号「1」のリンクを修正し、1ページ目への遷移先をhttp://localhost:4321/に統一します。

// src/pages/page/[page].astro
---
import Pagination from '../../components/Pagination.astro';
import Layout from '../../layouts/Layout.astro';
import { getBlogs } from '../../library/microcms';
import type { GetStaticPathsOptions } from 'astro';
import type { MicroCMSListResponse } from 'microcms-js-sdk';
import { type Blog } from '../../library/microcms';

export const getStaticPaths = async ({ paginate }: GetStaticPathsOptions) => {
 const response: MicroCMSListResponse<Pick<Blog, 'title'>> = await getBlogs({ fields: ['id', 'title'] });
 return paginate(response.contents, { pageSize: 3 });
};

const { page } = Astro.props;
const isFirstPage = page.url.prev === '/page/1';
---

<Layout title={`${page.currentPage}ページ目`}>
 <main>
   <Pagination
     data={page.data}
     currentPage={page.currentPage}
     lastPage={page.lastPage}
     urlPrev={`${isFirstPage ? '/' : page.url.prev}`}
     urlNext={page.url.next}
   />
 </main>
</Layout>
<style>
 main {
   margin: auto;
   padding: 1em;
   max-width: 60ch;
 }
</style>

// src/pages/index.astro-
--
import Layout from '../layouts/Layout.astro';
import Pagination from '../components/Pagination.astro';
import { getBlogs } from '../library/microcms';

// microCMSからPageSize分の記事を取得
const page = await getBlogs({ fields: ['id', 'title'], limit: 3 });
// 最終ページ番号
const lastPage = Math.ceil(page.totalCount / 3);
// 次のページのURL
const urlNext = lastPage > 1 ? '/page/2' : undefined;
---

<Layout title="My first blog with Astro">
 <main>
   <Pagination data={page.contents} currentPage={1} lastPage={lastPage} urlPrev={undefined} urlNext={urlNext} />
 </main></Layout
>

<style>
 main {
   margin: auto;
   padding: 1em;
   max-width: 60ch;
 }
</style>


ブラウザでは、トップページ(http://localhost:4321/) にページネーションの1ページ目が表示され、ページ遷移ボタンの「前のページ」と「次のページ」とページ番号「1」からもトップページに遷移できることが確認できます。

3. リダイレクト設定

Astroのページネーションページファイルは、paginate() 関数によって生成されるため、1ページ目のファイルが必ず作成されます。
そのため、http://localhost:4321/page/1にアクセスした場合でも、トップページのURL(http://localhost:4321/)にリダイレクトされるように設定します。

Astro側のリダイレクト設定

Astroの設定ファイルastro.config.mjsredirectsに設定を追加します。

import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
 redirects: {
   '/page/1': { // リダイレクト元のパス
     status: 301, // リダイレクトのステータスコード
     destination: '/' // リダイレクト先のパス
   }
 }
});


ホスティングサーバー(Netlify)側のリダイレクト設定

Astroのリダイレクト機能は、ビルドの際にmeta refreshタグがHTMLに出力される仕組みとなっています。
meta refreshは、リダイレクト元のサイトのSEO評価をリダイレクト先のサイトに引き継ぎできない点や、転送までのタイムラグが発生するなど、デメリットもあります。
そのため、可能な限りサーバー側でリダイレクトを設定する方法を利用しましょう。

サーバー側でHTTP 301レスポンスによるリダイレクトを行う場合は、ホスティングサービスにて設定を行います。
一例としてNetlifyの_redirectsファイルを使用したリダイレクト設定をご紹介します。

// _redirects
/page/1/ / 301!


リダイレクト元のパス(/page/1/)、リダイレクト先のパス(/)、リダイレクトステータス(301)、forceの指定(!)を設定しています。
forceは、既存のファイルが存在してもリダイレクトを強制的にリダイレクトを実行するかどうかを設定する項目です(デフォルトはfalse)。/page/1/が存在しているので、forceの設定をtrue(!)に指定しています。


終わりに

Astroのpageオプションは、ページネーションに必要なデータを簡単に取得できるため、複雑なロジックを自前で実装する手間を大幅に削減できます。
また、paginate() 関数はAstroのパーシャルページと組み合わせることで、続きを表示するタイプの「もっと見る」の実装も可能です。

今回ご紹介した実装方法がWeb開発の現場で少しでもお役に立てれば幸いです。今後も Astro の最新機能や活用事例など、様々な情報を共有していきたいと思いますので、ぜひご期待ください。

まずは、無料で試してみましょう。

APIベースの日本製ヘッドレスCMS「microCMS」を使えば、 ものの数分でAPIの作成ができます。

microCMSを無料で始める

microCMSについてお問い合わせ

初期費用無料・14日間の無料トライアル付き。ご不明な点はお気軽にお問い合わせください。

お問い合わせ

microCMS公式アカウント

microCMSは各公式アカウントで最新情報をお届けしています。
フォローよろしくお願いします。

  • X
  • Discord
  • github

ABOUT ME

かわにし
株式会社メンバーズ メンバーズルーツカンパニーでフロントエンドエンジニアおよびディレクターをしています。趣味は散歩と旅行です。
株式会社メンバーズ 認定パートナー
運用で社会を変革するデジタルクリエイター集団です。弊社は、microCMSを活用して、国内金融企業さまへの『デジタル運用特化』でお客様のビジネス変革・成果向上の内製化を推進します。
https://www.members.co.jp/
制作を依頼する