Astro + mircoCMSのブログ一覧にページネーションを実装する。
ブログ一覧を作成したときに、ブログコンテンツの数が大量になったら、10件ごとにページを分割できるように、ページネーションを導入した方が良いと思った。
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>