---
title: "WordPressとReactの連携方法：ヘッドレス化とテーマ組み込みの手順【WordPress 7.1・Next.js 16】"
url: "https://www.issoh.co.jp/tech/details/2059/"
published: 2024-02-21
updated: 2026-10-12
categories: ["CMS"]
publisher: "株式会社一創"
---

# WordPressとReactの連携方法：ヘッドレス化とテーマ組み込みの手順【WordPress 7.1・Next.js 16】

WordPressとReactをつなぐ方法は2つあります。表示をReact側へ丸ごと移す**ヘッドレス化**と、既存テーマの一部だけをReactで描く**テーマ組み込み**です。構築の手間も運用の形も違うため、先にどちらを作るかを決めないと手戻りになります。なお、ブロックエディター用の独自ブロックをReactで作る場合は、表示ではなく編集画面の拡張なので、別の手順（`@wordpress/create-block`）になります。この記事では選び方を示したうえで、REST APIでの取得、CORSの切り分け、テーマへの組み込み、Next.jsでのヘッドレス化を順に解説します。掲載したコードは、WordPress Playground CLI 3.1.57で起動したWordPress 7.1と、React 19.3.0、Next.js 16.4.0、@wordpress/scripts 36.1.0で実行して確認しました。

## まとめ：WordPressとReactの連携で先に決めること

- **サイト全体をReactで作り替えるならヘッドレス化、部品だけならテーマ組み込み**。検索フォームや料金シミュレーターを足すだけなら、ヘッドレス化は手間に見合いません。
- **データの取得はREST API**。追加インストールなしで`/wp-json/wp/v2/`が使え、カスタム投稿タイプは`show_in_rest`を有効にしたときだけ出ます。
- **公開データの取得ではCORSエラーは原則なし**。本体がOriginをそのまま許可して返すので、エラーの原因は独自ヘッダー・リダイレクト・WAFなど本体の外にあります。
- **テーマ組み込みのReactはWordPress同梱の18.3.1**。`wp-element`に依存させれば自前で同梱せずに済みますが、React 19の機能は使えません。
- **ヘッドレス化の表示側はNext.jsが第一候補**。静的生成で本文入りのHTMLを返し、title・canonical・サイトマップを表示側で出し直せば、SEOで不利になりません。App Routerを使うなら、React Server Componentsの脆弱性（CVE-2025-55182と、その後に追加公表された分）を塞いだ各系列の最新パッチへ上げておきます。

| 比較項目          | ヘッドレス化             | テーマ組み込み          |
| ------------- | ------------------ | ---------------- |
| Reactで描く範囲    | サイト全体              | ページの一部           |
| WordPressのテーマ | 表示に使わない            | そのまま使う           |
| Reactの版       | 自由（19系も可）          | 同梱の18.3.1        |
| データの受け渡し      | REST APIかWPGraphQL | REST APIかPHPから直接 |
| サーバー          | WordPressとフロントの2つ  | WordPressだけ      |
| プレビュー         | 自前で実装する            | 標準のまま動く          |

## ヘッドレス化とテーマ組み込みの選び方

判断の軸は2つです。1つはReactで描きたい範囲がサイト全体か一部か、もう1つは更新する人がWordPressの管理画面だけで作業を終えられる必要があるかです。

### ヘッドレス化：表示をNext.jsなどに分離する

WordPressは管理画面とデータの保管だけを担い、表示は別に立てたReactアプリがREST APIかWPGraphQL経由で受け持ちます。画面の作りを自由に決められ、同じ記事をWebとアプリに配信できる代わりに、サーバーが2つになり、テーマや表示系プラグインは使えなくなります。ヘッドレスCMS一般の仕組みや他製品との比較は[ヘッドレスCMSとは？従来型との違い・料金比較とプレビュー実装【2026年10月】](/tech/details/1317/)で扱っています。

### テーマ組み込み：既存テーマの一部をReactで描く

PHPのテーマはそのまま使い、動きが必要な箇所だけにReactのコンポーネントを差し込みます。ブロックエディターがReactで作られているため、WordPressは最初からReactを同梱しており、既存サイトを壊さずに段階的に導入できます。

### 判断基準：ヘッドレス化を見送る条件

次のどれかに当てはまるなら、ヘッドレス化は見送ってテーマ組み込みから始めるべきです。

- 更新担当が非エンジニアで、公開前のプレビューを管理画面だけで確認したい
- Contact Form 7やスライダーなど、表示系プラグインの機能に依存している
- 目的が表示速度の改善だけで、キャッシュや画像の最適化をまだ試していない
- フロントエンドのサーバー（VercelやNetlifyなど）の保守を担当する人がいない

ヘッドレス化は、Webとアプリへの同時配信や、フロントを専任エンジニアが作り込む体制があるときに初めて元が取れます。表示速度が目的でも、遅さの原因が画像やサードパーティのタグ、サーバーの応答にあるならヘッドレス化しても改善せず、ブラウザだけで描画するSPAにすると初回の表示はむしろ遅くなります。迷ったら、まずテーマ組み込みでReactの比率を上げ、運用が回ることを確かめてから分離します。既存サイトの改修とヘッドレス化のどちらで進めるか決めきれない場合は、[WordPressサイト制作・CMS構築](/service/web/wordpress/)の窓口で構成から相談できます。

## ローカルにWordPress 7.1を立てる

手元で試すなら[WordPress 7.1.3（2026年10月6日公開）](https://wordpress.org/download/releases/)を**WordPress Playground CLI**で起動するのが速い方法です。Node.jsだけで動き、データベースやPHPを別に入れる必要がありません。

```
# WordPress 7.1 をローカルで起動（Node.jsだけで動く。DBやPHPの導入は不要）
npx @wp-playground/cli@latest server --port=9400 --wp=7.1

# 自作のプラグインを読み込ませるときは mu-plugins にマウントする
npx @wp-playground/cli@latest server --port=9400 --wp=7.1 \
  --mount=./mu-plugins:/wordpress/wp-content/mu-plugins

# 起動後の確認
curl -s http://127.0.0.1:9400/wp-json/wp/v2/posts
```

以降のcurlの例では、REST APIのベースURLを`http://127.0.0.1:9400/wp-json/wp/v2`とします。React・Next.jsのコードは本番を想定して`cms.example.com`と書いているので、手元で試すときは置き換えてください。

## REST APIで記事を取得する

REST APIは投稿・固定ページ・メディア・カスタム投稿タイプをJSONで返す本体の機能です。仕様は[REST API Handbook](https://developer.wordpress.org/rest-api/)にまとまっています。

### 一覧の取得と\_fields・ページ送り

投稿の一覧は`/wp/v2/posts`です。[グローバルパラメーター](https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/)の`_fields`で返す項目を絞ると、本文を丸ごと含む既定の応答より転送量が減ります。アイキャッチや著者も一緒に欲しいときは`_embed`を付けます。[ページ送り](https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/)は`per_page`（1〜100）と`page`で行い、総件数と総ページ数は応答ヘッダーの`X-WP-Total`・`X-WP-TotalPages`に入ります。

```
# 一覧：必要な項目だけ返す（_fields）。総ページ数はレスポンスヘッダーに入る
curl -s -D - "http://127.0.0.1:9400/wp-json/wp/v2/posts?per_page=10&_fields=id,title,link"

# 応答ヘッダーの抜粋（実測）
# x-wp-total: 1
# x-wp-totalpages: 1

# per_page は 1〜100。101 にすると 400 が返る
curl -s "http://127.0.0.1:9400/wp-json/wp/v2/posts?per_page=101"
# {"code":"rest_invalid_param","message":"Invalid parameter(s): per_page", ...}
```

パーマリンク設定が「基本」のサイトや、`/wp-json/`への書き換えが効かないサーバーでは、`/?rest_route=/wp/v2/posts`の形でも同じ応答が返ります（実測で200）。

### カスタム投稿タイプをREST APIに出す

カスタム投稿タイプは、登録時に`show_in_rest`を`true`にしないとAPIに出ません。指定しなかった投稿タイプにアクセスすると、404の`rest_no_route`が返ります。ブロックエディターもREST APIを使うため、`show_in_rest`が無い投稿タイプは編集画面がクラシックエディターになります。

```
<?php
// functions.php または独自プラグイン：カスタム投稿タイプをREST APIに公開する
add_action( 'init', function () {
  register_post_type( 'product', array(
    'label'        => '製品',
    'public'       => true,
    'show_in_rest' => true,       // これが無いと /wp/v2/products は rest_no_route（404）
    'rest_base'    => 'products', // URLの末尾。省略時は投稿タイプ名
    'supports'     => array( 'title', 'editor', 'thumbnail' ),
  ) );
} );
```

### 書き込みはApplication Passwordsで認証する

読み取りは認証なしで動きますが、投稿の作成や更新には認証が必要です。認証なしでPOSTすると、401の`rest_cannot_create`が返りました。外部のアプリからは、ユーザー画面で発行するApplication PasswordsをBasic認証で送ります（[Authentication](https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/)）。ブラウザのコードにパスワードを埋め込むと誰でも読めるので、書き込みはサーバー側の処理に寄せます。

## CORSエラーの原因を切り分ける

「WordPress React CORS」で見つかる記事の多くは、`functions.php`でCORSヘッダーを付け直す方法を紹介しています。ただ、WordPress本体はもともとCORSヘッダーを返しています。7.1.3のソース（`wp-includes/rest-api.php`の`rest_send_cors_headers`）は、リクエストに`Origin`があればその値を`Access-Control-Allow-Origin`にそのまま入れ、`Access-Control-Allow-Credentials: true`も付けます。Playgroundで`Origin: http://localhost:5173`を付けてGETすると、実際にそのOriginが返りました。

### CORSエラーが出る3つの原因：独自ヘッダー・リダイレクト・WAF

公開記事のGETでCORSエラーが出るなら、本体以外のところで止まっています。確認する順番は次のとおりです。

- **独自のリクエストヘッダー**：許可されるヘッダーの既定値は`Authorization`・`X-WP-Nonce`・`Content-Disposition`・`Content-MD5`・`Content-Type`の5つだけです。`X-Custom`のような独自ヘッダーを送ると、プリフライト（OPTIONS）の応答に含まれず、ブラウザが本リクエストを送りません。
- **リダイレクト**：`http`から`https`、`www`の有無、末尾のスラッシュなどでAPIのURLがリダイレクトされると、リダイレクト応答にCORSヘッダーが付かないためブラウザがそこで止め、プリフライトがある場合はリダイレクトそのものが許されません。APIのURLは最終的なURLで書きます。
- **セキュリティプラグインとWAF**：REST APIを未ログインのユーザーに閉じる設定や、海外IP・ボット判定のブロックは、エラー応答にCORSヘッダーが付かないため、ブラウザには「CORSエラー」としか表示されません。開発者ツールのネットワークタブで、応答のステータスコードを先に見ます。

独自ヘッダーを使う場合は、`rest_allowed_cors_headers`フィルターで許可リストに足します。追加後は、プリフライト応答の`Access-Control-Allow-Headers`に`X-Custom`が含まれました。

```
<?php
// 独自のリクエストヘッダーを送るなら、許可リストに足す
add_filter( 'rest_allowed_cors_headers', function ( $headers ) {
  $headers[] = 'X-Custom';
  return $headers;
} );
```

### 許可するOriginを絞る

本体の既定はどのOriginでも許可するため、自社のフロントだけに絞りたいときは本体の処理を外して置き換えます。下のコードでは、リストにないOriginには`Access-Control-Allow-Origin`が返らなくなりました。

```
<?php
// 許可するOriginを絞る（既定はリクエストのOriginをそのまま返す）
add_action( 'rest_api_init', function () {
  remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
  add_filter( 'rest_pre_serve_request', function ( $value ) {
    $allowed = array( 'https://www.example.com', 'http://localhost:5173' );
    $origin  = get_http_origin();
    if ( $origin && in_array( $origin, $allowed, true ) ) {
      header( 'Access-Control-Allow-Origin: ' . $origin );
      header( 'Access-Control-Allow-Methods: GET, OPTIONS' );
      header( 'Vary: Origin', false );
    }
    return $value;
  } );
}, 15 );
```

ただし、CORSはブラウザが他サイトのJavaScriptに応答を読ませないための仕組みで、アクセス制限ではありません。curlやサーバーからは、Originを絞っても記事を取得できます（実測で200）。公開したくないデータは権限チェックで守ります。

## Reactで記事一覧を表示する

単体のReactアプリは`npm create vite@latest my-app -- --template react`で作れます。create-vite 9.2.1のテンプレートはReactを`^19.2.8`、Viteを`^8.3.0`で指定しており（2026年10月時点ではReact 19.3.0とVite 8.3.4が入ります）、開発サーバーは`http://localhost:5173`で起動します。下のコンポーネントは、通信の中断と取得失敗の表示、`X-WP-TotalPages`の読み取りまで入れた形です。React 19.3.0で、Playgroundの記事を取得して一覧が描画されることを確認しました。

```
import { useEffect, useState } from "react";

const API = "https://cms.example.com/wp-json/wp/v2";

export default function Posts() {
  const [posts, setPosts] = useState([]);
  const [totalPages, setTotalPages] = useState(0);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const params = new URLSearchParams({
      per_page: "10",
      _fields: "id,link,title",
    });
    fetch(`${API}/posts?${params}`, { signal: controller.signal })
      .then((res) => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        setTotalPages(Number(res.headers.get("X-WP-TotalPages")));
        return res.json();
      })
      .then(setPosts)
      .catch((e) => {
        if (e.name !== "AbortError") setError(e.message);
      });
    return () => controller.abort();
  }, []);

  if (error) return <p>取得に失敗しました：{error}</p>;
  return (
    <>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <a href={post.link}>{post.title.rendered}</a>
          </li>
        ))}
      </ul>
      <p>全{totalPages}ページ</p>
    </>
  );
}
```

`title.rendered`はHTMLエンティティを含むことがあります。タイトルに「&」などが入るサイトでは、表示前にデコードするか、サニタイズしたうえでHTMLとして挿入します。

この構成はブラウザだけで描画するSPAで、初期HTMLに記事が入りません。検索流入を狙う公開サイトは、後述のNext.jsのようにサーバー側でHTMLを作ります。React全般のインデックス対策は[React SEO対策とは？SSR・SSG・プリレンダリングの選び方と実装手順](/column/details/2413/)にまとめています。

## 既存テーマにReactを組み込む

テーマ組み込みでは、Reactを自分で同梱するより、WordPressが持っているReactを使うほうが軽くなります。WordPress 7.1.3が登録するReactの版は18.3.1です（`wordpress-develop`の7.1.3タグの`package.json`）。

### ビルドなしでwp.elementを使う

スクリプトの依存に`wp-element`を指定すると、WordPressは`react.min.js`と`react-dom.min.js`を先に読み込み、グローバルの`wp.element`からReactの関数を使えるようにします。

```
<?php
// functions.php：WordPress同梱のReact（wp-element）に依存させて読み込む
add_action( 'wp_enqueue_scripts', function () {
  wp_enqueue_script(
    'my-react-app',
    get_theme_file_uri( 'js/app.js' ),
    array( 'wp-element' ), // react.min.js・react-dom.min.js が先に読み込まれる
    '1.0.0',
    array( 'in_footer' => true )
  );
} );
// テンプレート側にマウント先を置く：<div id="my-react-app"></div>
```

```
( function () {
  const { createElement: h, createRoot, useState } = wp.element;

  function Counter() {
    const [ count, setCount ] = useState( 0 );
    return h( 'button', { onClick: () => setCount( count + 1 ) }, 'クリック数: ' + count );
  }

  const el = document.getElementById( 'my-react-app' );
  if ( el ) {
    createRoot( el ).render( h( Counter ) );
  }
} )();
```

Playgroundで、フロントページに`react.min.js?ver=18.3.1.1`が出力され、ボタンのクリックで表示が更新されることを確認しました。JSXが使えない点を除けば、このままで動きます。

### @wordpress/scriptsでJSXをビルドする

JSXで書きたい場合は、公式のビルドツール[@wordpress/scripts](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/)を使います。`@wordpress/element`から読み込んだReactはバンドルに含まれず、実行時に同梱のReactを参照します。下のコードを`src/index.js`に置き、`npx wp-scripts build`を実行すると、`build/index.js`と、依存関係を書いた`build/index.asset.php`が出力されます。下のコンポーネントをビルドした`build/index.js`は292バイトで、依存には`react-jsx-runtime`と`wp-element`が入りました。

```
import { createRoot, useState } from '@wordpress/element';

function Counter() {
  const [ count, setCount ] = useState( 0 );
  return (
    <button onClick={ () => setCount( count + 1 ) }>
      クリック数: { count }
    </button>
  );
}

const el = document.getElementById( 'my-react-app' );
if ( el ) {
  createRoot( el ).render( <Counter /> );
}
```

```
<?php return array(
  'dependencies' => array(
    'react-jsx-runtime',
    'wp-element'
  ),
  'version' => '213730b56779e1432497'
);
```

```
<?php
// functions.php：wp-scripts の出力した依存関係ファイルをそのまま渡す
add_action( 'wp_enqueue_scripts', function () {
  $asset = include get_theme_file_path( 'build/index.asset.php' );
  wp_enqueue_script(
    'my-react-app',
    get_theme_file_uri( 'build/index.js' ),
    $asset['dependencies'],
    $asset['version'],
    array( 'in_footer' => true )
  );
} );
```

このビルド結果をPlaygroundのフロントページで読み込み、同梱の`react-jsx-runtime.min.js?ver=18.3.1`と組み合わせてボタンが動くことを確認しました。この方式ではReactの版をWordPressが決めます。React 19で加わった`use`やActionsは使えないので、React 19の機能が必要ならヘッドレス化を選びます。

### Interactivity APIで足りる場面

ボタンの開閉や絞り込みなど、ブロック単位の小さな動きであれば、WordPress 6.5で本体に入った[Interactivity API](https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/)でも作れます。HTML属性（`data-wp-*`）で状態と表示を結び付ける方式で、サーバーで描画したHTMLをそのまま動かすため既存のブロックと混ぜやすくなります。画面全体の状態管理が要るアプリ的なUIはReactのほうが向いています。

## Next.jsでヘッドレス化する

ヘッドレス化の表示側には、レンダリング方式をページごとに選べるNext.jsを使うのが一般的です。2026年10月時点の最新安定版は16.4.0です。Next.jsそのものの導入は[Next.jsとは？16系App Routerで最小アプリを動かす手順と採用判断](/tech/details/17373/)で解説しています。

### App Routerで記事ページを静的生成する

下の3ファイル（`lib/wp.js`・`app/page.jsx`・`app/posts/[slug]/page.jsx`）で、記事の一覧ページと記事ページを静的に生成し、5分ごとに再生成する構成になります。`WP_API_URL`にWordPressのREST APIのURLを入れて`next build`を実行すると、記事ページは`generateStaticParams`でビルド時にHTML化され、ビルド結果の一覧には「Revalidate 5m」と表示されました。生成されたHTMLには記事の見出しと本文が入っていました。

```
// lib/wp.js：WordPress REST APIの取得処理
const WP_API = process.env.WP_API_URL; // 例: https://cms.example.com/wp-json/wp/v2

export async function getPosts() {
  const res = await fetch(`${WP_API}/posts?per_page=20&_fields=id,slug,title`, {
    next: { revalidate: 300 },
  });
  if (!res.ok) throw new Error(`WordPress API: ${res.status}`);
  return res.json();
}

export async function getPostBySlug(slug) {
  const res = await fetch(`${WP_API}/posts?slug=${encodeURIComponent(slug)}`, {
    next: { revalidate: 300 },
  });
  if (!res.ok) throw new Error(`WordPress API: ${res.status}`);
  const [post] = await res.json();
  return post ?? null;
}
```

```
// app/page.jsx
import Link from "next/link";
import { getPosts } from "../lib/wp";

export default async function Home() {
  const posts = await getPosts();
  return (
    <ul>
      {posts.map((p) => (
        <li key={p.id}>
          <Link href={`/posts/${p.slug}`}>
            <span dangerouslySetInnerHTML={{ __html: p.title.rendered }} />
          </Link>
        </li>
      ))}
    </ul>
  );
}
```

```
// app/posts/[slug]/page.jsx
import { notFound } from "next/navigation";
import { getPosts, getPostBySlug } from "../../../lib/wp";

export async function generateStaticParams() {
  const posts = await getPosts();
  return posts.map((p) => ({ slug: p.slug }));
}

export default async function PostPage({ params }) {
  const { slug } = await params;
  const post = await getPostBySlug(slug);
  if (!post) notFound();
  return (
    <article>
      <h1 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
      <div dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
    </article>
  );
}
```

`content.rendered`はWordPressが整形したHTMLです。投稿者を信頼できない運用（寄稿を受け付けるなど）では、挿入前にサニタイズします。SSG・ISR・SSRの使い分けは[Next.jsのレンダリング方式｜SSG・ISR・SSR・CSRの違いとApp Routerでの選び方](/tech/details/3791/)で詳しく比べています。

### プレビューはDraft Modeで作る

ヘッドレス化すると、WordPressの「プレビュー」ボタンは表示側に届きません。Next.jsでは[Draft Mode](https://nextjs.org/docs/app/api-reference/functions/draft-mode)を使い、プレビュー用のルートでトークンを確かめてから`draftMode()`を有効にし、下書きを認証付きで取得して表示します。`draftMode`はApp Routerでは非同期関数なので、`await draftMode()`で呼びます。この実装を省くと、更新担当は公開してからでないと見た目を確認できません。

### React Server Componentsの脆弱性への対応

2025年12月3日、React Server Componentsに認証なしで遠隔からコードを実行される脆弱性（CVE-2025-55182、CVSS 10.0）が[React公式](https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components)から公表されました。影響を受けるのは`react-server-dom-webpack`などの19.0、19.1.0、19.1.1、19.2.0で、当初の修正版は19.0.1、19.1.2、19.2.1でした。その後、同じパッケージにサービス拒否（CVE-2025-55184・CVE-2025-67779・CVE-2026-23864）とソースコード露出（CVE-2025-55183）の脆弱性が[追加で公表](https://react.dev/blog/2025/12/11/denial-of-service-and-source-code-exposure-in-react-server-components)され、19.0.3・19.1.4・19.2.3までの修正も不完全とされたため、現在は19.0.4・19.1.5・19.2.4以降が必要です。[Next.jsの告知](https://nextjs.org/blog/CVE-2025-66478)によると、App Routerを使う15.x・16.xが影響を受け、13.x、14.xの安定版、Pages Router、Edge RuntimeはこのRCEの対象外です。ただし、追加された脆弱性については、React公式の更新手順が13.3.x〜14.xにも14.2.35への更新を求めています。

WordPress本体が同梱するのはブラウザで動くReact 18.3.1で、React Server Componentsのサーバー実装は入っていません。React公式も「サーバーを使わないReactのコードは影響を受けない」としており、本体とテーマ組み込みは対象外です。影響を受けるのはNext.jsで作ったヘッドレス化のフロントなので、`next`を各系列の最新パッチへ上げてください。

### Faust.js・Frontityを選ぶ前に確認すること

WP Engineの[Faust.js](https://faustjs.org/docs/)は、Next.jsでヘッドレスWordPressを作るためのツール群で、プレビューや認証を肩代わりします。`@faustwp/core`は2026年10月9日に3.5.1が出ており、開発は続いています。ただし前提はWPGraphQLとNext.jsで、REST APIだけで組む構成には向きません。

一方、かつて「WordPressのためのReactフレームワーク」と呼ばれた[Frontity](https://github.com/frontity/frontity)は、READMEで「not under active development anymore」と明記され、GitHubのリポジトリはアーカイブされています。開発チームは2021年8月にAutomatticへ合流し、現在はWordPress本体のInteractivity APIを開発しています。新しい案件で採用する理由はありません。

## REST APIとWPGraphQLの選び方

記事一覧と記事ページが中心のサイトなら、REST APIで足ります。1画面で投稿・カテゴリ・メニュー・カスタムフィールドをまとめて取りたいなど、取得の組み合わせが複雑になったら[WPGraphQL](https://www.wpgraphql.com/)を検討します。必要な項目だけを1回のリクエストで問い合わせられます。

```
# エンドポイントは /graphql（パーマリンク設定が「基本」のときは /?graphql）
query {
  posts(first: 10) {
    nodes {
      databaseId
      title
      slug
      date
      featuredImage { node { sourceUrl altText } }
    }
  }
}
```

WPGraphQLの最新版は2.23.1（2026年9月22日）です。WordPress.orgは2024年10月にWPGraphQLを公式に推奨する「canonical plugin」にすると[発表](https://wordpress.org/news/2024/10/wpgraphql/)しましたが、2026年10月12日時点の[プラグインページ](https://wordpress.org/plugins/wp-graphql/)は「Community plugin」の表示のままです。本体の機能ではなく、追加のプラグインとして保守する前提で選びます。上のクエリはWPGraphQL 2.23.1を入れたPlaygroundで記事のIDとタイトルを返しました。

## ヘッドレス化したWordPressのSEO：メタ情報・サイトマップ・canonical

ヘッドレス化そのものはSEOに有利でも不利でもありません。結果を分けるのは、初期HTMLに本文が入っているかと、従来はプラグインが出していたタグを表示側で出し直したかです。

従来のテーマでは、Yoast SEOやAll in One SEOがtitle・meta description・OGP・構造化データ・サイトマップを出力しますが、ヘッドレス化するとこれらは表示側に自動では出力されません（Yoast SEOはREST APIの応答に`yoast_head_json`を付けるので、[表示側で読み出して出力](https://developer.yoast.com/customization/apis/rest-api/)できます）。Next.jsでは`generateMetadata`でtitleやdescriptionを、`app/sitemap.js`でサイトマップを生成します。WordPressが出すサイトマップをそのまま使うと、URLがWordPress側のドメインのままになり、表示側のURLと食い違います。**canonicalも表示側のURLで出す**こと、WordPress側のURL（`cms.example.com`など）はnoindexにするか、ログインが必要な場所に置くことを、移行の作業項目に入れてください。

## よくある質問

### WordPressでSSRはできますか？

WordPressのテーマはもともとサーバー側でHTMLを作って返すので、従来の構成もサーバー描画です。Reactの表示をサーバー側で描画したい場合は、ヘッドレス化してNext.jsのSSRやSSGを使います。

### WordPressをSPAにできますか？

できます。REST APIで記事を取得してReactで描けばSPAになりますが、初期HTMLに本文が入らないため、検索から集客するサイトでは静的生成かサーバー描画を組み合わせます。

### ヘッドレス化するとプラグインは使えなくなりますか？

管理画面で動くプラグイン（カスタムフィールドの入力、権限管理など）は使えます。テーマの表示に何かを出力するプラグイン（フォーム、スライダー、SEOタグの出力など）は表示側に反映されないため、同じ機能をフロントで作り直すか、APIでデータを渡す拡張を使います。

### テーマに組み込むReactのバージョンは選べますか？

`wp-element`に依存させる方式では、WordPressが同梱する版（7.1.3では18.3.1）に固定されます。Reactを自前でバンドルすれば別の版も使えますが、同梱のReactに依存するブロックやプラグインと同じページに載ると、2つのReactが読み込まれて容量が増えます。

### Frontityは今も使えますか？

READMEで開発終了が明記され、リポジトリもアーカイブされているため、新しい案件での採用は勧めません。Next.jsとREST API（またはWPGraphQL）で組むのが現在の標準的な構成です。

## 関連記事

- [ヘッドレスCMSとは？従来型との違い・料金比較とプレビュー実装【2026年10月】](/tech/details/1317/)
- [Next.jsのレンダリング方式｜SSG・ISR・SSR・CSRの違いとApp Routerでの選び方](/tech/details/3791/)
- [Next.jsとは？16系App Routerで最小アプリを動かす手順と採用判断](/tech/details/17373/)
- [React SEO対策とは？SSR・SSG・プリレンダリングの選び方と実装手順](/column/details/2413/)
- [WordPress 7.0「Armstrong」の新機能・変更点と安全なアップデート手順](/tech/details/11971/)

---

出典: [WordPressとReactの連携方法：ヘッドレス化とテーマ組み込みの手順【WordPress 7.1・Next.js 16】](<https://www.issoh.co.jp/tech/details/2059/>)（株式会社一創）
