Astro + mircoCMSのブログ一覧にページネーションを実装する。

ブログ一覧を作成したときに、ブログコンテンツの数が大量になったら、10件ごとにページを分割できるように、ページネーションを導入した方が良いと思った。

https://motomura-it.com/blog/

Astroのドキュメントをもとに手順を記録する。

ブログ一覧ページを作る。(pages/blogs/index.astroとpages/blogs/[blogId].astro)

まずは、ブログ一覧ページを作る。 https://motomura-it.com/blog/

このような形で作成される。

src

└ pages

 └ blog

  └ [blogId].astro

  └ index.astro

まずは、/pagesに新しいディレクトリ /blogを作成し、index.astroを作成する。

ページ内容としては、microCMSから取得したコンテンツのタイトルとURLのリスト一覧を表示するページである。

---
import Layout from '../layouts/Layout.astro';
import { fetchContent } from '../lib/fetchContent.js';
const posts = await fetchContent('blogs', 100);
---
<Layout>
  <header></header>
  <div class="container">
    <nav class="breadcrumbs"></nav>
    <aside></aside>
    <main>
      <div class="main_container">
       {posts.contents
        .map(post => (
                <li><a href={`/blog/${post.id}`}>{post.title}</a></li>
        )
       )}
      </div>
    </main>
  </div>
  <footer></footer>
</Layout>

次に[blog].astroを用意して、各ページにアクセスできるようにする。

Astroはファイルベースのルーティングを採用しており、src/pages/ディレクトリに.astroファイルを配置すると、自動的に対応するページのURLが生成される仕組みとなっている。

動的ルーティングの場合、ファイル名に[ ](ブラケット)記法を使い、getStaticPaths()関数で生成するパスの配列を返す。Astroドキュメント

[blogId].astro
---
import Layout from '../layouts/Layout.astro';
import { fetchContent } from '../lib/fetchContent.js';

const { blogId } = Astro.params;
const post = await fetchContent(`blogs/${blogId}`);

export async function getStaticPaths() {
  const posts = await fetchContent('blogs');
  return posts.contents.map(post => ({
    params: { blogId: post.id },
  }));
}
---

<Layout>
  <header></header>
  <div class="container">
    <nav class="breadcrumbs"></nav>
    <aside></aside>
    <main>
      <div class="main_container">
        <h1>{post.title}</h1>
        <div set:html={post.content} />
      </div>
    </main>
  </div>
  <footer></footer>
</Layout>

上記の場合だと、コンテンツの数が増えればどんどんリストが作成されて、見づらくなることが予想される。

Astroにはpaginate()関数があり、これを使うとページネーション用のプロパティ(前ページ・次ページのURLや総ページ数など)を簡単に生成できる。

ページネーションを実現するために、分割ページ用のファイルを用意する。blog/[page].astro だ。

[page]の部分には取得されるページ数が生成され、URLは、motomura-it.com/blog/1、motomura-it.com/blog/2 のようになる。

src

└ pages

 └ blog

  └ [blogId].astro

  └ [page].astro

  └ index.astro

getStaticPaths()の引数に{ paginate }を渡し、paginate()の第1引数に取得したコンテンツデータ、第2引数に{ pageSize: xx }を渡すことで、1ページにxx項目ずつ表示されるページを自動的に分割して生成できる。

各ページのデータは、const { page } = Astro.props; で利用できるようにする。

[page].astro
---
import { createClient } from "microcms-js-sdk";
import Layout from '../../layouts/Layout.astro';
import Breadcrumb from '../../components/Breadcrumb.astro';

export async function getStaticPaths({paginate }) {
  const client = createClient({
    serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
    apiKey: import.meta.env.MICROCMS_API_KEY,
  });
  const posts =  await client.getAllContents({
    endpoint: "blogs",
  })
  return paginate(posts, { pageSize: 10 });
}

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

<Layout>
  <header></header>
  <div class="container">
    <nav class="breadcrumbs"></nav>
    <aside></aside>
    <main>
      <div class="main_container">
       <ul>
        {page.data.map(post => (
         <li><a href={`/blog/${post.id}`}>{post.title}</a></li>
        ))}
       </ul>
      </div>
    </main>
  </div>
  <footer></footer>
</Layout>

次にページネーションを作成する。

取得したpageのプロパティに、currentPage:現在のページやlastPage:最終ページの値などがあるので確認する。

<pre>{JSON.stringify(page, null, 2)}</pre>

条件を指定して、ページネーションをつけ、移動できるようにする。

<div class="pagenation">
 {page.currentPage === 2 && page.currentPage === page.lastPage && (
  <a href={`/blog`}>← 前のページ</a>
 )}
                
 {page.currentPage === 2 && page.currentPage < page.lastPage && (
  <a href={`/blog`}>← 前のページ</a>
  <a href={`/blog/${page.currentPage + 1}`}>次のページ →</a>
 )}
                
 {page.currentPage > 2 && page.currentPage < page.lastPage && (
  <a href={`/blog/${page.currentPage - 1}`}>← 前のページ</a>
  <a href={`/blog/${page.currentPage + 1}`}>次のページ →</a>
 )}
                
 {page.currentPage === page.lastPage && page.currentPage > 2 && (
  <a href={`/blog/${page.currentPage - 1}`}>← 前のページ</a>
 )}
</div>

こうすると、/blog/1 、/blog/2 と各ページに移動できる。一方で /blog にアクセスできなくなる。

レストパラメーターを使用する [...page].astro

レストパラメーターについて

これを使うと、 /blog/1 ではなく/blog にアクセスできるようになる。

index.astro に次のページに移動できるように追加する。

index.astro
---
import Layout from '../layouts/Layout.astro';
import { fetchContent } from '../lib/fetchContent.js';
const posts = await fetchContent('blogs');
---
<Layout>
  <header></header>
  <div class="container">
    <nav class="breadcrumbs"></nav>
    <aside></aside>
    <main>
      <div class="main_container">
       <ul>
        {posts.contents
         .map(post => (
                 <li><a href={`/blog/${post.id}`}>{post.title}</a></li>
         )
        )}
       </ul>
       <div class="pagenation">
        {posts.contents.length > 10 &&( 
         <a href="/blog/2">次のページ →</a>
        )}
       </div>
      </div>
    </main>
  </div>
  <footer></footer>
</Layout>