HeadlessCMS Strapiキャッチアップ

業務でNext.jsとHeadlessCMSであるstrapiを連携させることになったのでキャッチアップしていく
結論
- strapiでコレクション(投稿タイプ)を作成
- それをAPIとして公開する
- Next.js側でコールしてUI構築

初期セットアップ
npx create-strapi@latest ${project-name}
を実行するだけ。
${project-name}
は任意のプロジェクト名。
あとはいつも通りに npm install
を実行し依存関係をインストールする。
ローカルサーバー起動
package.json
を見れば一目瞭然だが、npm run develop
でローカルサーバーを起動できる。
以下のようなダッシュボード画面になる。
コレクションの作成
コレクションとは、複数のデータを管理するためのコンテンツタイプのこと。
Wordpressでいうカスタム投稿タイプに近そう。
公式ドキュメントでは「Restaurant」というコレクションを追加して複数のフィールドを追加した。
WordpressのACFみたいな印象を受けた。
- unique
- required
などの制約をつけることも可能
リレーションも設定できる!
便利!
デプロイ
npm run strapi deploy
でデプロイ可能。
調査が甘いかもしれないが、デプロイ先はデフォルトではstrapi cloudになるが、vercel,aws,gcpなどにもデプロイできそう。
今回はstrapi cloudを使用する。
デプロイコマンドを流したあとはURLが発行されるのでそちらにアクセスすることで確認できる。
APIをコールしてみる
今回はUIを構築していないのでcurl
でコールしてレスポンスだけ確認してみる。
# 雛形
curl https://プロジェクトURL/api/{コレクション名}
curl https://プロジェクトURL/api/restaurants
レスポンスはJSONで返ってきていた
{
"data": [
{
"id": 2,
"documentId": "jx5qpxx0obt31bcf7oilii1g",
"Name": "Biscotte Restaurant",
"Description": [
{
"type": "paragraph",
"children": [
{
"text": "Welcome to Biscotte restaurant! ",
"type": "text"
}
]
},
{
"type": "paragraph",
"children": [
{
"text": "Restaurant Biscotte offers a cuisine based on fresh, quality products, often local, organic when possible, and always produced by passionate producers.",
"type": "text"
}
]
}
],
"createdAt": "2025-03-03T08:25:12.011Z",
"updatedAt": "2025-03-03T08:32:32.200Z",
"publishedAt": "2025-03-03T08:32:32.408Z"
}
],
"meta": {
"pagination": {
"page": 1,
"pageSize": 25,
"pageCount": 1,
"total": 1
}
}
}
ご覧の通りdata
とmeta
が分かれて返ってくるのでとても扱いやすそう

dockerで開発環境を整える場合
サンプルのdockerfileがあるからそのまま流用して良さそう

一通り目を通してわかったこと
・デプロイは必須ではない(当たり前)
http://localhost:1337/api/${コレクション名}
のようにデータにアクセスすることができるのでローカルでデータを取得可能
・privareフィールドにしていなくてもデフォルトで返ってこないフィールドがある
The REST API by default does not populate any relations, media fields, components, or dynamic zones.
https://docs.strapi.io/dev-docs/api/rest/populate-select#population
直訳すると、REST APIはデフォルトでリレーション、メディアフィールド、コンポーネント、ダイナミックゾーンを返さない、とのこと。
そういった類のフィールドを返したい場合は、populate
パラメータをリクエストに付与する必要がある。
詳しくはこちらをみる https://docs.strapi.io/dev-docs/api/rest/populate-select#population
const response = await fetch("http://localhost:1337/api/posts?populate=*", {
cache: "no-store",
});
・Rich text (Block)を表示する方法
プラグインを使用する必要がある
型も定義されているので使いやすい
"use client";
import {
BlocksRenderer,
type BlocksContent,
} from "@strapi/blocks-react-renderer";
interface Props {
title: string;
id: number;
documentId: string;
slug: string;
excerpt: string;
content: BlocksContent;
}
export default function PostItem({
title,
id,
documentId,
slug,
excerpt,
content,
}: Props) {
return (
<div>
<h1 className="text-4xl font-bold mb-4 text-white">{title}</h1>
<p>id: {id}</p>
<p>documentId: {documentId}</p>
<p>slug: {slug}</p>
<p>expert: {excerpt}</p>
<BlocksRenderer content={content} />;
</div>
);
}

ページネーション
pagination
パラメータを使用することでページネーションを実装(レスポンスを分割)できる
page
によるページネーションの場合
・パラメータ | 型 | 説明 | default |
---|---|---|---|
pagination[start] |
int | ページ番号 | 1 |
pagination[limit] |
int | ページの大きさ(1ページ当たりの件数) | 25 |
pagination[withCount] |
bool | コンテンツの総数とページ数をレスポンスに追加するかどうか | true |
1ページ目の10件のみを取得する場合
GET /api/articles?pagination[page]=1&pagination[pageSize]=10
{
"data": [
// ...
],
"meta": {
"pagination": {
"page": 1,
"pageSize": 10,
"pageCount": 5,
"total": 48
}
}
}
offset
によるページネーションの場合
・パラメータ | 型 | 説明 | default |
---|---|---|---|
pagination[page] |
int | 開始値(つまり、返される最初のエントリ) | 1 |
pagination[pageSize] |
int | 返されるエントリの数 | 25 |
pagination[withCount] |
bool | エントリの合計数を表示するかどうか | true |
最初の10件のみを取得する場合
GET /api/articles?pagination[start]=0&pagination[limit]=10
{
"data": [
// ...
],
"meta": {
"pagination": {
"start": 0,
"limit": 10,
"total": 42
}
}
}

日本語化
-
src/admin/app.example.tsx
を複製しapp.tsx
にリネーム - リネームした
app.tsx
のconfig.locales['ja']
のコメントアウトを外すapp.tsximport type { StrapiApp } from "@strapi/strapi/admin"; export default { config: { locales: ["ja"], // これ }, bootstrap(app: StrapiApp) { console.log(app); }, };
- 再ビルドして、再起動
npm run build
→npm run dev
- ダッシュボード画面の左下にプロフィールがあるのでそこから使用する言語を変更する