Astro.js 記事一覧でページネーションを導入する

ブログの記事数が増えてくると、一覧ページが長くなってしまいます。ページネーションを導入すれば、ページを分割して表示でき、ユーザー体験が向上します。ここでは、Astroの `paginate` 機能を使って、記事一覧ページに1ページあたり5件表示のページネーションを実装する手順を紹介します。

## ページネーション実装手順

### 1.動的ルートファイルを作成 (`src/pages/blog/[...page].astro`)**

* もし既に記事一覧ページがあれば、そのファイル（例えば `src/pages/blog/index.astro`）を **削除** するか、別の名前に変更してください。（例: `index-old.astro`）
* 次に、`src/pages/blog/` ディレクトリ内に `[...page].astro` という名前で **新しいファイルを作成** します。このファイルが `/blog/`, `/blog/2`, `/blog/3`, ... といったページをすべて処理します。

```
your-project/
└── src/
    ├── content/
    │   └── blog/
    ├── pages/
    │   ├── blog/
    │   │   └── [...page].astro <-- これを新規作成
    │   ├── tags/
    │   └── [...slug].astro (個別記事ページ)
    └── layouts/
```

### 2.`getStaticPaths` でページ分割を定義

作成した `src/pages/blog/[...page].astro` ファイルを開き、フロントマター部分 (`---` 内) に `getStaticPaths` 関数を実装します。ここでAstroの `paginate` 関数を使ってページ分割を行います。

```astro
---
// src/pages/blog/[...page].astro
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro'; // 適切なレイアウトを指定

export async function getStaticPaths({ paginate }) {
  // 1. 'blog'コレクションの記事をすべて取得
  const allPosts = await getCollection('blog');

  // 2. 公開日 (pubDate) の新しい順に記事をソート (ページ分割する前にソートする)
  const sortedPosts = allPosts.sort((a, b) =>
    b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
  );

  // 3. paginate関数でページ分割 (1ページあたり5件)
  //    ソート済みの記事リストを渡す
  return paginate(sortedPosts, { pageSize: 5 });
}

// paginateから渡された現在のページ情報を受け取る
const { page } = Astro.props;

// 個別記事ページへのURLを生成するヘルパー関数 (他ページで使っているものを流用)
function getPostUrl(post) {
  if (post.data.url) {
    return `/${post.data.url}/`;
  }
  return `/blog/${post.slug}/`;
}
---
```

* `getStaticPaths` は引数として `{ paginate }` を受け取ります。
* `getCollection('blog')` で全記事を取得し、必要に応じて `sort()` で並び替えます（**重要:** `paginate` する前にソートしてください）。
* `paginate(sortedPosts, { pageSize: 5 })` を呼び出します。第1引数に記事データの配列、第2引数の `pageSize` に1ページあたりの表示件数を指定します。これが各ページのパスとデータを自動生成してくれます。
* `const { page } = Astro.props;` で、現在のページに関する情報（記事データ、URLなど）を受け取ります。

### 3.ページネーションされた記事リストの表示

`[...page].astro` のHTMLテンプレート部分で、`page.data`（現在のページの記事リスト）を使って記事を表示します。

```astro
---
// --- 上記のフロントマターコード ---
---
<BaseLayout title={`ブログ記事一覧 - ${page.currentPage}ページ目`}>
  <h1>ブログ記事一覧</h1>
  <p>{page.start + 1}件目から{page.end + 1}件目を表示 ({page.total}件中)</p>

  <ul>
    {/* page.data に現在のページの5件分の記事が入っている */}
    {page.data.map((post) => (
      <li>
        <a href={getPostUrl(post)}>{post.data.title}</a>
        <span style="font-size: 0.8em; margin-left: 8px;">
          ({post.data.pubDate.toLocaleDateString()})
        </span>
      </li>
    ))}
  </ul>
</BaseLayout>
```
* `Astro.props` から受け取った `page` オブジェクトを使います。
* `page.data` が現在のページに表示すべき記事（最大5件）の配列なので、これを `map()` でループして表示します。
* `page.currentPage` (現在のページ番号)、`page.total` (全記事数) なども表示に利用できます。
* 個別記事へのリンクは、これまで通り `getPostUrl` ヘルパー関数などで生成します。

### 4.ページネーションリンクの表示

記事リストの下などに、「前へ」「次へ」のナビゲーションリンクを追加します。`page.url.prev` と `page.url.next` を使います。

```astro
---
// --- 上記のフロントマターコード & 記事リスト表示コード ---
---
<BaseLayout title={`ブログ記事一覧 - ${page.currentPage}ページ目`}>
  {/* ... 記事リスト ... */}

  {/* ↓ ここからページネーションリンクを追加 */}
  <nav aria-label="ページネーション">
    {/* 前のページへのリンク (page.url.prevが存在する場合のみ表示) */}
    {page.url.prev ? (
      <a href={page.url.prev}>&larr; 前のページへ</a>
    ) : (
      <span>&larr; 前のページへ</span> // リンク無効表示 (任意)
    )}

    <span style="margin: 0 1em;">
      {page.currentPage} / {page.lastPage} ページ
    </span>

    {/* 次のページへのリンク (page.url.nextが存在する場合のみ表示) */}
    {page.url.next ? (
      <a href={page.url.next}>次のページへ &rarr;</a>
    ) : (
      <span>次のページへ &rarr;</span> // リンク無効表示 (任意)
    )}
  </nav>
  {/* ↑ ここまでページネーションリンク */}
</BaseLayout>
```
* `page.url.prev` が存在すれば「前のページへ」のリンクを表示します。最初のページでは `undefined` になります。
* `page.url.next` が存在すれば「次のページへ」のリンクを表示します。最後のページでは `undefined` になります。
* 現在のページ番号 (`page.currentPage`) と総ページ数 (`page.lastPage`) を表示すると分かりやすいです。

### 開発サーバーで確認

ターミナルで開発サーバーを起動します。

```bash
npm run dev
# または pnpm dev, yarn dev
```

ブラウザで以下のURLにアクセスして確認してみましょう。

* **1ページ目:** `http://localhost:4321/blog/`
* **2ページ目:** `http://localhost:4321/blog/2`
* **3ページ目:** `http://localhost:4321/blog/3`

各ページに記事が5件ずつ表示され、「前へ」「次へ」のリンクが正しく機能し、最後のページでは「次へ」が、最初のページでは「前へ」がリンク切れ（または非表示）になっていれば完了です。