あるクライアントから 2023 年初頭に連絡がありました。WordPress で約 14,000 件の投稿を運営するメディアパブリッシャーで、6 年間にわたって 4 人の開発者によって作られた寄せ集めのテーマ、そして本当に恥ずかしい Core Web Vitals スコアでした。LCP はモバイルで 7.2 秒に達していました。WP Rocket を試しました、CDN を試しました、プラグインの半分を削除することさえしました。それでも遅いままでした。問題は WordPress 自体にはありませんでした。すべてのページレンダリングが PHP を通り、肥大化したテーマを通り、2018 年以降見直されていないデータベースクエリチェーンを通っていたのです。
その時点で、私は Astro をフロントエンドとするヘッドレスセットアップに本格的にコミットしました。流行だからではなく、数千の投稿と大量の編集コンテンツを扱うサイトにとって、これが唯一の意味のあるアーキテクチャだったからです。Astro as the frontend. Not because it's fashionable — because for a site pushing thousands of posts with heavy editorial content, it was the only architecture that made sense.
正確に設定した方法はこちらです。トレードオフ、実際の設定、私を困らせた部分です。
---
Astro が Next.js ではなく選ばれた理由
この質問はよくもらいます。React中心のチームがすでにいるか、複雑なクライアント側のインタラクティビティが必要なら、Next.jsは明らかな選択肢です。ただ、コンテンツが多いサイトなら?Astroの方が勝ります。そこまで迷う必要もありません。
AstroはデフォルトではゼロのJavaScriptで配信します。ブログ、ニュースサイト、ドキュメントポータルなら、それが正しいデフォルトです。JavaScriptが必要な場所だけを選んで追加するのであって、ほぼ不要な200kbのReactバンドルをオプトアウトするのではありません。Astroドキュメントの部分的なハイドレーション(彼らはそれをIslands architectureと呼んでいます)の方が一文より詳しく説明されていますが、要するに:インタラクティブな部分だけがJSを得ます。記事本文、ヘッダー、サイドバー?静的なHTMLです。zero JavaScript by default. For a blog, a news site, or a documentation portal, that's the correct default. You opt into JavaScript where you need it, rather than opting out of a 200kb React bundle you mostly don't want. The Astro docs on partial hydration — they call it the Islands architecture — explain it better than I can in a sentence, but the short version is: only the interactive bits get JS. The article body, the header, the sidebar? Static HTML.
2022年後半、NextとWordPressで法律関連のコンテンツサイトを作りました。十分に速かったのですが、クライアントは「今は速いはずだろう」というのに、モバイルのLighthouseスコアが74だと何度も聞いてきました。ハイドレーションのオーバーヘッド。Astroなら、同じタイプのサイトは今、定期的に95~98に達しています。自慢ではなく、そういったアーキテクチャが無料で与えてくれるものです。
Astroが得意でないこと
正直なところ。サイトがリアルタイムのパーソナライゼーション、重いショッピングカート、または多くのコンポーネント間でクライアント側の状態に本当に依存するものが必要なら、Astroは不便に感じられ始めます。Reactアプリではありません。Islands パターンは強力ですが、SPAを構築するのとは異なるメンタルモデルです。2023年中盤にクライアント向けダッシュボードをAstroプロジェクトに無理やり入れようとして、2週間以内にNext.jsに戻しました。何を作ろうとしているのかを知ってください。
---
WordPressをHeadless CMSとして設定する
WordPressは本当に良いheadlessバックエンドです。WP REST APIはコアに含まれていて、ドキュメントも充実していて、編集チームは何も新しく学ぶ必要がありません。その最後のポイントは、開発者が通常認める以上に重要です。WP REST API ships in core, it's well-documented, and your editorial team doesn't have to learn anything new. That last point matters more than developers usually admit.
私が使う設定はこちらです:
- WordPressをサブドメイン上にインストールします。cms.yourdomain.comまたはapi.yourdomain.comを使っています。基本認証の背後に置くか、最低限、直接公開トラフィックを制限してください。フロントエンドはyourdomain.comです。2つの別々のデプロイメント。 — I use
cms.yourdomain.comorapi.yourdomain.com. Keep it behind basic auth or at minimum restrict direct public traffic. The frontend isyourdomain.com. Two separate deployments. - [WPGraphQL プラグイン](https://www.wpgraphql.com/)をインストールしてください。コンテンツサイトではREST APIよりGraphQLの方が好みです。クエリをコンポーネントと一緒に配置でき、必要なフィールドだけを取得できるからです。オーバーフェッチがありません。REST APIでも問題ありませんが、ポストタイプごとにカスタムフィールドが15個以上になると、GraphQLのアプローチの方が明らかにクリーンです。 — I prefer GraphQL over REST for content sites because you can co-locate your queries with your components and fetch exactly the fields you need. No over-fetching. The REST API is fine, but once you've got 15+ custom fields per post type, the GraphQL approach is noticeably cleaner.
- Advanced Custom Fields(ACF)と WPGraphQL for ACF拡張をインストールしてください。この組み合わせこそが、WordPressをヘッドレスコンテンツモデルとして本当に柔軟にする要です。ポストタイプごとに構造化されたデータを定義し、GraphQL経由で公開し、Astroが綺麗に消費できます。 and the WPGraphQL for ACF extension. This combo is what makes WordPress genuinely flexible as a headless content model — you can define structured data per post type, expose it through GraphQL, and Astro consumes it cleanly.
- コメント、絵文字、デフォルトのXML-RPCをまだ無効にしていなければ、無効にしてください。これらはオーバーヘッドと攻撃対象を増やすだけです。 if you haven't already. These add overhead and attack surface you don't need.
- ビルドを始める前に、パーマリンクを適切に設定してください。Astroのルートが既に設定されている途中でパーマリンクを変更するのは、本当に面倒です。 to something sensible before you start building. Changing them mid-project when your Astro routes are already set is a genuine pain.
人々を困らせる一つのこと:CORS。デフォルトでは、WordPressはAstro開発サーバー(localhost:4321で実行中)からのリクエストを許可しません。開発中は、テーマの functions.php またはちょっとしたユーティリティプラグインに以下を追加してください:localhost:4321) make requests to your WP install. Drop this into your theme's functions.php or a small utility plugin during development:
`` add_action('init', function() { header("Access-Control-Allow-Origin: *"); }); `` add_action('init', function() { header("Access-Control-Allow-Origin: *"); }); ``
本番環境では特定のオリジンに限定してください。当然のことながら。
---
Astroプロジェクト構造
複数プロジェクト全体で、これは一貫性のある自分のやり方です。ダース単位のヘッドレスビルドをしてきて、これが機能するものです:
`` src/ components/ layouts/ pages/ index.astro blog/ [slug].astro lib/ wpgraphql.ts ← すべての WP クエリロジックはここに格納 styles/ `` src/ components/ layouts/ pages/ index.astro blog/ [slug].astro lib/ wpgraphql.ts ← all WP query logic lives here styles/ ``
lib/wpgraphql.ts ファイルは、すべての GraphQL フェッチを一元管理する場所です。ページファイル全体に散らばったインラインフェッチ呼び出しはありません。すべてのクエリは名前付きの、エクスポートされた非同期関数です。14,000 件の投稿全体でこれをデバッグしていて、午前2時に何かが壊れた場合 — 後々自分自身に感謝することになります。lib/wpgraphql.ts file is where I centralise every GraphQL fetch. No inline fetch calls scattered across page files. Every query is a named, exported async function. Debugging this across 14,000 posts when something breaks at 2am — you'll thank yourself later.
ビルド時に投稿をフェッチする
Astro の getStaticPaths はここで最も重要です。数千の投稿を持つブログの場合:getStaticPaths is your bread and butter here. For a blog with thousands of posts:
`` export async function getStaticPaths() { const posts = await getAllPostSlugs(); // calls WPGraphQL return posts.map(post => ({ params: { slug: post.slug }, })); } `` export async function getStaticPaths() { const posts = await getAllPostSlugs(); // calls WPGraphQL return posts.map(post => ({ params: { slug: post.slug }, })); } ``
getAllPostSlugs は after カーソルを使って WPGraphQL をページネーションします — WordPress の GraphQL レイヤーはデフォルトでリクエストあたり 100 投稿を返すため、14,000 投稿の場合、ビルド時に 140 リクエスト作成します。怖く聞こえますね。実際には、まともなサーバー上で、フルビルドは約 4~5 分で実行されます。1日に数回再構築するサイトには完全に許容できます。 paginates through WPGraphQL using after cursors — WordPress's GraphQL layer returns 100 posts per request by default, so for 14,000 posts you're making 140 requests at build time. That sounds scary. In practice, on a decent server, the full build runs in about 4–5 minutes. Perfectly acceptable for a site that rebuilds a few times per day.
---
画像処理で気が狂わないようにする
これはだれも十分に話題にしない部分です。WordPress は CMS サブドメインを指すイメージ URL を保存しています。Astro が静的にビルドする場合、それらのイメージはまだ cms.yourdomain.com に存在します — つまり、訪問者のブラウザが WordPress サーバーからイメージをフェッチしており、CDN をバイパスする可能性があります。cms.yourdomain.com — which means your visitors' browsers are fetching images from your WordPress server, potentially bypassing your CDN.
これを処理するいくつかの方法:
- 両方のドメインの前に Cloudflare を配置する。最もシンプルなオプションだ。yourdomain.com と cms.yourdomain.com の両方を Cloudflare 経由でプロキシし、/wp-content/uploads/* に積極的なキャッシング設定を施せば、ほぼ問題ない。 Simplest option. Proxy both
yourdomain.comandcms.yourdomain.comthrough Cloudflare, configure aggressive caching on/wp-content/uploads/*, and you're mostly fine. - メディアオフロードプラグインを使う。私は WP Offload Media を気に入っている — アップロードを S3(またはそれに互換性のあるストレージ)に移動し、URL を自動的に書き換える。本格的なトラフィックを想定するサイトには、これが私が採用するアプローチだ。WordPress サーバーが画像を全く配信しなくなる。 I like WP Offload Media — it moves uploads to S3 (or compatible storage) and rewrites URLs automatically. This is the approach I use for any site expecting serious traffic. Your WordPress server stops serving images entirely.
- Astro の Image コンポーネント。ビルド時に制御できる画像(GraphQL 経由で取得したアイキャッチ画像など)であれば、リモート URL を Astro の <Image> コンポーネントに渡すだけで、最適化、リサイズ、そしてビルド出力から配信してくれる。素晴らしく機能する。ポスト本文の HTML に埋め込まれた画像には対応していない — そういう場合は別の処理が必要になる。 For images you control at build time (featured images pulled via GraphQL), you can pass the remote URL into Astro's
<Image>component and it'll optimise, resize, and serve them from your build output. Works brilliantly. Does not work for images embedded in post body HTML — that requires a different pass.
Seahawk は去年、トラベルコンテンツクライアントを案件として抱えていた — 約 8,000 投稿、画像が非常に多く、記事あたり平均 12 画像。WordPress サーバーは、ヘッドレス設定にしていたにもかかわらず、画像リクエストだけでハンマーで叩かれていた。S3 + CloudFront への移行で、オリジンの帯域幅を 94% 削減した。ホスティング費用の観点からは、本当に変革的だった。
---
インクリメンタルビルドと再構築の問題
スタティック生成を大規模で運用する際の実際の課題がこれだ:編集者が午後 3 時に投稿の訂正を公開して、フルリビルドが完了するまで 5 分待たなければいけない。ニュースルーム環境ではこれは受け入れ難い。
私が使ってきた方法がいくつかある:
オプション 1:Netlify または Vercel のオンデマンド ISR。Astro はアダプタ経由でサーバーサイドレンダリングに対応している — ほとんどのページはスタティック、特定のルートはオンデマンドで レンダリングされるハイブリッドモードで Astro を実行できる。ニュースサイト向けなら、直近 30 日分の投稿はスタティック事前レンダリング(高トラフィック、速度が必要)し、古いアーカイブページはオンデマンドサーバーレンダリング設定することが多い。両者のいいとこ取りだ。 Astro supports server-side rendering with adapters — you can run Astro in hybrid mode where most pages are static but specific routes are rendered on-demand. For a news site, I'll often statically pre-render the last 30 days of posts (high traffic, needs speed) and set older archive pages to server-render on demand. Best of both worlds.
オプション 2:Webhook トリガー による部分ビルド。WordPress は投稿保存時に Webhook を発火する(WP Webhooks プラグインを使えば簡単)。その Webhook が Netlify または Vercel のデプロイフックをたたく。ビルドが走り、変更があった部分だけを取得する。完全な部分ビルドではない — Astro はまだ全て再構築している — ただし、ビルドが高速に保たれていれば 4 分なら実用的だ。 WordPress fires a webhook on post save (easy with the WP Webhooks plugin). That webhook hits a Netlify or Vercel deploy hook. The build runs, it fetches only what's changed. It's not truly partial — Astro still rebuilds everything — but if you keep your build fast, 4 minutes is workable.
オプション3:SSRを全体に使用する。Node アダプタで Astro をVPSにデプロイします(私は Hetzner を使っています——安い、高速、信頼できます)。すべてのページはリクエスト時にレンダリングされ、Nginx または Cloudflare レベルで積極的にキャッシュして、投稿の更新は即座に反映されます。50,000件以上の投稿がある適切なパブリッシング運用を行う場合、これが私の選択です。 Deploy Astro with the Node adapter to a VPS (I use Hetzner for this — cheap, fast, reliable). Every page renders on request, you cache aggressively at the Nginx or Cloudflare level, and you have instant post updates. This is what I'd do for a proper publishing operation over 50,000 posts.
本音を言えば、ほとんどのサイトはオプション1または3の複雑性を必要としません。Webhook でトリガーされる4分間のリビルドはコンテンツサイトの90%に十分です。
---
パフォーマンス:実際に得られるもの
冒頭で取り上げたパブリッシャープロジェクト——Astro へ移行した後の結果です:
- モバイル上の LCP は7.2秒から1.1秒に低下しました(WebPageTest でロンドンノードからテストした結果)1.1s on mobile (tested with WebPageTest from a London node)
- Total Blocking Time は約800ミリ秒からゼロに低下しました(デフォルトではゼロJS のことを思い出してください)0ms (zero JS by default, remember)
- Google Search Console の Core Web Vitals レポートはデプロイメントから6週間以内に「良好」URL が3%から91%に向上しました91% "Good" within six weeks of deployment
- ホスティングコストは削減されました。WordPress サーバーはページを提供しなくなり、API レスポンスのみになったためです
これは魔法ではありません。PHP レンダリングをクリティカルパスから除去し、すべての読者に400kb のテーマ JavaScript バンドルを配信するのをやめることで起こるだけです。
---
つまずきやすいポイント
実際のプロジェクトでデバッグしてきたこと、ぶっちゃけた話です:
- 下書き投稿のプレビュー。ヘッドレス設定では本当に面倒です。WordPressのネイティブプレビューはフロントエンドレンダリングに依存しています。WordPressのプレビューnonce を受け取ってWPGraphQL経由で下書きを取得するカスタムプレビューエンドポイントをAstroで構築する必要があります。難しくはありませんが、きちんとやるのに1日かかります。 This is genuinely annoying in a headless setup. WordPress's native preview relies on front-end rendering. You need to build a custom preview endpoint in Astro that accepts a WordPress preview nonce and fetches the draft via WPGraphQL. Not hard, but it takes a day to do properly.
- リダイレクト。古いサイトに.htaccessに数百個のリダイレクトがあった場合、それらはいまWordPressサーバーに存在しています。Astroの設定で複製するか、WordPressにアクセスしたままで特定パスをプロキシするか、どちらかが必要です。両方やったことがあります。Astroで複製するほうが長期的にはきれいです。 If the old site had hundreds of redirects in
.htaccess, those live on the WordPress server now. You need to either replicate them in Astro's config, or keep WordPress accessible and proxy specific paths. I've done both. Replicating in Astro is cleaner long-term. - 検索。WordPressの組み込み検索はヘッドレス設定では役に立ちません。Algoliaと「WP Search with Algolia」プラグインを使っています。WPで投稿をインデックス化して、Astroの Island コンポーネントからAlgoliaをクエリします。よく機能します。 WordPress's built-in search is useless in a headless setup. I use Algolia with the WP Search with Algolia plugin. Index your posts in WP, query Algolia from an Astro Island component. Works well.
- メニューとナビゲーション。WordPressのメニューはWPGraphQLを通じて公開するのが微妙に厄介です。wpgraphql-acfのルートのほうがシンプルなことが多い— ナビゲーションをACFリピーターとしてモデル化して終了です。 WordPress menus are weirdly fiddly to expose through WPGraphQL. The
wpgraphql-acfroute often ends up being cleaner — just model your nav as an ACF repeater and call it done.
---
FAQ
WPGraphQLが必要ですか、それともREST APIだけでいいですか?
REST APIは完全に使えます。WordPressコアに組み込まれており、追加プラグインは不要です。標準的なポストタイプと最小限のカスタムフィールドを持つシンプルなサイトであれば問題ありません。GraphQLが活躍するのは、複雑なコンテンツモデルで、タイプごとに多数のカスタムフィールドがある場合です。_embedパラメータやネストされたREST呼び出しと格闘することなく、必要なフィールドだけを1つのリクエストで取得できるので、書くクエリのたびに時間を節約できます。どちらを使うかはあなた次第です。複雑さが一定レベルを超えると、GraphQLの方が洗練されていると思います。_embed parameters and nested REST calls, saves time on every query you write. Up to you. I just find GraphQL cleaner past a certain complexity threshold.
メンバー限定コンテンツのWordPress認証はどのように処理しますか?
JWT認証が標準的なアプローチです。JWT Authentication for WP REST APIプラグインをインストールして、ログイン時にトークンを発行し、GraphQLリクエストヘッダーに渡します。Astro側では、SSRルート(静的ではなく)を使ってこれを処理するので、ユーザー固有のコンテンツはリクエストごとにサーバー側で取得されます。これを静的に行おうとしないこと。そうするとカオスになります。JWT Authentication for WP REST API plugin, issue tokens on login, pass them in your GraphQL request headers. On the Astro side, you'd handle this with an SSR route (not static) so the user-specific content is fetched server-side per request. Don't try to do this statically — that way lies madness.
小規模ブログにとっては過度ではありませんか?
おそらくそうです。500件以下のポストと1人のエディターであれば、ヘッドレスセットアップを維持するオーバーヘッドは見合いません。優れたWordPressテーマを使い、画像を最適化して、それで済ませてください。このアーキテクチャが報われるのは、ボリューム、編集の複雑さ、またはパフォーマンスが収益に実質的な影響を与えるレベルのトラフィックがある場合です。
本番環境のホスティング構成はどのような感じですか?
WordPress(CMS のみ)は小規模VPS、または管理型WordPressホストで運用します。私はKinstaまたはCloudwaysを使用しています。Astroフロントエンドはプロジェクトに応じてVercel、Netlify、またはNginxを備えたHetzner VPSで運用します。すべてにCloudflareを配置します。中規模なコンテンツサイトの月額総費用は通常£60~£120で、多くの場合、負荷で苦労していた統合WordPressホストをクライアントが支払っていた費用より少ないです。less than what clients were paying for an all-in-one WordPress host that was struggling under the load.
---
正直な要約は以下の通りです。ヘッドレスWordPressとAstroは、コンテンツサイトにしばらくの間起きた最良の事柄の1つです。新しいからではなく、ツールがようやくそのアイデアに追いついたからです。WPGraphQLは安定しており、Astroのビルドシステムは高速で、パフォーマンスゲインは実数で測定可能です。
アーキテクチャを早期に正しく設定してください。特に画像戦略とリビルドアプローチです。そうすれば、後で消火活動に費やす時間ははるかに少なくなります。それだけです。
