Astroのサイトにページネーションを実装する方法【コピペ用コード付き】

Astroのサイトにページネーションを実装する方法【コピペ用コード付き】

Astroで制作したサイトに、1, 2, 3…のような番号付きのページネーションを実装したいという場合があるかと思います。

ページネーションの実装方法については、Astro公式のドキュメントでも紹介されています。

しかし、[前へ][次へ]だけのシンプルなものなので、当記事では、

  • [最後へ][最初へ]のリンク
  • [次へ][前へ]のリンク
  • [1][2][3]のような番号のリンク

を全て含んだページネーションを作成する方法を紹介します!

検証環境

当記事で紹介する方法は、以下のバージョンで動作確認を行っています。

検証環境のバージョン情報

Astro
Astro 5.1.3

また、記事のフォーマットはMDXを使用していますが、ヘッドレスCMSから記事を取得する場合も同様に実装可能です。

バージョンの違いによっては、この記事の通りに動作しない可能性がありますので、ご理解いただけますと幸いです。

ページネーションを設置するための記事一覧ページを作成

まずは、ページネーションを設置するための記事一覧ページを作成します。

  1. 記事一覧ページ[...page].astroを作成
  2. getStaticPathsでページネーションのパスを生成
  3. ページごとの記事一覧を表示
  4. ページネーションのコンポーネントを設置

1. 記事一覧ページ[...page].astroを作成

記事一覧ページは、

  • /blog/:1ページ目
  • /blog/2:2ページ目
  • /blog/3:3ページ目

となるようにしたいので、/src/pages/blog/ディレクトリに[...page].astro、もしくは[page].astroを作成します。

blogの部分は自由に変えてください。

[page][...page]はどちらを使うべき?

日本語のページネーション部分のドキュメントでは、[...page].astroではなく[page].astroが使用されていますが、この場合、

  • /blog/1
  • /blog/2
  • /blog/3

というパスが生成されます。

しかし多くの場合、/blog/1/blog/と同じ内容になるので、/blog/1は必要ないことが多いですよね。

実際にログを出力してみると、以下のようになります。

    # [page].astroとした場合
[
  { params: { page: '1' }, props: { page: [Object] } },
  { params: { page: '2' }, props: { page: [Object] } },
  { params: { page: '3' }, props: { page: [Object] } }
]
 
# [...page].astroとした場合
[
  { params: { page: undefined }, props: { page: [Object] } },
  { params: { page: '2' }, props: { page: [Object] } },
  { params: { page: '3' }, props: { page: [Object] } }
]
  

[page]では/blog/1のパスが生成されてしまいますが、[...page]とすると、/blog/1が消えて、/blog/が1ページ目になります。

これは英語版のドキュメントにのみ記載されていましたので、見落としがちなポイントですね・・。

もし/blog//blog/1の内容を別にするならば[page].astroでもいいのですが、そうでない場合はコンテンツの重複を防ぐため、[...page].astroを使用することをおすすめします。

2. getStaticPathsでページネーションのパスを生成

ここからは、作成した[...page].astroページの中身を作成していきます。

まずは、getStaticPathsを使用して、ページネーションのパスを生成します。

/src/pages/blog/[...page].astro
Astro
    ---
// Astroのコンテンツコレクションを使用している場合は、getCollectionをインポート
import { getCollection } from 'astro:content';
 
export async function getStaticPaths({ paginate }) {
  // 記事のコレクションを取得 + 下書き記事を除外する
  const posts = await getCollection('blog', ({ data }) => {
    return data.isDraft === false;
  });
 
  // 記事を新しい順に並び替え
  const sortedPosts = posts.sort((a, b) => {
    return new Date(b.data.publishedAt) - new Date(a.data.publishedAt);
  });
 
  // ページネーションのパスを生成 + 1Pごとの記事数を指定
  return paginate(sortedPosts, { pageSize: 10 });
}
 
const { page } = Astro.props;
---
  

それぞれ分解しながら説明していきます。

getCollectionで記事を取得

当記事ではAstroの コンテンツコレクション を利用しているため、getCollectionを使用して記事を取得しています。

/src/pages/blog/[...page].astro
Astro
    ---
import { getCollection } from 'astro:content';
 
export async function getStaticPaths({ paginate }) {
  const posts = await getCollection('blog', ({ data }) => {
    return data.isDraft === false;
  });
  // 省略
}
---
  

ヘッドレスCMSなど、コンテンツコレクション以外で記事を管理している場合は、別の方法で記事を取得してください。

記事を新しい順に並び替え

取得した記事を新しい順に並び替えるために、publishedAtを基準にsortメソッドを使用しています。

/src/pages/blog/[...page].astro
Astro
    ---
const sortedPosts = posts.sort((a, b) => {
  return new Date(b.data.publishedAt) - new Date(a.data.publishedAt);
});
---
  

こちらもそれぞれの方法で並び替えを行なってください。

ページネーションのパスを生成

Astroにはページネーションを生成するためのpaginate関数が用意されています。

/src/pages/blog/[...page].astro
Astro
    ---
export async function getStaticPaths({ paginate }) {
 
  // 省略
  const sortedPosts = ...;
 
  return paginate(sortedPosts, { pageSize: 10 });
}
---
  

paginate()には、取得した記事の配列と、1ページあたりの記事数を指定します。

ここでは10としているので、全記事を10記事ずつに分割するのに必要なページ分のパスを生成します。

検証環境では23記事あるので、paginate()で3ページ分のパスが生成されます。

    [
  { params: { page: undefined }, props: { page: [Object] } },
  { params: { page: '2' }, props: { page: [Object] } },
  { params: { page: '3' }, props: { page: [Object] } }
]
  

これでページネーションのパスが生成されました!

/blog/○のページにアクセスできるようになりましたが、まだページの中身は空なので、次で作成していきます。

3. ページごとの記事一覧を表示

1ページ目には1〜10番目の記事、2ページ目には11〜20番目の記事・・・というように、記事の一覧データを表示していきます。

/src/pages/blog/[...page].astro
Astro
    ---
import { getCollection } from 'astro:content';
import Layout from '@/layouts/Layout.astro';
 
export async function getStaticPaths({ paginate }) {
  const posts = await getCollection('blog', ({ data }) => {
    return data.isDraft === false;
  });
 
  const sortedPosts = posts.sort((a, b) => {
    return new Date(b.data.publishedAt) - new Date(a.data.publishedAt);
  });
 
  return paginate(sortedPosts, { pageSize: 10 });
}
 
const { page } = Astro.props;
---
 
<Layout>
  <h1>記事一覧</h1>
  <ul>
    {
      page.data.map((post) => {
        return (
          <li>
            <a href={`/blog/${post.slug}`}>
              <h2>{post.data.title}</h2>
              <p>{post.data.description}</p>
            </a>
          </li>
        )
      })
    }
  </ul>
</Layout>
  

記事のデータは、page.dataに配列として格納されているので、mapメソッドを使用して一覧表示しています。

ブログカードに表示する内容やレイアウトは、各自のデザインに合わせて変更してください。

console.log(page.data)で中身を確認しながら調整すると良いでしょう。

これで、ページごとの記事一覧を表示することができました!

4. ページネーションのコンポーネントを設置

最後に、ページネーションのコンポーネントを設置します。

/src/pages/blog/[...page].astro
Astro
    ---
import { getCollection } from 'astro:content';
import Layout from '@/layouts/Layout.astro';
import Pagination from '@/components/Pagination.astro';
 
export async function getStaticPaths({ paginate }) {
  // 省略
}
 
const { page } = Astro.props;
---
 
<Layout>
  <h1>記事一覧</h1>
  <!-- 記事一覧を表示 -->
  <ul></ul>
 
  <Pagination page={page} baseUrl="/blog" />
</Layout>
  

ページネーション作成に必要なデータはpageプロパティに格納されているので、次で作成する<Pagination />コンポーネントにpageを渡します。

追記: baseUrlの設定

また、baseUrlはページネーションのベースとなるURLを指定します。

こうすることで、サイト内に複数のページネーションを設置する際にも、同じコンポーネントを使用できるようになります。

ページネーションコンポーネントを作成

ここからは、ページネーションのコンポーネントを作成します。

簡易的ですが、以下のようなデザインにしてみます。

ページネーションのデザイン

src内の任意のディレクトリで、Pagination.astroというファイルを作成してください。

ここでは、src/components/Pagination.astroとしています。

まずはコード全体の紹介から。

/src/components/Pagination.astro
Astro
    ---
// paginate()関数で取得したpageプロパティ
const {page, baseUrl} = Astro.props;
 
// ページネーションの総ページ数
const totalPages = page.lastPage;
 
// ページ番号の配列 [1, 2, 3]のような形式で生成
const pageNumbers = Array.from({length: totalPages}, (_, i) => {
  return i + 1;
});
 
// ページ番号からページURLを生成
const getPageUrl = (pageNumber) => {
  return pageNumber === 1 ? page.url.first : `${baseUrl}/${pageNumber}`;
}
---
 
<nav class="pagination" aria-label="ページネーション">
 
  <ul class="list">
    <!-- 最初のページ -->
    <li class="item">
      {
        page.url.first ? (
          <a class="link" href={page.url.first}>&lt;&lt;</a>
        ) : (
          <span class="link">&lt;&lt;</span>
        )
      }
    </li>
 
    <!-- 前のページ -->
    <li class="item">
      {
        page.url.prev ? (
          <a class="link" href={page.url.prev}>&lt;</a>
        ) : (
          <span class="link">&lt;</span>
        )
      }
    </li>
 
    <!-- ○番目のページ -->
    {
      pageNumbers.map((pageNumber) => {
        return (
          <li class="item">
            {
              page.currentPage === pageNumber ? (
                <span class="link current" aria-current="page">{pageNumber}</span>
              ) : (
                <a class="link" href={getPageUrl(pageNumber)}>{pageNumber}</a>
              )
            }
          </li>
        );
      })
    }
 
    <!-- 次のページ -->
    <li class="item">
      {
        page.url.next ? (
          <a class="link" href={page.url.next}>&gt;</a>
        ) : (
          <span class="link">&gt;</span>
        )
      }
    </li>
 
    <!-- 最後のページ -->
    <li class="item">
      {
        page.url.last ? (
          <a class="link" href={page.url.last}>&gt;&gt;</a>
        ) : (
          <span class="link">&gt;&gt;</span>
        )
      }
    </li>
  </ul>
</nav>
 
<style>
  .pagination {
    width: 100%;
 
    .list {
      display: flex;
      justify-content: center;
      gap: 1rem;
    }
 
    .link {
      width: 4rem;
      height: 4rem;
      border: 1px solid;
      display: flex;
      align-items: center;
      justify-content: center;
 
      &.current {
        background-color: #333;
        color: #fff;
      }
    }
  }
</style>
  

主な仕様は以下の通りです。

  • [最後へ][最初へ]のリンク
  • [次へ][前へ]のリンク
  • [1][2][3]のような番号のリンク
  • 現在表示中のページ、またはリンク不可の場合は<a>タグではなく<span>タグで表示

必要のない機能は削除して、自身の使用用途に合わせてカスタマイズしてください。

1. pageプロパティから必要な情報を取得

[...page].astroから渡されるpageプロパティから、ページネーションに必要な情報を取得します。

/src/components/Pagination.astro
Astro
    ---
// paginate()関数で取得したpageプロパティ
const {page} = Astro.props;
  

今回使用するプロパティは以下の通りです。

プロパティ名説明
page.data現在のページに表示する記事の配列。pageSize10なら、10記事分のデータが格納された配列になる。
page.sizeページごとに表示する記事数。pageSizeに設定した値と同じ。
page.totalgetCollectionで取得した記事の総数。
page.currentPage1から始まる現在のページの番号。(例:現在のページが/blog/2なら2になる)
page.lastPageページネーションに必要なトータルのページ数。(例:記事数が30pageSize10なら、3になる)
page.url.current現在のページのURL。
page.url.prev前のページのURL。もし現在表示中のページが最初のページなら、undefinedになる。
page.url.next次のページのURL。もし現在表示中のページが最後のページなら、undefinedになる。
page.url.first最初のページのURL。もし現在表示中のページが最初のページなら、undefinedになる。
page.url.last最後のページのURL。もし現在表示中のページが最後のページなら、undefinedになる。

ページ番号の配列を作成

まずはページネーションの総ページ数を取得します。

例えば記事総数が30pageSize10の場合、3ページ分のページネーションが必要になりますね。

ページネーションの総数は、以下のプロパティから取得可能です。

/src/components/Pagination.astro
Astro
    ---
const totalPages = page.lastPage;
---
  

page.lastPageでページネーションの総数を取得できますが、lastPageという名前は分かりにくいので、totalPagesという変数に格納しています。

この数字から、[1, 2, 3]というようなページ番号の配列を生成するために、以下のようにArray.from()を使用します。

/src/components/Pagination.astro
Astro
    ---
const pageNumbers = Array.from({length: totalPages}, (_, i) => {
  return i + 1;
});
---
  

Array.from()は、第1引数に配列の長さを指定し、第2引数に各要素に対して実行する関数を指定します。

配列の長さはtotalPages、各要素はi + 1として、[1, 2, 3]というような配列を生成しています。

ページ番号からページURLを生成する関数を作成

[最後へ][最初へ][次へ][前へ]のリンクはpageプロパティから生成できますが、数字部分のリンクは自作する必要があります。

以下は、12などのページ番号から、そのページのURLを生成する関数です。

/src/components/Pagination.astro
Astro
    ---
const getPageUrl = (pageNumber) => {
  return pageNumber === 1 ? page.url.first : `${baseUrl}/${pageNumber}`;
}
  

pageNumber1の場合はpage.url.firstを返し、それ以外は/${baseUrl}/数字の形式でURLを生成します。

これでフロントマター内のコードは完成です!

2. 最初、前、次、最後のページへのリンクを表示

次に、[最初へ][前へ][次へ][最後へ]のリンクを表示します。

/src/components/Pagination.astro
Astro
    <!-- 最初のページ -->
<li class="item">
  {
    page.url.first ? (
      <a class="link" href={page.url.first}>&lt;&lt;</a>
    ) : (
      <span class="link">&lt;&lt;</span>
    )
  }
</li>
  

最初のページへのURLは、page.url.firstで取得できます。

もし現在表示中のページが最初のページなら、page.url.firstundefinedになるので、条件分岐で<a>タグか<span>タグかを切り替えています。

/src/components/Pagination.astro
Astro
    <!-- 前のページ -->
<li class="item">
  {
    page.url.prev ? (
      <a class="link" href={page.url.prev}>&lt;</a>
    ) : (
      <span class="link">&lt;</span>
    )
  }
</li>
  

前のページへのURLは、page.url.prevで取得できます。

こちらもこれ以上前のページがない場合はundefinedになるので、同様に条件分岐を行なっています。

[最後へ][次へ]は同じ仕組みなので、説明は割愛します。

3. ページ番号のリンクを表示

最後に、[1][2][3]のようなページ番号のリンクを作成します。

/src/components/Pagination.astro
Astro
    {
  pageNumbers.map((pageNumber) => {
    return (
      <li class="item">
        {page.currentPage === pageNumber ? (
          <span class="link current" aria-current="page">
            {pageNumber}
          </span>
        ) : (
          <a class="link" href={getPageUrl(pageNumber)}>
            {pageNumber}
          </a>
        )}
      </li>
    );
  });
}
  

先ほどフロントマターで生成したpageNumbersには、[1, 2, 3]のようなページ番号が格納されています。

これをmapメソッドでループし、現在表示中のページ番号と一致する場合は<span>タグで表示し、それ以外は<a>タグのリンクを表示しています。

各ページ番号のリンクは、先ほど作成したgetPageUrl関数を使用して生成しています。

また、現在表示中のページ番号にはaria-current="page"を設定します。これでスクリーンリーダーに「現在のページ」であることを伝えることができます。

これでページネーションのコンポーネントが完成しました!

【まとめ】Astroのサイトにページネーションを実装する方法

今回は、Astroでページネーションを実装する方法を紹介しました。

公式のドキュメントでは前後のページを表示するだけのページネーションが紹介されていますが、当記事では、

  • [最後へ][最初へ]のリンク
  • [次へ][前へ]のリンク
  • [1][2][3]のような番号のリンク

を全て含んだページネーションを実装しています。

コピペして自由にお使いいただけるよう作成しましたので、自身の使用用途に合わせてカスタマイズしてください。

現状ではページ番号が全て表示されるようになっているので、記事数が多くなるとデザインが崩れる場合があります。

ページ番号を省略して...などで表示するように調整するコードを追記する予定ですので、お待ちいただければと思います。