Astroで作る — 投稿一覧と詳細ページ
このガイドでは、Astro の静的生成(SSG)で「お知らせ一覧」と「お知らせ詳細」の2ページを作ります。ビルド時に公開APIを呼び、投稿の更新は Webhook からの再ビルドで反映する構成です。
1. クライアントを1か所で作る
SDKクライアントは src/lib/cms.ts に1つだけ作り、各ページから import します。APIキーは環境変数から読みます。
import { ShareDanCmsClient } from '@sharedan/cms-sdk';
export const cms = new ShareDanCmsClient({
baseUrl: 'https://api.cms.sharedan.co.jp',
apiKey: import.meta.env.CMS_API_KEY,
siteCode: import.meta.env.CMS_SITE_CODE,
});
公開APIの一覧は、1回のリクエストで最大100件(per_page の上限)までしか返しません。投稿が100件を超えても取りこぼさないよう、レスポンスの
meta.last_page(最後のページ番号)まで page を進めて全件を集める関数も用意し、一覧ページと詳細ページの両方で使います。
import type { PostSummary } from '@sharedan/cms-sdk';
import { cms } from './cms';
// 公開APIの一覧は1回に最大100件。meta.last_page まで page を進めて全件を集める
export async function fetchAllPosts(postType: string): Promise<PostSummary[]> {
const items: PostSummary[] = [];
let page = 1;
while (true) {
const res = await cms.posts.list(postType, { page, perPage: 100 });
items.push(...res.items);
if (page >= res.meta.last_page) break;
page += 1;
}
return items;
} 2. 一覧ページ
一覧のレスポンスには本文(body)は含まれないため、軽量です。上部固定の投稿は
pinned: true で先頭に来ます。全件を取得したうえで Astro の
paginate() で20件ずつに分け、/news・/news/2… のページを生成します。ファイル名を [...page].astro にすると、1ページ目が番号なしの
/news になります。前後のページのURLやページ番号は page から取得できます。
---
import type { GetStaticPaths } from 'astro';
import { fetchAllPosts } from '../../lib/fetchAllPosts';
export const getStaticPaths = (async ({ paginate }) => {
const posts = await fetchAllPosts('news');
// 20件ずつに分け、/news・/news/2・/news/3 … を生成する
return paginate(posts, { pageSize: 20 });
}) satisfies GetStaticPaths;
const { page } = Astro.props;
---
<ul>
{page.data.map((p) => (
<li>
<a href={`/news/${p.slug}`}>{p.title}</a>
<small>{p.category.name}</small>
</li>
))}
</ul>
<nav>
{page.url.prev && <a href={page.url.prev}>前へ</a>}
<span>{page.currentPage} / {page.lastPage}</span>
{page.url.next && <a href={page.url.next}>次へ</a>}
</nav> /news/2 のようなURLになるため、数字だけのスラッグ(例:2)の投稿があると、詳細ページとURLが重なります。数字だけのスラッグを使う場合は、一覧を src/pages/news/page/[...page].astro のように別の階層に置いてください。
posts.list() / posts.get() に localeを渡すと言語別の公開版を取得でき、ビルド時に言語別ページとして焼けます。未翻訳は原文へフォールバックします。詳しくは多言語サイトを作るを参照してください。
3. 詳細ページ(動的ルート)
getStaticPaths では、1で用意した fetchAllPosts()
で全件を取得してスラッグを列挙します(投稿が100件を超えても、すべての詳細ページが生成されます)。本文は
renderProseMirror() でHTML化して
set:html で差し込みます。
---
import { cms } from '../../lib/cms';
import { fetchAllPosts } from '../../lib/fetchAllPosts';
import { renderProseMirror } from '@sharedan/cms-sdk';
export async function getStaticPaths() {
// 100件を超えても全投稿の詳細ページを作るため、全件を取得してスラッグを並べる
const posts = await fetchAllPosts('news');
return posts.map((p) => ({ params: { slug: p.slug } }));
}
const post = await cms.posts.get('news', Astro.params.slug!);
const html = renderProseMirror(post.body);
---
<article>
<h1>{post.title}</h1>
<div set:html={html} />
</article> posts.get())は投稿ごとに1回呼び出されます。レートリミットについてはレートリミットの扱いを参照してください。
4. 更新を反映する
SSG なので、投稿の公開・更新時に再ビルドが必要です。管理画面の「サイト設定 › 外部連携」で Webhook または GitHub Actions を有効にすると、次のイベントで通知されます。
export const prerender = false)にするハイブリッド構成も選べます。公開APIの応答は 20ms 前後です。
このページはお役に立ちましたか?
改善要望・不具合・ドキュメントの分かりにくい点など、お気軽にお寄せください。いただいた内容は今後の改善に役立てます。
お寄せいただきありがとうございます。今後の改善に役立てます。