Astro Content Collectionsは、Astro V 2.0.0 からある Astro の古参機能で、Markdown 系のファイルをコンテンツとして扱うことができる機能です。
以下のようなファイルをsrc/pages
以下に置けば、src/content/blog/*
にある Markdown ファイルを一覧として取得する事ができます。
---
import { getCollection } from "astro:content";
const posts = (await getCollection("blog"));
---
{
posts.map((post) => (
<div>
<h2>{post.data.title}</h2>
<p>{post.data.description}</p>
</div>
));
}
しかし、このままだと、全てのコンテンツが一度に表示されてしまいます。 数件しかないコンテンツなら問題ありませんが、数十件、数百件と増えてくると、ページの表示速度が遅くなったり、ユーザーの利便性が低下してしまいます。 そこで、ページネーションを実装して、コンテンツを分割して表示するようにします。
Astro でページネーションを実装するには、動的ルーティングとgetStaticPathsを使用します。具体的には、[page].astro
のような動的ルートファイルを作成し、その中でgetStaticPaths
関数を実装します。
まず、src/pages/blog/[page].astro
というファイルを作成します。このファイル名の[page]
部分が実際のページ番号(1, 2, 3…)に置き換えられます。
getStaticPaths
関数内でpaginate
ヘルパー関数を使用して、ページネーション用のパスとデータを生成します。
---
interface Paginate {
paginate: (
items: any[],
options: {
pageSize: number;
}
) => any;
}
export async function getStaticPaths({ paginate }: Paginate) {
const posts = (await getCollection("blog")).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);
return paginate(posts, { pageSize: 10 });
}
---
この例では、すべてのblog
記事を取得し、公開日の降順で並べ替えて、1 ページあたり 10 件の記事を表示するよう設定しています。 インターフェースはany
でもいいと思います。私の環境では、型の指定がないと getStaticPath で Warn が出てしまったので、指定しています。
getStaticPaths
で生成されたデータは、Astro.props.page
として利用できます。
---
interface Props {
page: {
data: any[]; // 現在のページのデータ(ブログ記事など)
url: {
prev: string | undefined; // 前のページへのURL(ない場合はundefined)
next: string | undefined; // 次のページへのURL(ない場合はundefined)
};
currentPage: number; // 現在のページ番号
};
}
const { page } = Astro.props as Props;
---
コンテンツの下部には、前後のページへのリンクを含むページネーション UI を配置します。
一般的なナビゲーションならこんな感じでしょうか。
があります。
---
// src/pages/blog/[page].astro
const posts = (await getCollection("blog"))
const { page } = Astro.props as PageProps;
const lastPage = Math.ceil(posts.length / 10);
---
{
page.url.prev && page.currentPage > 2 && (
<a
href={`/blog/1/`}
class="px-3 py-2 rounded-lg text-sm text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/30"
>
«
</a>
)
}
{
page.url.prev && page.currentPage - 1 > 2 && (
<span class="px-3 py-2 text-sm text-gray-600">...</span>
)
}
{
page.url.prev && (
<a
href={page.url.prev}
class="px-3 py-2 rounded-lg text-sm text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/30"
>
{page.currentPage - 1}
</a>
)
}
<span class="px-3 py-2 rounded-lg text-sm bg-blue-600 text-white">
{page.currentPage}
</span>
{
page.url.next && (
<a
href={page.url.next}
class="px-3 py-2 rounded-lg text-sm text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/30"
>
{page.currentPage + 1}
</a>
)
}
{
page.url.next && page.currentPage + 1 < lastPage && (
<span class="px-3 py-2 text-sm text-gray-600">...</span>
)
}
{
page.url.next && (
<a
href={`/blog/${lastPage}/`}
class="px-3 py-2 rounded-lg text-sm text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/30"
>
»
</a>
)
}
この実装により、以下の URL パターンでブログ記事一覧にアクセスできるようになります:
/blog/1/
- 最初のページ/blog/2/
- 2 ページ目/blog/3/
- 3 ページ目 …各ページには指定した件数(例:10 件)の記事が表示され、記事が増えても快適に閲覧できるようになります。