Astroの記事一覧にページネーションを付ける|静的ページを分割する手順
Astroの記事一覧を公開日の新しい順に並べ、paginateで静的URLへ分割し、前後リンク、1ページ目、canonical、サイトマップまで検査する手順を解説します。
記事が増えるたびに一覧ページが長くなるなら、Astroのpaginate()で複数の静的ページへ分けられます。ブラウザで全記事を取得してからJavaScriptで隠すのではなく、ビルド時に/kiji/、/kiji/2/のようなHTMLを生成する方法です。
この記事ではContent Collectionsのarticlesを対象に、公開記事だけを新しい順へ並べ、1ページ10件で分割します。コレクションの作り方から確認したい場合は、先にAstroでMarkdown記事サイトを作る最小構成を参照してください。
ページ分割が必要になる目安
ページネーションを入れる判断は、記事数だけで決めません。一覧を実機で開き、最初の表示までの時間、HTMLの大きさ、目的の記事を探す手間を確認します。20件でも各カードの画像や説明が長ければ分割が役立ち、100件でも題名だけなら一画面にまとめたほうが探しやすい場合があります。
導入前に、次の条件を決めておきます。
- 1ページに表示する件数
- 並び順と、同じ公開日になった場合の順番
- 1ページ目を
/kiji/と/kiji/1/のどちらにするか - 下書きや非公開記事を一覧から除く条件
- 記事が増減して最終ページが消えたときの確認方法
ここでは10件を初期値にしますが、10件がすべてのサイトに最適という意味ではありません。Astroのpaginate()はpageSizeを省略すると10件に分けます。明示しておくと、後から仕様を読む人が既定値を調べずに済みます。
URL設計の比較
| ルートファイル | 生成されるURLの例 | 向いている場合 | 注意点 |
|---|---|---|---|
src/pages/kiji/[page].astro | /kiji/1/、/kiji/2/ | 1ページ目にも番号を付けたい | 既存の/kiji/を残すと一覧URLが二つになりやすい |
src/pages/kiji/[...page].astro | /kiji/、/kiji/2/ | 1ページ目を短いURLにしたい | 既存のindex.astroと同じURLを生成しないよう責務を移す |
この記事では、サイト内リンクを短い/kiji/へ統一しやすい後者を使います。どちらを選んでも、公開後に形式を変える場合は既存URLの扱いを先に決めます。
記事を並べ替える
分割する前に全記事の順番を固定します。先に分割して各ページ内だけを並べ替えると、新しい記事が2ページ目へ残るなど、一覧全体の順番が崩れます。
---
import { getCollection } from 'astro:content';
const articles = (await getCollection(
'articles',
({ data }) => !data.draft
)).sort((a, b) => {
const dateDiff = b.data.pubDate.getTime() - a.data.pubDate.getTime();
return dateDiff || a.id.localeCompare(b.id);
});
---
この例では公開日の降順にし、日付が同じ記事はidで順番を固定しています。Astro公式資料でも、生成されたコレクションの取得順は非決定的で環境により異なるため、順番が必要なら自分で並べ替えるよう案内されています。実サイトにupdatedDateや固定記事のpinnedがある場合も、どの項目を優先するかを決めてから一つの比較関数へまとめます。
既存のsrc/pages/kiji/index.astroが一覧を担当している場合、次の動的ルートと同じ/kiji/を作らないよう、実装時に一覧の責務を移します。元ファイルを残したまま追加するのではなく、現在のレイアウトとカード表示を新しいページへ移植してください。
paginateで静的URLを作る
1ページ目を短い/kiji/にするなら、一覧ページをsrc/pages/kiji/[...page].astroとして作ります。通常の[page].astroは/kiji/1/、/kiji/2/のように生成しますが、残余引数を使う[...page].astroでは1ページ目がパラメーターなしのURLになります。
---
import type { GetStaticPaths } from 'astro';
import type { Page } from 'astro';
import { getCollection, type CollectionEntry } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import ArticleCard from '../../components/ArticleCard.astro';
export const getStaticPaths = (async ({ paginate }) => {
const articles = (await getCollection(
'articles',
({ data }) => !data.draft
)).sort((a, b) => {
const dateDiff = b.data.pubDate.getTime() - a.data.pubDate.getTime();
return dateDiff || a.id.localeCompare(b.id);
});
return paginate(articles, { pageSize: 10 });
}) satisfies GetStaticPaths;
interface Props {
page: Page<CollectionEntry<'articles'>>;
}
const { page } = Astro.props;
---
<BaseLayout
title={`記事一覧${page.currentPage > 1 ? ` ${page.currentPage}ページ目` : ''}`}
description="ユタラボの記事を新しい順で掲載しています。"
>
<main>
<h1>記事一覧</h1>
<p>全{page.total}本中 {page.start + 1}〜{page.end + 1}本を表示</p>
<div class="grid grid-2">
{page.data.map((article) => (
<ArticleCard
id={article.id}
title={article.data.title}
description={article.data.description}
category={article.data.category}
pubDate={article.data.pubDate}
/>
))}
</div>
</main>
</BaseLayout>
paginate()へ渡した配列の一部がpage.dataへ入り、現在ページはpage.currentPage、全ページ数はpage.lastPage、全件数はpage.totalで取得できます。Astroの静的出力ではgetStaticPaths()の結果がビルド時に列挙されるため、公開後のブラウザで分割処理を走らせる必要はありません。
前後ページのリンクを付ける
前後リンクにはpage.url.prevとpage.url.nextを使います。最初のページではprev、最後のページではnextがundefinedになるため、存在するときだけa要素を出します。
<nav class="pagination" aria-label="記事一覧のページ">
{page.url.prev && <a href={page.url.prev}>前のページ</a>}
<span aria-current="page">
{page.currentPage} / {page.lastPage}
</span>
{page.url.next && <a href={page.url.next}>次のページ</a>}
</nav>
リンクはクリック処理だけのボタンにせず、移動先をhrefに持つ通常のa要素にします。Googleの公式資料では、クローラーは一般にa要素のhrefからURLを見つけ、利用者の操作が必要なボタンは通常クリックしないと説明されています。CSSで無効らしく見せるリンクを残すより、移動先がないときは要素自体を出さないほうが状態も明確です。
aria-current="page"は、関連するページ群のうち現在のページを支援技術へ伝える属性です。ページ番号をすべて並べる場合も、現在位置にだけ付けます。件数が多いサイトではリンクが横へあふれないよう、まずは前・現在・次を表示し、必要になってから先頭・末尾や省略記号を足すと、小さい画面でも扱いやすくなります。
1ページ目のURLを統一する
同じ一覧が/kiji/と/kiji/1/の両方で開く構成は避け、内部リンクは一方へ統一します。[...page].astroを使う構成なら、ロゴ、パンくず、カテゴリページなどから一覧へ戻るリンクも/kiji/にそろえます。
すでに/kiji/1/を公開していた場合は、利用状況と配信環境を確認してから/kiji/への恒久的なリダイレクトを検討します。公開済みURLの有無とアクセス状況はサイトごとに異なるため、ここは一律に削除せず「※要確認」です。
ページの途中から先頭へ戻すリンクが必要なら、Astro 4.12以降で利用できるpage.url.firstも使えます。ただし、現在のサイトが対応バージョンかをpackage.jsonで確認してから採用します。単純に/kiji/が固定されているサイトでは、明示的なリンクでも構いません。
canonicalとサイトマップを確認する
2ページ目以降をすべて1ページ目の重複として扱わないよう、各ページのcanonicalは現在のURLを指すようにします。Googleはページネーションされた各ページへ固有URLを与え、1ページ目を全ページ共通のcanonicalにせず、各ページ固有のcanonicalを使うよう案内しています。
共通レイアウトがcanonicalを受け取れるなら、page.url.currentから絶対URLを組み立てて渡します。
---
const canonical = new URL(page.url.current, Astro.site);
---
<link rel="canonical" href={canonical} />
このコードにはastro.config.*のsite設定が必要です。Astro.siteが未設定なら、公開ドメインを推測せず設定を確認します。末尾スラッシュの有無も、生成されるURL、内部リンク、canonicalでそろえてください。
canonicalの共通実装と検査方法は、canonical URLの書き方でも確認できます。
公式の@astrojs/sitemapは、getStaticPaths()で生成した動的ルートを含む静的ページをビルド時にサイトマップへ載せます。導入済みでも、生成されたsitemap-*.xmlを開き、/kiji/と最終ページが含まれるかを確認します。検索エンジンに一覧ページが見つからない場合は、robots.txtとsitemapのトラブル解決の順序で切り分けます。設定だけを見て合格にせず、生成物を検査するのが安全です。
記事追加後の崩れを検査する
最後に、記事数がページ境界をまたぐ状況を試します。1ページ10件なら、公開記事が9件、10件、11件のときにビルドし、不要な空ページがないこと、11件目が2ページ目へ出ること、前後リンクが正しいことを確認します。実サイトの記事数を一時的に変えられない場合は、テスト用データで同じ境界を再現します。
検査項目は次のとおりです。
- 下書きが
page.totalと一覧へ含まれない - 各記事が重複せず、公開日の順に並ぶ
- 1ページ目と最終ページの前後リンクが欠けている
- 各ページのcanonicalが自分自身を指す
- サイトマップに先頭と最終ページがある
- 存在しない次ページが生成されていない
- 狭い画面でもカードとナビゲーションが横へはみ出さない
内部リンクの検査まで自動化する場合は、Astroサイトのリンク切れを自動検査する方法が使えます。ページネーションは一度作って終わりではなく、記事追加で総ページ数が変わる機能です。ビルド後のHTMLとサイトマップを検査対象に含めておけば、最終ページだけリンク切れになる事故を公開前に見つけられます。
よくある質問
Astroのpaginate()は何件ごとに分割されますか?
pageSizeを省略した場合は10件です。意図が伝わるよう、paginate(articles, { pageSize: 10 })のように明示しておくと変更箇所も分かりやすくなります。
1ページ目を/kiji/にできますか?
できます。ルートをsrc/pages/kiji/[...page].astroにすると、先頭ページをパラメーターなしの/kiji/として生成できます。同じURLを作るsrc/pages/kiji/index.astroがある場合は併存させず、一覧の実装を移してください。
下書きをページ総数から除外するにはどうしますか?
paginate()へ渡す前に、getCollection('articles', ({ data }) => !data.draft)のように絞り込みます。表示時だけ下書きを隠すとpage.totalや最終ページ数には残るため、取得段階で除外します。
2ページ目以降のcanonicalは1ページ目へ向けますか?
一律には向けません。Googleの公式資料は、各ページに固有URLと自己参照のcanonicalを持たせる方法を案内しています。page.url.currentとAstro.siteから、ページごとの絶対URLを作ります。
記事が減ったとき、存在しなくなった最終ページはどう確認しますか?
公開記事数をページ境界の前後で再現してビルドし、生成HTML、内部リンク、サイトマップを確認します。すでに公開済みのURLが消える場合のリダイレクト要否は、アクセス状況と配信環境により異なるため「※要確認」です。
一次情報
- Astro Routing Reference(
paginate()、pageプロパティ、URL生成。2026-07-24確認): https://docs.astro.build/ja/reference/routing-reference/ - Astro Content Collections公式資料(
getCollection()の絞り込みと明示的な並べ替え。2026-07-24確認): https://docs.astro.build/en/guides/content-collections/ - Astro Sitemap公式資料(静的に生成した動的ルートとサイトマップ。2026-07-24確認): https://docs.astro.build/en/guides/integrations-guide/sitemap/
- Google Search Central「Pagination Best Practices」(クロール可能なリンク、固有URL、canonical。2026-07-24確認): https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading
- MDN Web Docs「aria-current」(ページネーションの現在位置。2026-07-24確認): https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-current
続き(結論と実データ)はnoteに置いています
ここでは手順のところまで書きました。実際に出た数字、うまくいかなかった条件、そのまま使える設定ファイルは、 note の記事にまとめてあります。