CMS移行を伴うリニューアル:WordPressからHeadless CMSへ

W3Techsの統計では2025年時点でWordPressが全ウェブサイトの約43%を支えています[1]。一方、HTTP ArchiveのレポートではモバイルのCore Web Vitals(実ユーザーデータに基づく指標。以降CWV)合格率がおよそ4割にとどまる状況が続いています[2]。機能拡張を重ねた一体型CMSのテーマとプラグインはスピードやスケールの足かせになりやすく[3]、オムニチャネル配信(Web以外のアプリやサイネージなど複数チャネルへの配信)やチーム開発における変更速度にも影響します[4]。CTOの視点では、WordPressをコンテンツソースとして活かしつつフロントを分離するヘッドレス化は有力な選択肢ですが、成果の鍵はパフォーマンス、編集体験、SEOを同時に満たす設計と段階的移行にあります。本文ではデータに基づく移行の判断軸、落とし穴の回避策、Next.jsやWPGraphQLを用いた実装パターンを具体的なコードとともに提示し、測定と投資回収の考え方まで掘り下げます。
なぜHeadlessへ移るのか:成果とトレードオフを数値で語る
ヘッドレス化の主目的はビルド(UI開発)と配信(CDN/エッジ)を明確に分離することにあります。ビルドの自由度はフロントエンドフレームワークの選択に、配信の安定性はエッジキャッシュの一貫性にそれぞれ依存します。公開データや実務の知見では、高速化がコンバージョンや直帰率に与える影響が繰り返し報告されており[5]、LCP(Largest Contentful Paint。最大要素の描画)を2.5秒未満、CLS(Cumulative Layout Shift。累積レイアウトシフト)を0.1未満に保つことは検索流入と収益の双方に寄与するとされています[6]。テーマ主導の一体型構成からNext.js+エッジ配信へ移行すると、これらの目標を安定達成しやすくなるという報告が増えています[2][6]。もちろん、万能薬ではありません。編集画面とプレビューの乖離が生じやすいこと、プラグインで賄えていた処理をAPIとフロント側で再設計する負荷、コストの費目がホスティングからビルド・CDN・観測に再配分される点など、トレードオフは明確です。それでも、配信のボトルネックがPHP実行とDB I/OからCDNのヒット率とオフロード戦略へ移ることは、トラフィック増大局面の運用リスクを大幅に下げます[4]。
ビジネス指標で見れば、変更のリードタイム短縮とA/B実験の回転率向上が分かりやすい果実です。コンポーネント駆動のUIとスキーマ化されたコンテンツを組み合わせると、テンプレート修正が記事群全体に波及するまでの時間が短縮され、プレビューから公開までの待ち時間も詰められます。移行後に編集から公開までのリードタイムが短縮し、機能リリースのデプロイ頻度が高まる傾向は多くの現場で観測されます。費用については、インフラ費は横ばい〜小幅の増加に収まるケースもあり、パフォーマンス改善と運用効率化の相乗効果で中短期(数ヶ月〜四半期単位)での投資回収が期待されます。重要なのは、効果測定の設計を移行前から用意することです。これにより、移行の価値を数値で説明できます。
設計の勘所:コンテンツ、配信、観測を一体で考える
最初に固めるべきはコンテンツモデリングです。WordPressをヘッドレス化するならWPGraphQL(WordPressのデータをGraphQLで提供するプラグイン)やREST APIで取得できるスキーマに寄り添いながら、最終的に採用するHeadless CMS(Contentful、microCMS、Sanity、Strapiなど)の型へ写像します。ブロックエディタ由来のリッチテキストや埋め込みは、プレーンテキスト化で意味を失いがちです。ブロックの粒度で構造化し直して将来の流用先(アプリ、サイネージ、メール)への拡張性を確保しておくと再移行のコストが跳ね上がりません。画像や動画は生成系を含む派生バリアントの規約を事前に定め、原則としてオリジナルは一意、派生はCDNで遅延生成する方針がスケールします。
あわせて、SEOの土台となるコンテンツ戦略を設計段階で固定します。主要キーワードと検索意図(商用調査・情報探索など)をテンプレート単位でマッピングし、タイトル/H1/見出し/内部リンク/パンくず/スキーマ(構造化データ)の各フィールドをCMSの必須項目としてスキーマ化します。こうしておくと、テンプレート変更やチャネル追加時も「キーワードの一貫性」と「メタデータの欠落防止」が自動的に担保されます。重複コンテンツや類似記事は移行前に統合(canonical化や301)し、将来のクロール予算の無駄を減らす設計に寄与します。
配信ではキャッシュ戦略が要です。パブリックページはできる限りエッジキャッシュで捌き、個別最適が必要なパーソナライズはクッキーやヘッダで分岐しつつ、静的と動的の境界を明示します。長大なサイトでビルド時間が増大する場合は、ISR(Incremental Static Regeneration。増分静的再生成)とWebhookで再生成を駆動し、トラフィックが偏るページのみを先行生成する冷温混在の方針が有効です。TTFB(Time To First Byte。最初のバイトまでの時間)はエッジヒット時に200msを切ることを狙い、ミス時でも地域PoP(Point of Presence。CDN拠点)から500ms以内に収めるとLCP全体の安定性が上がります[2]。ルーティングは旧URL構造からのリダイレクトと正規化をルール化し、正規URL、カノニカル、hreflang、構造化データを連動させてSEOの連続性を守ります。計測はCWV、ヒット率、オリジンへのフォールバック率、ビルド・再生成キューの滞留などを常時可視化し、ボトルネックを数字で捉えます。観測の一貫性が移行後の安定運用を左右します。
Next.jsとWPGraphQLの基本パターン
WordPressをソースとする場合、WPGraphQLでスキーマ化されたデータをNext.jsの静的生成やISRに流し込むのが実装しやすい構成です。フェッチの際はタイムアウトと再試行、型安全の担保、そして不正データのバリデーションを組み込み、失敗時のフォールバックUIとログを用意します。以下は「必要なフィールドだけ取り、ISRで再生成、失敗時はnotFoundで検索エンジンにも正しいシグナルを返す」という最小パターンです。
// pages/[slug].tsx
import type { GetStaticProps, GetStaticPaths } from 'next';
import Head from 'next/head';
type Post = { id: string; slug: string; title: string; contentHtml: string };
const WP_ENDPOINT = process.env.WP_GRAPHQL_ENDPOINT!;
async function fetchGraphQL(query: string, variables: Record<string, any>) {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 8000); // タイムアウトでハングを防止
try {
const res = await fetch(WP_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables }),
signal: ctrl.signal
});
if (!res.ok) throw new Error(`WPGraphQL ${res.status}`);
const json = await res.json();
if (json.errors) throw new Error(JSON.stringify(json.errors));
return json.data;
} finally {
clearTimeout(t);
}
}
export const getStaticPaths: GetStaticPaths = async () => {
const data = await fetchGraphQL(
`query { posts(first: 100) { nodes { slug } } }`,
{}
);
const paths = data.posts.nodes.map((n: any) => ({ params: { slug: n.slug } }));
return { paths, fallback: 'blocking' }; // 未生成は初回アクセスでブロック生成
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
try {
const data = await fetchGraphQL(
`query ($slug: ID!) { post(id: $slug, idType: SLUG) { id slug title content } }`,
{ slug: params!.slug }
);
const p = data.post;
if (!p) return { notFound: true, revalidate: 60 }; // 404を正しく返す
const post: Post = { id: p.id, slug: p.slug, title: p.title, contentHtml: p.content };
return { props: { post }, revalidate: 300 }; // ISR: 5分ごとに再検証
} catch (e) {
console.error(e);
return { notFound: true, revalidate: 60 };
}
};
export default function PostPage({ post }: { post: Post }) {
return (
<>
<Head><title>{post.title}</title></Head>
<article dangerouslySetInnerHTML={{ __html: post.contentHtml }} />
</>
);
}
GraphQLクエリ自体はWPGraphQLが提供する型に準拠させ、必要なフィールドだけを取得して過剰なDOM肥大を避けます。タイトル、抜粋、本文、日付、カテゴリ、アイキャッチなどSEOに重要な要素を明示的に取得します。
query PostBySlug($slug: ID!) {
post(id: $slug, idType: SLUG) {
id
slug
title(format: RENDERED)
excerpt(format: RENDERED)
content(format: RENDERED)
date
categories { nodes { name slug } }
featuredImage { node { sourceUrl altText } }
}
}
WebhookとISRで編集体験を壊さない
編集者は保存や公開のたびに反映を待たされると生産性が落ちます。ISRとWebhookを組み合わせて、対象ページのみの再生成を非同期で行うと待ち時間を感じにくくなります。署名検証を伴うWebhookエンドポイントを用意し、再生成のキュー滞留を監視します。CMSごとにヘッダや署名方式は異なるため、検証ロジックは使用サービスに合わせて差し替えます。
// pages/api/revalidate.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import crypto from 'crypto';
function verifySignature(req: NextApiRequest) {
const secret = process.env.WEBHOOK_SECRET!;
const sig = req.headers['x-hub-signature-256'] as string;
const body = JSON.stringify(req.body);
const h = 'sha256=' + crypto.createHmac('sha256', secret).update(body).digest('hex');
return crypto.timingSafeEqual(Buffer.from(h), Buffer.from(sig));
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (req.method !== 'POST') return res.status(405).end();
if (!verifySignature(req)) return res.status(401).json({ ok: false });
const slug = req.body?.slug as string;
if (slug) {
await res.revalidate(`/${slug}`); // 対象ページのみ再生成
} else {
await res.revalidate('/'); // フォールバック
}
return res.json({ revalidated: true });
} catch (e) {
console.error(e);
return res.status(500).json({ revalidated: false });
}
}
エッジキャッシュとヘッダ設計
キャッシュはヒット率だけでなく安全な無効化もセットで設計すべきです。Next.jsのミドルウェアやSSRハンドラでCache-Controlを適切に付与し、stale-while-revalidateを前提にした劣化許容を明文化します。これにより、瞬間負荷やキャッシュミス時でも体感性能を損ねにくくなります。
// pages/server-side.tsx (SSRの例)
import type { GetServerSideProps } from 'next';
export const getServerSideProps: GetServerSideProps = async ({ res }) => {
res.setHeader('Cache-Control', 'public, s-maxage=300, stale-while-revalidate=120');
return { props: { now: Date.now() } };
};
export default function Page({ now }: { now: number }) {
return <div>{now}</div>;
}
リダイレクトの一元管理
移行では旧URLからの恒久的な移動が避けられません。リダイレクトはアプリケーションコードに散らさず、設定として宣言的に持つと再現性が高まります。Next.jsなら設定ファイルで読み込み可能です。合わせて、410(恒久削除)の扱い、クエリ付きURLの正規化、末尾スラッシュやwww有無のポリシーもルール化します。
// next.config.js
const fs = require('fs');
function loadRedirects() {
const raw = fs.readFileSync('./redirects.json', 'utf-8');
return JSON.parse(raw);
}
module.exports = {
async redirects() {
const rules = loadRedirects();
return rules.map(r => ({ source: r.from, destination: r.to, permanent: true }));
}
};
コンテンツ移送の自動化と変換
エディタが作った資産を一括で移送するには、旧APIからの収集、HTMLの正規化、画像の再取り込み、そして新CMSへの作成を安全に繋ぐ必要があります。レートリミットと再試行、ログの相関ID付与を実装し、途中で止まっても再開できるよう冪等性を担保します。あわせて、タイトル/H1やメタディスクリプション、OGP、alt属性などSEO関連フィールドの欠落チェックも移行スクリプト側で実施すると後工程が安定します。
// scripts/migrate.ts
import fetch from 'node-fetch';
import pLimit from 'p-limit';
type WPPost = { id: number; slug: string; title: { rendered: string }; content: { rendered: string } };
async function listPosts(page = 1): Promise<WPPost[]> {
const res = await fetch(`https://example.com/wp-json/wp/v2/posts?per_page=50&page=${page}`);
if (res.status === 400) return []; // 終了
if (!res.ok) throw new Error(`WP ${res.status}`);
return res.json();
}
async function createNewCMS(post: WPPost) {
const body = {
slug: post.slug,
title: post.title.rendered,
body: sanitize(post.content.rendered)
};
const res = await fetch(process.env.NEW_CMS_ENDPOINT!, {
method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.TOKEN}` },
body: JSON.stringify(body)
});
if (!res.ok) throw new Error(`NewCMS ${res.status}`);
}
function sanitize(html: string) {
return html.replace(/\sdata-[^=]+="[^"]*"/g, '');
}
async function main() {
const limit = pLimit(5);
for (let page = 1; ; page++) {
const posts = await listPosts(page);
if (posts.length === 0) break;
await Promise.all(posts.map(p => limit(() => createNewCMS(p))));
}
}
main().catch(e => { console.error(e); process.exit(1); });
SEOを落とさない移行戦略:二重運用と観測の設計
トラフィックを守るためには、切り替え日にすべてを賭けないことが最重要です。二重運用期間を設け、サブパス単位で新旧を段階的に切り替えます。URLの正規化とサイトマップは事前に新旧で差分比較し、主要クエリのランディングページが壊れていないかを検索コンソールとログで見ます。構造化データはスキーマの後方互換を意識し、パンくず、記事、FAQなどのタイプが変わる場合でもキーとなるプロパティが継続して出力されるようにします。メタデータはCMSのフィールドとして強制し、テンプレート側での上書きは例外扱いにします。こうしておくと、移行後の編集に揺らぎが出ず、CWV改善の効果が検索に正しく伝わります。
さらに、実務で効く具体策をいくつか挙げます。主要クエリごとに「キーワードマッピング表」を作成し、対象URL、意図、タイトル/H1、見出し構造、内部リンクの発信元/受け先、カノニカルを紐づけます。サイトマップは大規模サイトでは分割(例:/sitemap-posts.xml、/sitemap-categories.xml)し、更新頻度の高い集合を優先的に再生成します。内部リンクはテンプレート化し、重要ランディングへの被リンク本数とパスの深さを可視化して維持します。リダイレクトはワイルドカードに頼りすぎず、トップランディングは個別定義で精度を上げます。レンダリングはSSG/ISRを優先し、SSRが必要なページでもクリティカルCSSや遅延読込で初期描画を阻害しないようにします。クロール制御はrobots.txtとX-Robots-Tag、noindexの出し分けをテンプレートで管理し、404/410の差異はログからの自動抽出で補正します。
計測のダッシュボードは移行前から構築しておくと、因果の判断が容易です。LCP、CLS、INP(Interaction to Next Paint)などのCWVはフィールドデータを優先し、ページ種別とデバイス別に追います[7]。CDNヒット率、オリジン負荷、再生成キューのレイテンシ、エラーレート、そしてユーザー行動の主要ファネルを同一の時間軸で可視化すると、切替時の変動がどの層の問題かを迅速に切り分けられます。失敗を恐れる必要はありません。数値に基づく即時のロールバックと再展開が容易であることこそ、ヘッドレス化の設計上の利点なのです。
ケーススタディ:10,000記事のメディアを分離する
例えば約1万件の記事を抱えるメディアでは、WordPressテーマの技術的負債が膨らみやすく、編集と配信の関心を分離するメリットが大きくなります。この規模では、当面はWordPressをマスターとして残し、WPGraphQLで抽出しながらNext.jsを前段に、画像は最適化CDNに切り替え、コンテンツの新規作成は段階的にHeadless CMSへ移すハイブリッド構成が現実的です。二重運用期間中は人気上位のコア記事から先に新フロントで提供し、ロングテールはトラフィックの閾値を満たした順にISRで置き換えます。運用ではキャッシュ無効化の対象をURLパターンで限定し、全無効化を避けながら事故時の復旧を数分単位で回せる体制を整えます。こうした進め方は、LCP/CLSの安定、オーガニック流入の維持・回復、編集のプレビュー即時性向上といった効果につながりやすいと考えられます。費用はCDNとビルドの増加分をオリジンの縮小で相殺しうる一方、観測基盤への投資を計画的に見込むのが堅実です。
このシナリオからの示唆は単純です。移行の成功はプラットフォーム選定ではなく、スキーマ、配信、観測を繋ぐ設計と、段階的な露出コントロールに宿るということです。テーマやプラグインというレイヤの最適化に限界を感じたら、関心の分離をアーキテクチャの中心に据えて再設計する合図です。併せて、コンテンツの品質とブランド体験の一貫性を評価指標として明文化し、チーム全員が追うべきメトリクスとして可視化すれば、技術投資は経営の言葉で語れるようになります。移行は終わりではなく、改善の速度を取り戻すための起点だと位置づけるのが良いでしょう。
まとめ:関心の分離で速度と品質を両立する
WordPressからHeadless CMSへの移行は、単に流行に乗る意思決定ではありません。編集、フロント、配信、観測という異なる関心を分離し、それぞれに最適な技術と運用を当て込む設計転換です。データに基づけば、パフォーマンス向上と変更速度の回復は収益とブランド体験の改善に繋がります。とはいえ、すべてを一度に置き換える必要はありません。まずはクリティカルなランディングの一部から段階的に切り替え、二重運用下で指標を観測しながら広げていく進め方が現実的です。
次に取るべき小さな一歩を考えてみてください。例えば、WPGraphQLを有効化してスキーマの棚卸しをする、Next.jsでパイロットの詳細ページをISRで公開してTTFBとLCPを測る、旧URLのリダイレクト定義を設定として外出しする、といった取り組みは明日から始められます。数字で変化を可視化できたとき、移行の是非は自然にチームの共通認識になります。速度、安定性、編集体験の三つ巴を同時に満たすアーキテクチャへ、一歩ずつ移っていきましょう。
参考文献
- WordPress.com. Over 43% Use WordPress (2025-04-17). https://wordpress.com/blog/2025/04/17/wordpress-market-share/
- HTTP Archive Web Almanac 2024 – Performance. https://almanac.httparchive.org/en/2024/performance
- Contentstack. Discover Headless CMS Benefits for Scaling Digital Experiences. https://www.contentstack.com/blog/all-about-headless/discover-headless-cms-benefits-for-scaling-digital-experiences
- Smashing Magazine. Headless WordPress With Storyblok. https://www.smashingmagazine.com/2021/07/wordpress-headless-cms-storyblok/
- web.dev. The impact of Core Web Vitals on ad revenue. https://web.dev/articles/cwv-impact-ad-revenue
- Google Search Central. Core Web Vitals. https://developers.google.com/search/docs/appearance/core-web-vitals
- HTTP Archive Web Almanac 2024 – Performance (FID replaced by INP). https://almanac.httparchive.org/en/2024/performance#fid-vs-inp