目次
WordPressは昔から広く使われているCMSですが、いまでもお知らせ・記事の投稿や更新の場面で使われることが多いと感じています。
実際、管理画面に見覚えのある方も多いのではないでしょうか。
一方で、サイトの表示部分はWordPressに任せず、見た目や機能はNext.jsのようなモダンなフレームワークで実装したい、という場面もあります。
そこで今回は、WordPressから記事データを取得し、自分で作ったNext.jsのサイトに表示する構成を試してみました。
いわゆる「WordPressをヘッドレスCMSとして利用する」構成ですが、実際に触ってみると、記事取得そのものよりも、インフラまわりに注意が必要でした。
この記事では、その構成と実装の流れを整理して解説します。
最終的な構成

WordPressに保存された記事データを、AWSのプロキシを経由してNext.js側で取得し、公開サイトに表示する構成です。

記事の更新はWordPressの管理画面から行い、投稿した内容はNext.jsのサイトで表示します。
準備
WordPress
今回はスターサーバーにWordPressを構築しました。
親会社のXServerの構築方法はこちら。
そのほか国内主要レンタルサーバーのWordPress構築方法は各事業者の案内をご覧ください。
Next.js
無料プランで商用利用ができるNetlifyというホスティングサービスに、Next.jsでWebサイトを構築しておきました。
信じられないぐらいかんたんにNext.js製サイトの公開ができますし、強力なエコシステムのおかげで、わたしのようなバックエンドの知識に自信のないエンジニアにも保守・運用ができています。
実装
まず最初に、WordPressのREST APIから投稿データが取得できること確認します。
わたしは使い慣れたPOSTMANで確認しています。ターミナルからcURLコマンドでもOKです。
WordPress側では、/wp-json/wp/v2/posts にアクセスすると投稿データをJSONで取得できます。

このレスポンスを使えば、WordPressで入稿した記事を、別で作ったフロントエンド側に表示できます。
Next.js側では、記事一覧を表示する /blog と、記事詳細を表示する/blog/[slug] を用意しました。
一覧ページでは公開記事の配列を取得して並べ、詳細ページではslugに合致する記事を1件取得する構成です。
WordPressとの通信処理は、ページごとに直接書くのではなく、共通の取得関数にまとめています。一覧ページや詳細ページでは取得対象を指定するだけです。
function wpJsonUrl(path: string) {
return `${WP_BASE_URL}/wp-json/${path.replace(/^\//, "")}`;
}
async function wpFetchJson(path: string, { auth = false, cache = "force-cache" } = {}) {
const headers: Record<string, string> = { Accept: "application/json" };
if (auth) headers.Authorization = basicAuthHeader();
const res = await fetch(wpJsonUrl(path), { headers, cache });
if (!res.ok) throw new Error("WP fetch failed");
return res.json();
}一覧ページ /blog では、取得した記事をタイトル付きのリンクとして並べています。

ステータスが公開済みstatus=publish になっている記事を条件にして、認証なしで取得するようにしました。
export default async function BlogPage() {
const posts = await getPublishedPosts({ perPage: 20 });
return (
<main className={styles.page}>
<h1>Blog</h1>
<ul className={styles.list}>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title?.rendered}</Link>
</li>
))}
</ul>
</main>
);
}記事詳細ページ/blog/[slug]では、URLに含まれるslugを使って1件だけ記事を取得しています。
取得した記事が存在しない場合は、Next.js 側で 404 を返すようにしています。
const { slug } = await params;
const post = await getPublishedPostBySlug(slug);
if (!post) return notFound();
ローカル環境では、ここまでで記事一覧と記事詳細の表示を確認できました。
ただし、公開環境にデプロイすると、別の問題が出てきました。
ビルドエラー
そのまま Netlify にデプロイすれば動くだろうと思っていたのですが、プレビューサーバーへのデプロイしようとしたところエラーが発生しました。
問題が出たのは、Netlify のビルド時です。
/blogを事前生成しようとしたタイミングで、WordPress 側の REST API 取得に失敗し、ビルドが止まりました。
Error occurred prerendering page "/blog"
WP fetch failed: 403 Forbidden
https://example.stars.ne.jp/cms/wp-json/wp/v2/posts?...
Export encountered an error on /blog/page: /blogこの時点では、Next.js 側の実装が悪いのか、WordPress 側の設定が悪いのか、まだ切り分けができていませんでした。
ただ、少なくともローカルでは動いていたものが、Netlify のビルド環境では失敗している、という違いが見えていました。
最終的な原因にたどり着くきっかけになったのは、このログに出ていた 403 Forbidden (閲覧権限なし)でした。
アプリの表示処理よりも前、記事データを取りに行く段階で止まっていたことになります。
状況をもう少しはっきりさせるために、ビルド前に WordPress へ単純なアクセス確認を行うスクリプトを追加しました。
その結果、Next.js の事前HTML生成処理(prerender)とは関係なく、Netlify のビルド環境から WordPress に直接アクセスした時点で 403 になっていることが分かりました。
[wp-smoke] status: 403 Forbidden
[wp-smoke] server: nginx
[wp-smoke] content-type: text/htmlこのログから、Next.js の描画処理そのものではなく、WordPress への接続条件に問題がある可能性が高くなりました。
原因
WordPress 側の同じ API パスをローカル環境や Postman から確認すると、こちらでは問題なく 200 でレスポンスが返っていました。
一方で、Netlify のビルド環境からのアクセスだけが 403 になっていました。
結果 | 確認箇所 |
|---|---|
OK | ローカル環境からのアクセス |
OK | Postman からのアクセス |
閲覧権限なし | Netlify のビルド環境からのアクセス |
つまり、WordPress の REST API 自体が壊れているわけではなく、アクセス元によって結果が変わっている状態でした。
この差から、Next.js 側の実装というより、サーバー側のアクセス制限を疑いました。
実際に確認してみると、スターサーバーの WordPress セキュリティ設定で、REST API に対する国外アクセス制限が有効になっていました。

ローカル環境や Postman では問題なく取得できていたため、最初は WordPress 側の制限をあまり疑っていなかったのですが、Netlify のビルド環境が国外アクセスとして扱われ、 403 になっていたと考えられます。
つまり、「公開環境からWordPressに取りに行く経路」がサーバー側の制限に引っかかっていたというのが実際の原因です。
ローカル環境では動くのに、公開環境ではうまくいかない......、みたいな環境に起因するエラーは精神的にとてもしんどいです。原因がはっきりした瞬間は心の底から安堵しました。
解決方法
取得経路の方針検討
Next.jsの実装を大きく変える必要はないので、WordPress への取得経路だけを見直す方針を考えました。
最初に考えたのは、WordPress 側の REST API 制限をそのまま緩めて使う方法です。
これなら一番簡単ですが、公開環境としては少し不安が残ります。今回の構成では、WordPress を CMS として使い続けながら、できればサーバー側のセキュリティ設定も維持したかったため、他の方法を検討しました。
次に、Next.js 側の配置先そのものを国内サーバーへ移す案も考えました。
ただしそれをやると、Netlify のプレビュー環境やデプロイの手軽さを手放すことになります。今回やりたかったのは、フロント側の運用を大きく変えることではなく、WordPress との接続経路だけを調整することです。
そこで、Netlify 上の Next.js はそのままにして、WordPress に取りに行く出口だけを差し替える方針にしました。
AWSプロキシの構築
WordPress REST API へのアクセスだけを肩代わりする小さなプロキシ(代理)サーバーが必要です。
そこで今回は、AWS の東京リージョンに API Gateway と Lambda を用意し、Netlify 上の Next.js からはその入口に向けて記事データを取得する構成にしました。
API Gateway が受け口、Lambda が中継処理を担当します。
Next.js からのリクエストを API Gateway で受け取り、Lambda が WordPress 側の REST API にアクセスし、そのレスポンスを返すだけです。
常時サーバーを立てるほどではない一方で、取得経路だけを国内に寄せたかったため、今回の用途にはこの構成が合っていました。

Lambda 側の実装では、転送先や制御用の値をコードに直書きせず、AWS の Lambda 環境変数として設定するようにしました。
WP_ORIGIN:WordPress 側の接続先 URLPROXY_KEY:Next.js からのリクエストを判別するためのキーALLOW_PATH_PREFIX:転送を許可するパスの接頭辞
これなら、たとえば WordPress の接続先を変えたいときや、許可する API の範囲を絞りたいときにも、コード全体を触らずに調整できます。
また、今回は WordPress の REST API 全体を無条件に中継するのではなく、許可したパスだけを転送する形にして、役割を絞りました。
Lambda 側の処理の要点はおおむね次のようなものです。
export const handler = async (event) => {
// 共有鍵が一致しないアクセスは拒否
const proxyKey = event.headers?.["x-proxy-key"];
if (proxyKey !== process.env.PROXY_KEY) {
return { statusCode: 403, body: "Forbidden" };
}
// 読み取り専用として GET のみ許可
if (event.requestContext?.http?.method !== "GET") {
return { statusCode: 405, body: "Method Not Allowed" };
}
// WordPress REST API の許可パスだけを中継
const path = event.rawPath || "/";
if (!path.startsWith(process.env.ALLOW_PATH_PREFIX || "/wp-json/wp/v2/")) {
return { statusCode: 400, body: "Bad Request" };
}
// WordPress に転送して、その結果を返す
const origin = (process.env.WP_ORIGIN || "").replace(/\/$/, "");
const res = await fetch(`${origin}${path}`);
return {
statusCode: res.status,
body: await res.text(),
};
};入口となる API Gateway 側では、GET /{proxy+} のルートを作成し、その統合先としてこの Lambda 関数を設定しました。
こうしておくと、/wp-json/wp/v2/posts のようなパスをまとめて受け取れるため、WordPress REST API を中継する用途と相性がよかったです。


ここでやっていることをかなり簡単に言うと、Next.js は WordPress に直接アクセスする代わりに、まず API Gateway にリクエストを送り、そこから先の接続だけを Lambda に任せています。
そのため、フロントエンド側から見ると、取得先 URL が WordPress 直結から AWS 側の入口に変わっただけで、一覧ページや詳細ページのやるべきことはほとんど変わっていません。
設定後は、まず API Gateway 側の URL に対してリクエストし、WordPress の投稿 JSON が返ることを確認しました。
ここでレスポンスが返ってきたので、API Gateway → Lambda → WordPress の経路が通っていることがわかります。

プロキシ経由で取得
AWS 側で中継経路を確認できたあと、Next.js 側では WordPress の REST API を直接叩いていた共通取得処理を、プロキシ経由でも使える形に差し替えました。
ページごとの実装は大きく変えず、取得処理の入口だけを差し替える形にしたため、/blog や /blog/[slug] の実装はそのままです。
async function wpFetchJson(path: string, { auth = false, cache = "force-cache" } = {}) {
const headers: Record<string, string> = { Accept: "application/json" };
if (auth) headers.Authorization = basicAuthHeader();
if (process.env.WP_PROXY_KEY) headers["x-proxy-key"] = process.env.WP_PROXY_KEY;
const res = await fetch(wpJsonUrl(path), { headers, cache });
if (!res.ok) throw new Error("WP fetch failed");
return res.json();
}この構成で運用する理由
プロキシ経由で WordPress の投稿 JSON が取得できることを確認したあと、WordPress 側では REST API に対する国外アクセス制限を再び有効にした状態で、Netlify 側のビルドと記事取得が通ることを確認できました🙆♀️
今回の解決方法では、
- WordPress は入稿用 CMS として使う
- Next.js は Netlify 上で表示を担当する
- WordPress へのアクセスだけ AWS の東京リージョン(日本国内)経由にする
という形で、それぞれの役割を分けて整理しました。
これにより、WordPress側のREST APIに対する国外アクセス制限を有効にしたまま構成を成立させることができました。
WordPressへのアクセス経路をむやみに広げずに済み、既存のセキュリティ設定を保ちながら運用しやすい構成になっています。
なお、このプロキシ用途に限れば、現時点ではAWSの利用料金はかなり小さく、私の環境では無料枠の範囲にほぼ収まっています。
ただし無料利用枠の扱いはアカウント作成時期によって異なります。お使いのアカウントの請求画面をご確認ください。
おわりに
わたし自身、WordPressにはここ5年以上ほとんど触れていませんでした。
ただ最近、業務でWordPressで制作されたサイトに触る機会があり、いまでも多くの方に使われていることをあらためて実感しました。
また、すでにWordPressを使い慣れている方にとって、別のCMSへ移行するのはやはり抵抗があるだろうとも感じました。
そこで、WordPressをヘッドレスCMSとして使い、表示はNext.jsに任せる構成が成り立つのではないかという考えに至ります。最初はかなり机上の空論に近い状態でした。
実際に手を動かして構築してみると、やはり課題にぶつかりました。
日々攻撃手法は変化しており、特にWordPressのように広く使われている仕組みは、標的になりやすい面もあります。
そのため公開環境では、単に動けばよいのではなく、元のサーバー側のセキュリティ対策をできるだけ崩さない構成にしたいと考えました。
最終的には、AWSのAPI GatewayとLambdaを挟むことで、WordPress側のサーバー設定やセキュリティ対策をそのまま活かしつつ、Netlify上のNext.jsから記事を取得できるようにしています。
入稿はWordPress、表示はNext.jsと役割を分けることで、これまでの運用を活かしながら、見た目や機能は、より自由に制作できます。
WordPressを引き続き使いたいけれど、表示側はモダンな構成にしたい、という場面では現実的な選択肢に入ってくるのではないかと思います。


