2026年7月9日
2026年7月9日
WordPressのREST APIを使ったヘッドレスサイトの構築方法
はじめに
「WordPressをヘッドレスCMSとして使い、フロントエンドをNext.jsで構築したい」「REST APIでコンテンツを取得してReactアプリに表示したい」「WordPressの管理画面の使いやすさとモダンなフロントエンドを両立させたい」——ヘッドレスWordPressはコンテンツ管理とフロントエンドの柔軟性を両立できます。
症状・原因
ヘッドレス構成では、WordPressはバックエンドAPIとして機能し、フロントエンドは別ドメインで動作します。CORS設定・認証(JWT/Application Passwords)・REST APIのカスタマイズが必要です。プレビュー機能や下書きコンテンツの取得には特別な対応が必要になります。
解決手順
ステップ1:REST APIの基本とCORSを設定する
// functions.php: CORS設定(フロントエンドのドメインを許可)
add_action( 'init', function(): void {
$allowed_origins = [
'https://frontend.example.com',
'http://localhost:3000', // 開発環境
];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if ( in_array( $origin, $allowed_origins, true ) ) {
header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
header( 'Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS' );
header( 'Access-Control-Allow-Credentials: true' );
header( 'Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce' );
}
if ( 'OPTIONS' === $_SERVER['REQUEST_METHOD'] ) {
status_header( 200 );
exit;
}
} );
// REST API のレスポンスにCORSヘッダーを追加
add_action( 'rest_api_init', function(): void {
remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
add_filter( 'rest_pre_serve_request', function( $value ) {
// カスタムCORSヘッダーはinitで設定済みのため、デフォルト処理を無効化
return $value;
} );
} );
ステップ2:REST APIで取得するデータをカスタマイズする
// functions.php: 投稿一覧APIにカスタムフィールドを追加
add_action( 'rest_api_init', function(): void {
// カスタムフィールドをREST APIレスポンスに追加
register_rest_field( 'post', 'custom_fields', [
'get_callback' => function( array $post ): array {
return [
'price' => get_post_meta( $post['id'], '_price', true ),
'author_bio' => get_post_meta( $post['id'], '_author_bio', true ),
'reading_time' => my_calculate_reading_time( $post['id'] ),
];
},
'schema' => [
'type' => 'object',
'properties' => [
'price' => [ 'type' => 'string' ],
'author_bio' => [ 'type' => 'string' ],
'reading_time' => [ 'type' => 'integer' ],
],
],
] );
// アイキャッチ画像のURLをレスポンスに含める
register_rest_field( 'post', 'featured_image_url', [
'get_callback' => function( array $post ): ?string {
$id = get_post_thumbnail_id( $post['id'] );
return $id ? wp_get_attachment_image_url( $id, 'large' ) : null;
},
] );
// カテゴリー名をレスポンスに含める
register_rest_field( 'post', 'category_names', [
'get_callback' => function( array $post ): array {
return wp_list_pluck(
get_the_category( $post['id'] ),
'name'
);
},
] );
} );
function my_calculate_reading_time( int $post_id ): int {
$content = get_post_field( 'post_content', $post_id );
$word_count = mb_strlen( strip_tags( $content ) );
return (int) ceil( $word_count / 400 ); // 日本語は400字/分で計算
}
ステップ3:JWT認証を設定する(JWT Authentication for WP-API)
// wp-config.php: JWT認証用の秘密鍵を設定
// プラグイン「JWT Authentication for WP-API」を使用
define( 'JWT_AUTH_SECRET_KEY', 'your-very-secure-secret-key-here' );
define( 'JWT_AUTH_CORS_ENABLE', true );
// .htaccess: Authorizationヘッダーを通す(Apache)
// SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
// フロントエンド(Next.js): JWT認証でWordPress APIを呼び出す
const API_BASE = process.env.NEXT_PUBLIC_WP_API_URL; // https://cms.example.com/wp-json
// ログインしてJWTトークンを取得
async function login(username, password) {
const res = await fetch(`${API_BASE}/jwt-auth/v1/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
const data = await res.json();
return data.token; // JWTトークン
}
// 認証が必要なエンドポイントにアクセス
async function getDrafts(token) {
const res = await fetch(`${API_BASE}/wp/v2/posts?status=draft`, {
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
}
ステップ4:Next.jsでISR(増分静的再生成)を実装する
// pages/posts/[slug].jsx (Next.js): ISRでWordPressコンテンツを表示
const API_BASE = process.env.WP_API_URL;
export async function getStaticPaths() {
const res = await fetch(`${API_BASE}/wp/v2/posts?per_page=100&_fields=slug`);
const posts = await res.json();
return {
paths: posts.map(p => ({ params: { slug: p.slug } })),
fallback: 'blocking', // 未生成のページはSSRでフォールバック
};
}
export async function getStaticProps({ params }) {
const res = await fetch(`${API_BASE}/wp/v2/posts?slug=${params.slug}&_embed`);
const posts = await res.json();
if (!posts.length) return { notFound: true };
return {
props: { post: posts[0] },
revalidate: 60, // 60秒後に再生成(ISR)
};
}
export default function PostPage({ post }) {
return (
<article>
<h1 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
<div dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
</article>
);
}
ステップ5:プレビューとWebhookを設定する
// functions.php: Next.jsプレビューモード用のエンドポイント
add_action( 'rest_api_init', function(): void {
register_rest_route( 'my-headless/v1', '/preview', [
'methods' => 'GET',
'callback' => 'my_headless_preview_callback',
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
] );
} );
function my_headless_preview_callback( WP_REST_Request $request ): WP_REST_Response {
$post_id = absint( $request->get_param( 'id' ) );
$post = get_post( $post_id );
if ( ! $post ) {
return new WP_REST_Response( [ 'error' => 'Post not found' ], 404 );
}
// Next.jsのプレビューURLにリダイレクト
$preview_url = sprintf(
'%s/api/preview?secret=%s&id=%d',
rtrim( get_option( 'headless_frontend_url' ), '/' ),
PREVIEW_SECRET_TOKEN,
$post_id
);
return new WP_REST_Response( [ 'previewUrl' => $preview_url ], 200 );
}
// 投稿公開時にNext.jsのISRを再検証(revalidate)
add_action( 'save_post', function( int $post_id ): void {
if ( wp_is_post_revision( $post_id ) || 'publish' !== get_post_status( $post_id ) ) {
return;
}
$frontend_url = get_option( 'headless_frontend_url' );
$revalidate_token = REVALIDATE_SECRET_TOKEN;
wp_remote_post( $frontend_url . '/api/revalidate', [
'body' => wp_json_encode( [ 'token' => $revalidate_token, 'id' => $post_id ] ),
'headers' => [ 'Content-Type' => 'application/json' ],
'timeout' => 5,
] );
} );
注意事項
dangerouslySetInnerHTMLを使う場合、WordPressのコンテンツはすでにサーバー側でサニタイズされていますが、フロントエンドでのスクリプト実行(XSS)リスクを理解した上で使用してください。- REST APIのデフォルトエンドポイント(
/wp-json/wp/v2/usersなど)はユーザー情報を公開します。不要な場合はrest_endpointsフィルターで無効化してください。 - ISRの
revalidate時間はコンテンツの更新頻度に合わせて設定してください。上記のWebhookと組み合わせると、投稿更新時に即座に再検証できます。
まとめ
ヘッドレスWordPressの構築は「CORSを設定→register_rest_field()でカスタムフィールドをAPIに追加→JWT/Application PasswordsでAPI認証→Next.jsのISRでgetStaticProps+revalidateを設定→save_postフックで更新時にrevalidate APIを呼び出し」の流れで整備します。関連記事:WordPressのREST APIにカスタムエンドポイントを追加する方法、WordPressのGraphQLを使う方法。