Astroで制作したサイトに、1, 2, 3…のような番号付きのページネーションを実装したいという場合があるかと思います。
ページネーションの実装方法については、Astro公式のドキュメントでも紹介されています。
しかし、[前へ][次へ]だけのシンプルなものなので、当記事では、
- [最後へ][最初へ]のリンク
- [次へ][前へ]のリンク
- [1][2][3]のような番号のリンク
を全て含んだページネーションを作成する方法を紹介します!
検証環境
当記事で紹介する方法は、以下のバージョンで動作確認を行っています。
検証環境のバージョン情報
また、記事のフォーマットはMDXを使用していますが、ヘッドレスCMSから記事を取得する場合も同様に実装可能です。
バージョンの違いによっては、この記事の通りに動作しない可能性がありますので、ご理解いただけますと幸いです。
ページネーションを設置するための記事一覧ページを作成
まずは、ページネーションを設置するための記事一覧ページを作成します。
- 記事一覧ページ
[...page].astro
を作成 getStaticPaths
でページネーションのパスを生成- ページごとの記事一覧を表示
- ページネーションのコンポーネントを設置
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
を使用して、ページネーションのパスを生成します。
---
// 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
を使用して記事を取得しています。
---
import { getCollection } from 'astro:content';
export async function getStaticPaths({ paginate }) {
const posts = await getCollection('blog', ({ data }) => {
return data.isDraft === false;
});
// 省略
}
---
ヘッドレスCMSなど、コンテンツコレクション以外で記事を管理している場合は、別の方法で記事を取得してください。
記事を新しい順に並び替え
取得した記事を新しい順に並び替えるために、publishedAt
を基準にsort
メソッドを使用しています。
---
const sortedPosts = posts.sort((a, b) => {
return new Date(b.data.publishedAt) - new Date(a.data.publishedAt);
});
---
こちらもそれぞれの方法で並び替えを行なってください。
ページネーションのパスを生成
Astroにはページネーションを生成するためのpaginate
関数が用意されています。
---
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番目の記事・・・というように、記事の一覧データを表示していきます。
---
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. ページネーションのコンポーネントを設置
最後に、ページネーションのコンポーネントを設置します。
---
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
としています。
まずはコード全体の紹介から。
---
// 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}><<</a>
) : (
<span class="link"><<</span>
)
}
</li>
<!-- 前のページ -->
<li class="item">
{
page.url.prev ? (
<a class="link" href={page.url.prev}><</a>
) : (
<span class="link"><</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}>></a>
) : (
<span class="link">></span>
)
}
</li>
<!-- 最後のページ -->
<li class="item">
{
page.url.last ? (
<a class="link" href={page.url.last}>>></a>
) : (
<span class="link">>></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
プロパティから、ページネーションに必要な情報を取得します。
---
// paginate()関数で取得したpageプロパティ
const {page} = Astro.props;
今回使用するプロパティは以下の通りです。
プロパティ名 | 説明 |
---|---|
page.data | 現在のページに表示する記事の配列。pageSize が10 なら、10記事分のデータが格納された配列になる。 |
page.size | ページごとに表示する記事数。pageSize に設定した値と同じ。 |
page.total | getCollection で取得した記事の総数。 |
page.currentPage | 1 から始まる現在のページの番号。(例:現在のページが/blog/2 なら2 になる) |
page.lastPage | ページネーションに必要なトータルのページ数。(例:記事数が30 でpageSize が10 なら、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 になる。 |
ページ番号の配列を作成
まずはページネーションの総ページ数を取得します。
例えば記事総数が30
でpageSize
が10
の場合、3ページ分のページネーションが必要になりますね。
ページネーションの総数は、以下のプロパティから取得可能です。
---
const totalPages = page.lastPage;
---
page.lastPage
でページネーションの総数を取得できますが、lastPage
という名前は分かりにくいので、totalPages
という変数に格納しています。
この数字から、[1, 2, 3]
というようなページ番号の配列を生成するために、以下のようにArray.from()
を使用します。
---
const pageNumbers = Array.from({length: totalPages}, (_, i) => {
return i + 1;
});
---
Array.from()
は、第1引数に配列の長さを指定し、第2引数に各要素に対して実行する関数を指定します。
配列の長さはtotalPages
、各要素はi + 1
として、[1, 2, 3]
というような配列を生成しています。
ページ番号からページURLを生成する関数を作成
[最後へ][最初へ][次へ][前へ]のリンクはpage
プロパティから生成できますが、数字部分のリンクは自作する必要があります。
以下は、1
や2
などのページ番号から、そのページのURLを生成する関数です。
---
const getPageUrl = (pageNumber) => {
return pageNumber === 1 ? page.url.first : `${baseUrl}/${pageNumber}`;
}
pageNumber
が1
の場合はpage.url.first
を返し、それ以外は/${baseUrl}/数字
の形式でURLを生成します。
これでフロントマター内のコードは完成です!
2. 最初、前、次、最後のページへのリンクを表示
次に、[最初へ][前へ][次へ][最後へ]のリンクを表示します。
<!-- 最初のページ -->
<li class="item">
{
page.url.first ? (
<a class="link" href={page.url.first}><<</a>
) : (
<span class="link"><<</span>
)
}
</li>
最初のページへのURLは、page.url.first
で取得できます。
もし現在表示中のページが最初のページなら、page.url.first
はundefined
になるので、条件分岐で<a>
タグか<span>
タグかを切り替えています。
<!-- 前のページ -->
<li class="item">
{
page.url.prev ? (
<a class="link" href={page.url.prev}><</a>
) : (
<span class="link"><</span>
)
}
</li>
前のページへのURLは、page.url.prev
で取得できます。
こちらもこれ以上前のページがない場合はundefined
になるので、同様に条件分岐を行なっています。
[最後へ][次へ]は同じ仕組みなので、説明は割愛します。
3. ページ番号のリンクを表示
最後に、[1][2][3]のようなページ番号のリンクを作成します。
{
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]のような番号のリンク
を全て含んだページネーションを実装しています。
コピペして自由にお使いいただけるよう作成しましたので、自身の使用用途に合わせてカスタマイズしてください。
現状ではページ番号が全て表示されるようになっているので、記事数が多くなるとデザインが崩れる場合があります。
ページ番号を省略して...
などで表示するように調整するコードを追記する予定ですので、お待ちいただければと思います。