2026年8月1日

2026年8月1日

WordPressとAstroを連携してコンテンツを管理・配信する設定方法

WordPress を記事の管理に使い、表示は Astro で静的サイトとして配信する構成では、Astro のコンテンツコレクションに「WordPress の REST API から記事を読み込むローダー」を書き、記事を公開したら WordPress からビルドを依頼する、という2つの設定が中心になります。この記事では、ローダーの書き方、記事ページの生成、公開時の自動ビルド、そして編集者の作業(プレビュー・予約投稿・ブロックの見た目)がどう変わるかまで、運用の視点で説明します。

この構成が向いているサイト

Astro は、ビルド時に HTML を生成して配信する静的サイトを得意とするフレームワークです。WordPress と組み合わせると、編集者は慣れた管理画面で記事を書き、訪問者には軽い静的ページを届けられます。WordPress 本体は公開サーバーに置かなくてもよくなるため、攻撃を受ける面を減らせる点も利点です。

一方で、記事を更新するたびにサイトをビルドし直す必要があります。更新頻度が1日に数回程度のブログやコーポレートサイトには向いていますが、数分おきに更新するニュースサイトや、会員ごとに表示が変わるサイトには、サーバー側で描画する仕組みのほうが合います。Astro でもアダプターを入れれば一部のページだけサーバー側で描画できるので、全体を静的にするか、ページ単位で分けるかを最初に決めておきます。

以下は Astro 5 で導入された Content Layer API(ローダーを使うコンテンツコレクション)を前提にしています。設定ファイルの場所や z の読み込み元など、細かな書き方はメジャーバージョンで変わることがあるため、利用中のバージョンの公式ドキュメントで確認してください(2026年10月現在)。

WordPress 側の準備

まず、WordPress の REST API から記事が取得できることを確認します。パーマリンク設定を「基本」以外にし、ブラウザで https://cms.example.com/wp-json/wp/v2/posts?_fields=id,slug,title を開いて JSON が返れば準備はできています。

運用面で決めておきたいのはスラッグの付け方です。日本語のスラッグは URL エンコードされた文字列として返ってくるため、Astro 側のファイル名やパスの扱いが分かりにくくなります。投稿画面でスラッグを英数字とハイフンにする運用ルールを、編集者と共有しておくと後の手間が減ります。

固定ページやカスタム投稿タイプも同じように取得できます。カスタム投稿タイプの場合は、登録時に REST API での公開(show_in_rest)が有効になっている必要があります。

コンテンツコレクションに WordPress のローダーを書く

src/content.config.ts に、WordPress から記事を読み込むコレクションを定義します。REST API は1回のリクエストで最大100件までしか返さないため、レスポンスヘッダー X-WP-TotalPages を見ながら全ページを取得します。

// src/content.config.ts
import { defineCollection, z } from 'astro:content';

const WP_API = 'https://cms.example.com/wp-json/wp/v2';

type WPPost = {
  id: number;
  slug: string;
  date: string;
  modified: string;
  title: { rendered: string };
  excerpt: { rendered: string };
  content: { rendered: string };
};

async function fetchAllPosts(): Promise<WPPost[]> {
  const all: WPPost[] = [];
  let page = 1;
  let totalPages = 1;
  do {
    const res = await fetch(
      `${WP_API}/posts?per_page=100&page=${page}&_fields=id,slug,date,modified,title,excerpt,content`
    );
    if (!res.ok) {
      throw new Error(`WordPress API error: ${res.status}`);
    }
    totalPages = Number(res.headers.get('X-WP-TotalPages') ?? '1');
    all.push(...((await res.json()) as WPPost[]));
    page++;
  } while (page <= totalPages);
  return all;
}

const posts = defineCollection({
  loader: async () => {
    const items = await fetchAllPosts();
    return items.map((p) => ({
      id: p.slug,
      title: p.title.rendered,
      date: p.date,
      modified: p.modified,
      excerpt: p.excerpt.rendered,
      content: p.content.rendered,
    }));
  },
  schema: z.object({
    title: z.string(),
    date: z.coerce.date(),
    modified: z.coerce.date(),
    excerpt: z.string(),
    content: z.string(),
  }),
});

export const collections = { posts };

取得に失敗したときに例外を投げているのは、WordPress が落ちているときに「記事が0件のサイト」をビルドして公開してしまうのを防ぐためです。ビルドが失敗すれば、多くのホスティングでは直前に公開したサイトがそのまま残ります。

schema を書いておくと、WordPress 側の項目が欠けていたり型が違ったりしたときに、ビルドの段階でエラーとして気づけます。日付を z.coerce.date() で Date 型にしておけば、ページ側で並べ替えや表示の整形がしやすくなります。

記事ページと一覧ページを作る

記事ページは getStaticPaths() でコレクションの全記事からパスを作ります。

---
// src/pages/blog/[slug].astro
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('posts');
  return posts.map((post) => ({
    params: { slug: post.id },
    props: { post },
  }));
}

const { post } = Astro.props;
---
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title set:html={post.data.title} />
  </head>
  <body>
    <article>
      <h1 set:html={post.data.title} />
      <time datetime={post.data.date.toISOString()}>
        {post.data.date.toLocaleDateString('ja-JP')}
      </time>
      <Fragment set:html={post.data.content} />
    </article>
  </body>
</html>

set:html は HTML をエスケープせずに挿入します。WordPress の本文は HTML なのでこの方法で表示しますが、投稿できるのが信頼できる編集者だけかを確認してください。一覧ページは getCollection('posts') の結果を日付で並べ替えて表示すれば作れます。

本文中の画像は WordPress のメディアの URL のまま表示されます。Astro の画像最適化を外部の画像に使う場合は、astro.config.mjs の image.domains に WordPress のドメインを登録する必要があります。

記事の公開でビルドを自動実行する

静的サイトでは、WordPress で記事を公開しただけでは表示が変わりません。Netlify、Vercel、Cloudflare Pages などのホスティングには、特定の URL に POST するとビルドが始まる「デプロイフック(ビルドフック)」があるので、これを WordPress から呼びます。

注意したいのは、記事の保存は短時間に何度も起こることです。保存のたびにビルドを走らせるとビルドが重なって無駄が多いため、ここでは WP-Cron の単発イベントを使い、最初の変更から2分後に1回だけビルドを依頼する形にします。その2分間に重なった保存はまとめて1回のビルドになり、ビルド依頼のあとに行った変更は、次の予約として改めて登録されます。専用のプラグインか mu-plugin に置き、デプロイフックの URL は wp-config.php に定数 ASTRO_DEPLOY_HOOK_URL として定義します。この URL を知っていれば誰でもビルドを起動できるため、リポジトリや公開される場所には書かないでください。

<?php
add_action( 'transition_post_status', function ( $new_status, $old_status, $post ) {
	if ( 'post' !== $post->post_type ) {
		return;
	}
	if ( 'publish' !== $new_status && 'publish' !== $old_status ) {
		return;
	}
	if ( wp_is_post_revision( $post ) || wp_is_post_autosave( $post ) ) {
		return;
	}
	if ( ! wp_next_scheduled( 'myastro_trigger_deploy' ) ) {
		wp_schedule_single_event( time() + 120, 'myastro_trigger_deploy' );
	}
}, 10, 3 );

add_action( 'myastro_trigger_deploy', function () {
	if ( ! defined( 'ASTRO_DEPLOY_HOOK_URL' ) ) {
		return;
	}
	$response = wp_remote_post( ASTRO_DEPLOY_HOOK_URL, [ 'timeout' => 10 ] );
	if ( is_wp_error( $response ) ) {
		error_log( 'Astro deploy hook failed: ' . $response->get_error_message() );
	}
} );

条件を「新旧どちらかの状態が公開」にしているので、公開・公開済み記事の更新・非公開化・ゴミ箱への移動のいずれでもビルドが走ります。下書きの保存ではビルドしません。

WP-Cron はサイトへのアクセスをきっかけに動くため、管理画面以外のアクセスがほとんどない WordPress では、イベントの実行が遅れることがあります。WordPress を非公開のサーバーに置く構成では特に起こりやすいので、wp-config.php に define( 'DISABLE_WP_CRON', true ); を書き、サーバーの cron から定期的に wp-cron.php を実行する設定にしておくと安定します。

確認は、記事を更新したあとにホスティングの管理画面でビルドが1回だけ始まるか、失敗したときに PHP のエラーログへ記録が残るかを見ます。

編集者の作業はどう変わるか

連携を始める前に、編集者に伝えておきたい変化があります。

プレビュー:WordPress のプレビューボタンは、WordPress のテーマで表示する仕組みです。静的サイトには下書きが含まれないため、Astro での見た目を公開前に確かめることはそのままではできません。対策としては、ステージング用の WordPress とサイトを用意する、Astro の一部のページをサーバー側で描画して認証付きで下書きを取得する、といった方法がありますが、後者は認証情報を安全に扱う設計が必要です。

反映までの時間:公開してからビルドが終わるまで数分かかります。「公開ボタンを押したのに変わらない」という問い合わせを減らすため、目安の時間を共有しておきます。

予約投稿:予約した時刻に WP-Cron が記事を公開し、その状態の変化で上記のビルド依頼が走ります。WP-Cron を安定させておくことが、予約投稿の正確さにもつながります。

ブロックの見た目:本文の HTML にはブロックエディターのクラス名が付いていますが、そのスタイルは Astro 側にはありません。WordPress に含まれるブロック用のスタイルシート(wp-includes/css/dist/block-library/style.min.css)を参考に必要な分を取り込むか、自前で用意します。テーマの theme.json で定義した色やフォントサイズのプリセットも、CSS 変数として出力されなくなるため、使っているものを Astro 側で定義し直す必要があります。

プラグインの表示機能:ショートコードは REST API の content.rendered に展開済みの HTML として入りますが、そのショートコードが必要とする CSS や JavaScript は読み込まれません。問い合わせフォームやコメントのように送信を伴う機能は、別のサービスか Astro 側の実装に置き換えます。

まとめ

  • Astro のコンテンツコレクションに、REST API を全ページ取得するローダーを書く。取得失敗時は例外を投げ、空のサイトを公開しない
  • schema で型を決めておくと、WordPress 側の変化にビルドの段階で気づける
  • 公開状態の変化を transition_post_status で拾い、WP-Cron で2分ぶんの変更をまとめてからデプロイフックを1回だけ呼ぶ
  • WP-Cron はサーバーの cron で動かし、予約投稿とビルドの遅れを防ぐ
  • プレビュー、反映までの時間、ブロックのスタイル、プラグイン機能の扱いを、連携前に編集者と共有する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲