AstroとWordPressでHeadless CMSブログを構築する
WordPressをHeadless CMS(ヘッドレスCMS:フロントエンドとバックエンドを分離したCMS)として活用し、Astroフレームワークでブログを構築します。
技術選定の理由
Astroを選んだ理由
Next.jsやNuxt.jsでも静的サイトを生成できますが、これらはアプリケーション開発に最適化されたフレームワークです。シンプルなサイト制作には少しオーバースペックです。
Astroの特徴:
- シンプルな構成で学習コストが低い
- 必要に応じてReactやVueなどのUIライブラリも使用可能
- 静的HTMLとして出力され、高速な表示が可能(インタラクティブなコンポーネント以外)
WordPressを選んだ理由
- 世界中で高いシェア率を持つCMS
- 多くのユーザーが慣れ親しんだUI
- REST APIによるHeadless CMSとしての利用が可能
実装手順
1. WordPress環境の構築
「Local」を使用して簡単にWordPress環境を構築します。
Local: https://localwp.com/
ガイドに従って進めると、以下のような管理画面が表示されます。

2. Astroのセットアップ
公式ドキュメント: https://docs.astro.build/ja/install/auto/
パスエイリアスの設定(オプション):
// tsconfig.json
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@src/*": ["src/*"]
}
}
}
環境変数の設定:
# .env
API_URL=http://{your-domain}/wp-json/wp/v2
使用例:
import.meta.env.API_URL
3. 投稿一覧の取得と表示
エンドポイント: /wp-json/wp/v2/posts
Astroではトップレベルでawaitを使用できるため、非同期処理をシンプルに記述できます。
index.astro
---
import Layout from '@src/layouts/Layout.astro';
type Post = {
link: string;
title: {
rendered: string;
};
};
const res = await fetch(`${import.meta.env.API_URL}/posts`);
const posts = await res.json() as Post[];
---
<Layout title="投稿一覧">
<main>
<ul>
{posts.map((post) => (
<li>
<a href={post.link} set:html={post.title.rendered} />
</li>
))}
</ul>
</main>
</Layout>

4. 投稿詳細ページの表示
エンドポイント: /wp-json/wp/v2/posts/{id}
ダイナミックルーティング(動的ルーティング)を使用するため、getStaticPaths関数で事前にパス情報を取得します。
pages/posts/[id].astro
---
import Layout from '@src/layouts/Layout.astro';
type Post = {
link: string;
title: {
rendered: string;
};
content: {
rendered: string;
}
};
export async function getStaticPaths() {
const res = await fetch(`${import.meta.env.API_URL}/posts`);
const posts = await res.json() as { id: number }[];
return posts.map(post => (
{ params: { id: post.id.toString() } }
))
}
const { id } = Astro.params;
const res = await fetch(`${import.meta.env.API_URL}/posts/${id}`);
const post = await res.json() as Post;
---
<Layout title={post.title.rendered}>
<main>
<h1 set:html={post.title.rendered} />
<p set:html={post.content.rendered} />
</main>
</Layout>

5. カスタム投稿の取得
WordPressのカスタム投稿タイプ(独自の投稿タイプ)もREST API経由で取得できます。
エンドポイント: /wp-json/wp/v2/{custom_post_type}
お知らせカスタム投稿(news)の例:
news/index.astro
---
import Layout from '@src/layouts/Layout.astro';
type News = {
id: number;
title: {
rendered: string;
};
};
const res = await fetch(`${import.meta.env.API_URL}/news`);
const news = await res.json() as News[];
---
<Layout title="お知らせ一覧">
<main>
<ul>
{news.map((item) => (
<li>
<a href={`/news/${item.id}`} set:html={item.title.rendered} />
</li>
))}
</ul>
</main>
</Layout>

ページネーション実装
先の投稿一覧ページではページネーションがありませんでした。実は投稿エンドポイントのヘッダーにページネーション情報が含まれています。
x-wp-totalpages
これを使用してページネーションを実装してみます。
pages/posts/index.astro
---
import Layout from '@src/layouts/Layout.astro';
import Paginate from '@src/components/Paginate.astro';
type Post = {
id: number;
title: {
rendered: string;
};
};
export async function getStaticPaths() {
const res = await fetch(`${import.meta.env.API_URL}/posts`);
const totalCount = res.headers.get('x-wp-totalpages') as string;
const pages: number[] = Array(+totalCount).fill(null).map((_, i) => i + 1);
return pages.map(page => (
{ params: { id: page.toString() } }
))
}
const { id } = Astro.params;
const res = await fetch(`${import.meta.env.API_URL}/posts?page=${id}`);
const posts = await res.json() as Post[];
const totalCount = res.headers.get('x-wp-totalpages') as string;
const pages: number[] = Array(+totalCount).fill(null).map((_, i) => i + 1);
---
<Layout title={`投稿一覧 | ${id}ページ目`}>
<main>
<ul>
{posts.map((post) => (
<li>
<a href={`/posts/${post.id}`} set:html={post.title.rendered} />
</li>
))}
</ul>
<Paginate pages={pages} currentPage={id} />
</main>
</Layout>
components/Paginate.astro
---
const { pages, currentPage } = Astro.props as { pages: number[], currentPage: string };
---
<style>
nav {
display: flex;
justify-content: center;
}
ul {
display: flex;
list-style: none;
padding: 0;
}
li {
margin: 0 0.5rem;
}
a {
display: grid;
place-items: center;
width: 2em;
height: 2em;
text-decoration: none;
color: #333;
}
.is-active {
pointer-events: none;
background-color: #333;
color: #fff;
}
</style>
<nav>
<ul>
{pages.map((page) => (
<li>
<a class:list={[{ 'is-active': page === +currentPage }]} href={`/posts/page/${page}`}>{page.toString()}</a>
</li>
))}
</ul>
</nav>

ビルドしてファイルを見てみる
ここまでできたらビルドしてみましょう。jsがなく、静的サイトとして出力されていることがわかります。
npm run build

ここからは余談
フォームプラグインとの連携
WordPressのフォームプラグインとしてContactForm7が有名ですが、実はRest APIを使用してフォームの送信ができます。
以下の記事が参考になります。
さらにSEOプラグインとしてYoast SEOが有名ですが、こちらもRest APIを使用してSEO情報を取得できます。
使用するプラグインによりますが、Rest APIが提供されている場合はAstroからも使用できます。
さいごに
ここまでできたら、あとは自由にカスタマイズしていきましょう。そのままastro記法を使用しても良いですし、ReactやVueなどの記法を使うことも使用できます。WordPressのプラグインを活かしてAstroに表示させることもできます。
ビルドされたファイルは静的なHTMLファイルとして出力されるため、高速なサイト表示ができます。
今回は機能としてほんの一握りを紹介しましたが、より多くの機能がAstroにはあります。
ぜひ試してみてください。
参考
Astroの公式ドキュメントは以下を参照にしてください。
基本的なWordPress Rest Apiのエンドポイントは以下を参照にしてください。
Discussion