APIによるページ生成
ここではページへデータを渡すで使ったgetStaticData()を応用し、APIやCMSから取得した記事を一覧へ流し込み、記事ごとの詳細ページを生成します。
ここでのデータ取得は開発・ビルド時に行います。公開サイトへのアクセスごとにAPIを呼ぶ機能ではありません。
Table of Contents
APIのレスポンスを用意する
この例では、次の形のJSONを返すAPIを使います。https://api.example.com/newsは説明用のURLなので、実際のAPIへ置き換えてください。
[
{ "slug": "hello", "title": "サイトを公開しました", "body": "最初のお知らせです。" },
{ "slug": "update", "title": "更新しました", "body": "サービス紹介を追加しました。" }
]取得処理をsrc/data/news.tsへまとめます。APIをまだ用意していない場合は、関数の中からこの例と同じ配列を返して試せます。
export type Article = { slug: string; title: string; body: string }
export async function getArticles(): Promise<Article[]> {
const response = await fetch("https://api.example.com/news")
if (!response.ok) {
throw new Error(`お知らせの取得に失敗しました: ${response.status}`)
}
const data: unknown = await response.json()
if (!Array.isArray(data)) throw new Error("お知らせの形式が正しくありません")
return data.map((item) => {
if (
typeof item !== "object" || item === null ||
typeof item.slug !== "string" || !/^[a-z0-9-]+$/.test(item.slug) ||
typeof item.title !== "string" || typeof item.body !== "string"
) {
throw new Error("お知らせの記事データが正しくありません")
}
return { slug: item.slug, title: item.title, body: item.body }
})
}HTTPエラーとデータの形を確認し、取得できなければビルドを失敗させます。APIごとの認証や検証は、この関数内で実装します。
一覧ページへデータを渡す
一覧ページのgetStaticData()でgetArticles()を呼び、取得した記事をpropsとして渡します。
import type { Metadata, PageProps, GetStaticData } from "minista/types"
import { getArticles, type Article } from "../../data/news"
type Props = PageProps & { articles: Article[] }
export const metadata: Metadata = { title: "お知らせ" }
export const getStaticData: GetStaticData = async () => ({
props: { articles: await getArticles() },
})
export default function News({ title, articles }: Props) {
return (
<>
<h1>{title}</h1>
<ul>
{articles.map((article) => (
<li key={article.slug}>
<a href={`/news/${article.slug}`}>{article.title}</a>
</li>
))}
</ul>
</>
)
}取得した記事のタイトルとリンクは、ビルド時にHTMLへ出力されます。サイト閲覧時にこのAPIへ接続する必要はありません。
データごとのページを作る
ファイル名へ[slug]を使い、ページごとのpathsとpropsを配列で返します。ファイルベースルーティングでnews/hello.tsxを作った場合は削除して、同じURLの競合を避けてください。
import type { GetStaticData, PageProps } from "minista/types"
import { getArticles, type Article } from "../../data/news"
type Props = PageProps & { article: Article }
export const getStaticData: GetStaticData = async () => {
const articles = await getArticles()
return articles.map((article) => ({
paths: { slug: article.slug },
props: { title: article.title, article },
}))
}
export default function ArticlePage({ article }: Props) {
return (
<article>
<h1>{article.title}</h1>
<p>{article.body}</p>
</article>
)
}pathsのキーは[slug]の名前と一致させます。値は文字列です。数値のIDを使う場合はString(id)へ変換します。
入力と出力を確認する
src/
├── data/
│ └── news.ts
└── pages/
└── news/
├── index.tsx
└── [slug].tsxdist/news/
├── index.html
├── hello.html
└── update.html[slug].htmlというファイルが出るのではなく、APIから返した各値でファイル名が決まります。APIのデータが変わったら、再ビルドしてHTMLと関連アセットを更新します。
認証情報と実行タイミング
認証が必要なAPIでは、ビルド環境に設定した環境変数を利用できます。例えばprocess.env.CMS_TOKENを取得し、リクエストのヘッダーへ渡します。
認証情報はページのprops、HTML、Islandへ渡す値に含めないでください。ブラウザへ公開するVITE_*変数にも入れません。必要な環境変数は、ターミナルやCIなどビルドを実行する環境へ設定してください。
getStaticData()は開発中のページ表示やcheck inspectでも実行されます。一覧と詳細の取得関数を共通化しても、APIリクエストが自動的に一回へまとまるわけではありません。
生成するページを検査する
npx minista check --json
npx minista inspect --json
npm run build
npm run previewURLの重複、API取得の失敗、ページの表示を確認します。複数のパラメーターには[category]/[slug].tsxのような構成を使えます。公開型はTypeScript、APIの契約はpluginSsgを参照してください。
公開手順はビルドとデプロイを参照してください。
次はレイアウトで、各ページへ共通のヘッダーとフッターを追加します。