Astroで作る — 投稿一覧と詳細ページ

入門 約15分 最終更新 2026-10-30 Astro 5

このガイドでは、Astro の静的生成(SSG)で「お知らせ一覧」と「お知らせ詳細」の2ページを作ります。ビルド時に公開APIを呼び、投稿の更新は Webhook からの再ビルドで反映する構成です。

前提:APIキーとサイトコードを取得済みで、クイックスタートのステップ2まで完了していること。

1. クライアントを1か所で作る

SDKクライアントは src/lib/cms.ts に1つだけ作り、各ページから import します。APIキーは環境変数から読みます。

src/lib/cms.ts
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 を進めて全件を集める関数も用意し、一覧ページと詳細ページの両方で使います。

src/lib/fetchAllPosts.ts
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 から取得できます。

src/pages/news/[...page].astro
---
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>
一覧の2ページ目以降は /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 で差し込みます。

src/pages/news/[slug].astro
---
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>
補足:ビルド1回あたり、一覧APIは「全件数 ÷ 100」回を一覧ページと詳細ページでそれぞれ(300件なら合計6回)、詳細API(posts.get())は投稿ごとに1回呼び出されます。レートリミットについてはレートリミットの扱いを参照してください。

4. 更新を反映する

SSG なので、投稿の公開・更新時に再ビルドが必要です。管理画面の「サイト設定 › 外部連携」で Webhook または GitHub Actions を有効にすると、次のイベントで通知されます。

イベント発生タイミング
post.published 投稿の公開・予約公開の実行時
post.updated 公開済み投稿の本文・メタ情報の更新時
faq.updated FAQの追加・更新・並び替え時
content.updated 固定コンテンツの更新時
ヒント:更新頻度が高いサイトは、一覧だけ SSR(export const prerender = false)にするハイブリッド構成も選べます。公開APIの応答は 20ms 前後です。
FEEDBACK

このページはお役に立ちましたか?

改善要望・不具合・ドキュメントの分かりにくい点など、お気軽にお寄せください。いただいた内容は今後の改善に役立てます。

返信をご希望の場合のみご記入ください。

スパム対策(レートリミット・ハニーポット・送信速度チェック)はサーバ側で自動適用されます。