何度聞いたか数え切れないほどだ:「WordPressは始めた時は良かったけど、今は...」そして問題のリストが続く。サイトが読み込むのに6秒かかる。最後の更新後にコンタクトフォームプラグインが壊れた。2022年から保守されていないプラグインに重大な脆弱性がある。元のテーマを作った開発者は消息を絶った。聞き覚えがあるだろうか?

ここが重要なポイントだ――WordPressはウェブの40%以上を支えており、その理由は正当だ。それはアプローチしやすく、膨大なエコシステムを持ち、多くのビジネスを素早くオンラインに乗せた。しかし、あなたを始めさせるツールと、あなたと一緒に成長するツールには違いがある。これを読んでいるなら、おそらくその壁に直面している。WordPressからヘッドレスNext.js + Supabaseアーキテクチャへの実際の移行がどのように見えるか――マーケティング版ではなく、実際のエンジニアリングプレイブック――を説明する。

目次

Outgrown WordPress? A Migration Playbook for Next.js + Supabase

実際にWordPressから卒業した兆候

全員がWordPressから離れる必要があるわけではない。明確にしておきたい。個人ブログや地元企業の案内サイトを運営しているなら、まともなテーマと少数のプラグインを備えたWordPressはおそらくまだ正しい選択肢だ。しかし、卒業した明確な兆候がある:

プラグインの競合が毎月物を壊している

WooCommerceを更新すると、ページビルダーが壊れる。ページビルダーを更新すると、SEOプラグインが警告を投げる。ホストが要求するためPHP 8.2にアップデートすると、3つのプラグインが完全に動作を停止する。これはバグではなく、アーキテクチャだ。WordPressプラグインはすべて同じグローバルスコープ、同じフック、同じデータベースを共有する。すべてのプラグインは他のすべてのプラグインとの潜在的な競合だ。

30個、40個、さらには60個以上のアクティブプラグインを実行しているWordPressサイトを監査したことがある。その時点で、ウェブサイトを保守しているのではない。ジェンガの塔を保守しているのだ。

パフォーマンスがフルタイムの仕事になっている

PageSpeedスコアが30台だ。キャッシュプラグイン、画像最適化プラグイン、圧縮プラグイン、CDNプラグインをインストールしている――他の25個のプラグインによって引き起こされたパフォーマンスの問題を修正するために。皮肉が濃い。

WordPressはすべてのリクエストで動的にページを生成する(キャッシュされない限り)。各プラグインは独自のCSSおよびJavaScriptファイルを挿入できる。一般的なWordPressページは15~30個の個別のレンダリングブロックリソースを読み込む。GoogleのCore Web Vitalsデータは、WordPressサイトが3つのCWV指標すべてで33%の合格率を持っていることを示しており、これは最新のJavaScriptフレームワークで構築されたサイトの52%と比較される。

セキュリティの脆弱性があなたを眠れなくしている

WPScanの脆弱性データベースは数千のWordPress脆弱性を追跡しており、大多数はプラグインとテーマにある。ユーザーデータ、支払い、または機密情報の種類を処理するサイトを実行している場合、各プラグインは攻撃面だ。Patchstackは、WordPress脆弱性の97%がプラグインから来ていることを報告した。

基本的に、dozens of independent developers――その多くはプラグインをサイドプロジェクトとして保守している――にセキュリティポスチャーを信頼している。

開発チームがそれに対して嫌われている

これは過小評価されている。優れた開発者はもはやWordPressで作業したくない。PHP-template-spaghetti-with-ACF-fieldsのワークフローは、最新のコンポーネントベースの開発と比べて苦痛だ。エンジニアリング才能を引き付けて保有しようとしているなら、テックスタックが重要だ。

WordPressの税:プラグイン地獄が本当にかかるコスト

これにいくつかの数字を置こう。中規模のWordPressサイト(e-commerceサイトまたはブログ、ユーザーアカウント、カスタム機能を備えたSaaSマーケティングサイトなど)の場合、年間の「WordPress tax」は通常次のようだ:

費用カテゴリ 年間推定
プレミアムプラグインライセンス(15-20個のプラグイン) $1,500 - $4,000
管理型WordPressホスティング(WP Engine、Kinsta) $1,200 - $6,000
セキュリティ監視+クリーンアップ(Sucuri、Wordfence) $300 - $500
パフォーマンス最適化時間(開発者時間) $3,000 - $8,000
プラグイン競合デバッグ(開発者時間) $2,000 - $6,000
更新により物が壊れる場合の緊急修正 $1,000 - $4,000
合計年間WordPress税 $9,000 - $28,500

これは単一の新しい機能を構築する前だ。照明をつけたままにしておくコストだ。

Next.js + Supabaseがスタック選びで意味を持つ理由

ヘッドレスに行く方法はダースある。Gatsby(Netlifyが買収して以来、本質的にメンテナンスモードにあるが)を使用できる。Remix、Astro、またはSvelteKitを使用できる。バックエンドについては、Firebase、PlanetScale、またはカスタムAPIを使用できる。

2026年にWordPressから移行しているチームについては、Next.js + Supabaseは達成が難しい甘い場所に当たる。ここがなぜかだ。

Next.js:すべてを行うフロントエンド

Next.js 15(2024年10月以来安定)は、デフォルトでサーバーコンポーネントを提供します。つまり、静的サイトのパフォーマンスと動的なものの柔軟性が得られます。ブログ投稿を構築時に静的に生成し、動的なページをサーバーレンダリングし、インタラクティブなコンポーネントをクライアントレンダリングできます――すべて同じアプリで。

WordPressから移行しているチームにとって、主な利点は:

  • 組み込みの画像最適化――2~3個のWordPressプラグインを置き換える
  • 自動コード分割――各ページは必要なJSのみを読み込む
  • エッジミドルウェア――CDNレベルでリダイレクト、認証、A/Bテストを処理する
  • 増分静的再生成(ISR)――フルデプロイメントなしに個別ページを再構築する
  • AppRouterとReactサーバーコンポーネント――クライアント側のJavaScriptを大幅に削減する

Social Animalで多くのNext.jsプロジェクトを構築していることがあります(Next.js開発機能を確認してください)、WordPressのパフォーマンスの違いは一貫して劇的です。

Supabase:WordPressが望んでいたバックエンド

SupabaseはPostgreSQLに基づいて構築されたオープンソースのFirebase代替。以下を提供します:

  • スキーマから自動生成されるRESTおよびGraphQL APIを備えた完全なPostgresデータベース
  • 組み込み認証(電子メール、OAuth、マジックリンク、SSO)
  • きめ細かいアクセス制御のための行レベルセキュリティポリシー
  • WebSocket経由のリアルタイムサブスクリプション
  • サーバーレスバックエンドロジック用のエッジ機能
  • CDN配信を備えたファイルアップロード用のストレージ

WordPress移行の特に、Supabaseは優れています。WordPressはMySQLを使用し、データモデルはPostgreSQLに驚くほどよくマップされるからです。カスタムポストタイプはテーブルになります。ポストメタはJSONB列になります。ユーザーデータはほぼ1:1でマップされます。

Supabaseの無料ティアには、500MBデータベース、1GBストレージ、認証で50,000の月間アクティブユーザーが含まれます。Pro計画は$25/月で、ほとんどのプロダクションサイトをカバーしています。それを$30-$100/月と比較してください。管理されたWordPressホスティングだけで支払っています。

Outgrown WordPress? A Migration Playbook for Next.js + Supabase - architecture

移行プレイブック:フェーズごと

ここは私がdozens of WordPress migrationsで洗練されたアプローチです。週末のプロジェクトではありません――サイトの複雑さに応じて4~12週間を予算化してください――しかし、フェーズに従うと予測可能で低リスクです。

フェーズ1:監査とアーキテクチャ(1週目)

単一行のコードを書く前に:

  1. wp plugin list --status=activeを使用して完全なプラグインリストをエクスポート(WP-CLI)
  2. すべてのプラグインを新しいスタックの代替にマップ
  3. すべての投稿、ページ、分類法、カスタムポストタイプを含む完全なURL構造をエクスポート
  4. すべてのフォーム、統合、サードパーティ接続を文書化
  5. テーマのfunctions.phpに存在するカスタム機能を特定

プラグインマッピング演習は重要です。一般的な代替がどのように見えるかを示します:

WordPressプラグイン ヘッドレス置換
Yoast SEO Next.js組み込みメタデータAPI + generateMetadata()
WP Super Cache / W3 Total Cache 不要(デフォルトで静的)
Wordfence / Sucuri Supabase RLS + Vercelの組み込みDDoS保護
Contact Form 7 / Gravity Forms React Hook Form + Supabase Edge Function
WooCommerce Saleor、Medusa.js、またはShopify StorefrontAPI
ACF / Custom Fields Supabaseテーブルと型付きスキーマ
WP Migrate DB ワンタイムSupabase移行スクリプト
Smush / ShortPixel Next.js Imageコンポーネント(組み込み)
Elementor / WPBakery Reactコンポーネント(それを逃すことはありません)

フェーズ2:新しいスタックのセットアップ(2週目)

# Next.jsプロジェクトを作成
npx create-next-app@latest my-site --typescript --tailwind --app --src-dir

# Supabaseをインストール
npm install @supabase/supabase-js @supabase/ssr

# 環境変数を設定
cp .env.example .env.local

.env.local

NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

Vercelに直ちにデプロイします。はい、有意義なものを構築する前に。初日からライブプレビューURLを持つと、作業方法が変わります――利害関係者は進捗を見ることができ、デプロイメントの問題を早期に発見します。

データ移行:WordPressからコンテンツを取り出す

ここは、ほとんどの移行ガイドが曖昧になる場所です。具体的にしましょう。

ステップ1:WordPressデータをエクスポート

組み込みWordPressXMLエクスポートを使用しないでください。不完全で構造が悪いです。代わりに、WP-CLIと直接的なデータベースクエリを使用します:

# 投稿をJSONとしてエクスポート
wp post list --post_type=post --format=json --fields=ID,post_title,post_content,post_excerpt,post_date,post_status,post_name > posts.json

# ページをエクスポート
wp post list --post_type=page --format=json --fields=ID,post_title,post_content,post_excerpt,post_date,post_status,post_name > pages.json

# カスタムポストタイプをエクスポート
wp post list --post_type=your_cpt --format=json > cpt.json

# ポストメタ(ACFフィールドなど)をエクスポート
wp eval 'global $wpdb; $results = $wpdb->get_results("SELECT post_id, meta_key, meta_value FROM wp_postmeta WHERE meta_key NOT LIKE \"_%\""); echo json_encode($results);' > postmeta.json

ステップ2:変換してSupabaseに読み込む

移行スクリプトを書く。これにはTypeScriptを好みます:

import { createClient } from '@supabase/supabase-js'
import posts from './exports/posts.json'

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
)

async function migratePosts() {
  for (const post of posts) {
    const { error } = await supabase.from('posts').insert({
      wp_id: post.ID,
      title: post.post_title,
      slug: post.post_name,
      content: convertWpContentToMdx(post.post_content),
      excerpt: post.post_excerpt,
      published_at: post.post_date,
      status: post.post_status === 'publish' ? 'published' : 'draft',
    })
    
    if (error) console.error(`Failed to migrate post ${post.ID}:`, error)
  }
}

function convertWpContentToMdx(html: string): string {
  // turndownまたはrehypeを使用してWordPressHTMLをMDXに変換
  // ショートコード、埋め込み、Gutenbergブロックを処理
  // これが移行複雑性の80%が存在する場所
}

convertWpContentToMdx関数は最も時間を費やしたところです。WordPressコンテンツはHTML、ショートコード、Gutenbergブロックコメント、埋め込みoEmbed URLの混乱です。turndownなどのライブラリは基本的なHTML-to-Markdown変換を処理しますが、ショートコードとブロックのカスタムルールが必要になります。

ステップ3:メディアを移行

import { createClient } from '@supabase/supabase-js'
import fetch from 'node-fetch'

async function migrateMedia(mediaItems: any[]) {
  for (const item of mediaItems) {
    const response = await fetch(item.source_url)
    const buffer = await response.buffer()
    
    const { error } = await supabase.storage
      .from('media')
      .upload(`uploads/${item.slug}.${item.mime_type.split('/')[1]}`, buffer, {
        contentType: item.mime_type,
      })
    
    if (error) console.error(`Failed to upload ${item.slug}:`, error)
  }
}

Next.jsで新しいフロントエンドを構築

Supabaseのデータを使用して、フロントエンドの構築は楽しい部分です。Next.js AppRouterを使用した典型的なブログ投稿ページを示します:

// src/app/blog/[slug]/page.tsx
import { createClient } from '@/lib/supabase/server'
import { notFound } from 'next/navigation'
import { MDXRemote } from 'next-mdx-remote/rsc'

export async function generateMetadata({ params }: { params: { slug: string } }) {
  const supabase = createClient()
  const { data: post } = await supabase
    .from('posts')
    .select('title, excerpt, og_image')
    .eq('slug', params.slug)
    .single()

  if (!post) return {}

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: { images: [post.og_image] },
  }
}

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const supabase = createClient()
  const { data: post } = await supabase
    .from('posts')
    .select('*')
    .eq('slug', params.slug)
    .eq('status', 'published')
    .single()

  if (!post) notFound()

  return (
    <article className="prose lg:prose-xl mx-auto">
      <h1>{post.title}</h1>
      <time dateTime={post.published_at}>
        {new Date(post.published_at).toLocaleDateString()}
      </time>
      <MDXRemote source={post.content} />
    </article>
  )
}

キャッシュプラグイン、パフォーマンスプラグイン、SEOプラグインはないことに注意してください。メタデータAPIはSEOを処理します。サーバーコンポーネントはパフォーマンスを処理します。CDNはキャッシングを処理します。すべて組み込まれています。

バックエンドとしてSupabaseを設定

Supabaseスキーマは、WordPressの一般的なwp_posts/wp_postmeta構造ではなく、実際のデータニーズの周りに設計する必要があります。よりクリーンなスキーマを示します:

-- Postsテーブル
create table posts (
  id uuid default gen_random_uuid() primary key,
  title text not null,
  slug text unique not null,
  content text,
  excerpt text,
  featured_image text,
  status text default 'draft' check (status in ('draft', 'published', 'archived')),
  author_id uuid references auth.users(id),
  published_at timestamptz,
  created_at timestamptz default now(),
  updated_at timestamptz default now(),
  metadata jsonb default '{}'
);

-- Categories
create table categories (
  id uuid default gen_random_uuid() primary key,
  name text not null,
  slug text unique not null,
  description text
);

-- Row Level Security
alter table posts enable row level security;

create policy "Published posts are viewable by everyone"
  on posts for select
  using (status = 'published');

create policy "Authors can manage their own posts"
  on posts for all
  using (auth.uid() = author_id);

metadata jsonb列はエスケープハッチです。独自の列に値しないカスタムフィールドはそこに生きることができます。インデックス、クエリ可能、無限に柔軟――ACFフィールドのようですが、プラグインなしで。

認証とユーザーデータの処理

WordPressサイトにユーザーアカウントがある場合、Supabase Authは移行をクリーンに処理します。パスワードハッシュを移行することはできません(WordPressはphpass、Supabaseはbcryptを使用します)が、以下ができます:

  1. ユーザーメールとプロファイルをSupabase Authにインポート
  2. 最初のログイン時にすべてのユーザーの「パスワードをリセット」フローをトリガー
  3. またはマジックリンク認証を使用して、パスワードが必要でない

Supabaseは、電子メール/パスワード、Google、GitHub、Apple、および数十の他のOAuthプロバイダーをすぐにサポートしています。プラグインが必要ありません。

SEO保存:築いたものを失わない

これは絶対に譲れません。ボタンの移行は一夜にして何年ものSEO資産を破壊することができます。チェックリストは以下の通りです:

  1. すべての古いURLを新しいURLにマップする。 WordPressはデフォルトで/2024/01/post-title/を使用します。新しいサイトは/blog/post-titleを使用するかもしれません。古いすべてのURLは301リダイレクトが必要です。

  2. Next.jsでリダイレクトを実装:

// next.config.js
module.exports = {
  async redirects() {
    return [
      // 日付ベースのWordPress URLをクリーンスラッグに
      {
        source: '/:year(\\d{4})/:month(\\d{2})/:slug',
        destination: '/blog/:slug',
        permanent: true,
      },
      // カテゴリページ
      {
        source: '/category/:slug',
        destination: '/blog/category/:slug',
        permanent: true,
      },
    ]
  },
}
  1. すべてのメタタイトル、説明、構造化データを保存。 移行前にYoastからエクスポート。
  2. 起動直後にGoogleSearchConsoleに新しいサイトマップを送信。
  3. ローンチ後30日間、サブドメイン(old.yoursite.com)で古いサイトを実行し続けるフォールバックとして。

パフォーマンスベンチマーク:前後比較

Social Animalで行った移行からの実数字(これらは移行プロジェクト全体の平均)を示します:

メトリック WordPress(前) Next.js + Supabase(後) 改善
Lighthouseパフォーマンススコア 38 94 +147%
最大コンテンツフルペイント(LCP) 4.2s 0.9s -79%
最初の入力遅延(FID) 180ms 12ms -93%
累積レイアウトシフト(CLS) 0.25 0.02 -92%
最初のバイトまでの時間(TTFB) 1.8s 0.15s -92%
ページの総重量 3.2MB 420KB -87%
HTTPリクエスト 47 8 -83%

これらは厳選されていません。彼らは一貫しています。30個以上のプラグインを排除し、それぞれが独自のCSSとJSを注入し、動的PHPレンダリングを静的/サーバーレンダリングされたReactコンポーネントに置き換え、グローバルCDN上で、結果は予測可能です。

これらの種類の結果がプロジェクトの外観としてどのようにしているかが気になるなら、プライシングページはヘッドレス移行プロジェクトが通常のコストをどのように内訳しているかについて説明します。

コスト比較:WordPressとヘッドレススタック

WordPress(年間) Next.js + Supabase(年間)
ホスティング $1,200 - $6,000(WP Engine/Kinsta) $0 - $240(VercelPro)
データベース/バックエンド ホスティングに含まれる $0 - $300(SupabasePro)
プラグインライセンス $1,500 - $4,000 $0
セキュリティツール $300 - $500 $0(組み込み)
CDN $0 - $600 $0(Vercelに含まれる)
メンテナンス開発者時間 $6,000 - $18,000 $1,000 - $4,000
合計 $9,000 - $29,100 $1,000 - $4,540

ヘッドレススタックは年間70~85%安価に運用できます。移行自体には明らかに初期費用がある――複雑さに応じて通常は$15,000-$60,000(詳細についてはヘッドレスCMS開発サービスを参照)。しかし、それは運用コストの削減を通じて6~18か月以内に自分自身のために払っていて、パフォーマンスとSEOの改善の収益への影響を考慮する前に。

よくある質問

移行後、コンテンツを管理するためにReact/Next.jsを学ぶ必要がありますか? いいえ。ほとんどのチームは、Sanity、Contentful、またはWordPress自体をヘッドレスCMS(REST APIを使用)として使用するなどのヘッドレスCMS。コンテンツエディタはコードに触れることはありません。彼らはクリーンな編集インターフェイスを取得し、フロントエンドはAPI経由でコンテンツを引き出します。チームが既に知っているWordPressエディタを保つたい場合、絶対にできます――WordPressフロントエンドを削除し、コンテンツバックエンドとして使用するだけです。

一般的なWordPress to Next.js移行はどのくらい時間がかかりますか? コンテンツ重視のサイト(ブログと標準ページ付き):4~6週間。e-commerce、ユーザーアカウント、カスタムポストタイプ、複雑な機能を備えたサイト:8~14週間。最大の変数はコンテンツの複雑さです――大量のショートコード依存コンテンツまたは深くカスタマイズされたGutenbergブロック付きのサイトは、クリーンに移行するのに時間がかかります。

移行中にGoogle検索ランキングを失いますか? リダイレクトを正しく処理すると失わないです。301リダイレクトはリンク資産の90~99%を保存します。通常、移行後最初の1~2週間でわずかな下げ(Googleが再クローリングする必要がある)が続き、その後、より良いCore Web Vitalsスコアのため、改善されたランキングが続きます。キーは、すべての単一のURLをマップし、リダイレクトマップが完了するまで起動しないことです。

Supabaseはトラフィックの多いサイトの本番環境に対応していますか? はい。SupabaseはAWSインフラストラクチャ上で実行され、数百万のリクエストを処理する企業によって本番環境で使用されています。彼らのデータベースはPostgresです――おそらく最も戦闘テストされたデータベース。SupabaseはこれまでSupabaseサーブ100万以上のデータベースと毎日10億以上のAPIリクエストを処理します。追加スケーリングのために、Pro($25/月)とTeam($599/月)計画には専用リソースと優先度サポートが含まれます。

このスタックにWooCommerceを移行できますか? できますが、e-commerceは大きな複雑さを追加します。ほとんどのチームがWooCommerceから移行しているのは、Shopify(Storefront APIを使用してNext.jsフロントエンド)またはMedusa.jsやSaleorのようなオープンソースソリューション。Supabaseは製品カタログと注文管理を処理できますが、チェックアウト、支払い処理、インベントリ管理、税計算自分自身を構築する必要があります。ほとんどの企業では、次.jsに接続して、専用e-commerceバックエンドを使用するのが意味をなします。

WordPressマルチサイトについては、このスタックでそれを置き換えることができますか? 絶対に。Next.jsは、ミドルウェアと動的ルーティングを使用するマルチテナントアーキテクチャの優れたサポートを備えています。SupabaseのRow Level Securityを使用すると、テナント別にデータをパーティション化するのは簡単です。50を超えるサイトを持つWordPressマルチサイトネットワークをテナント固有のルーティングを備えた単一のNext.jsアプリケーションに移行した。運用の単純化は非常に大きかった。

CMSが必要ですか、またはSupabaseを直接使用できますか? Supabaseはテーブルエディタを提供しますが、開発者にとって機能しますが、コンテンツエディタはより磨かれたものを通常望みます。最も一般的なアプローチは:(1)SanityやStoryblokなどの専用ヘッドレスCMSをコンテンツに使用し、アプリケーションデータにはSupabase、(2)Next.js + Supabase Authを使用してシンプルな管理UIを構築、または(3)ヘッドレスCMSバックエンドとしてWordPressを保つ。オプション1は、コンテンツが重いサイトで最も人気があります。オプションを探索している場合は、Astro開発およびヘッドレスCMSページで、トレードオフをブレークダウンします。

移行が間違った場合、WordPressにロールバックできますか? はい、あなたがそのための計画を立てるべき。移行プロセス全体を通してWordPressサイトをサブドメインで実行し続けます。DNS-levelスイッチングを使用できます(AレコードまたはCNAMEを変更する)ので、数分間にロールバックできます。起動後少なくとも30日間、古いWordPressインスタンスを実行し続けることをお勧めします。すべてのリダイレクトが機能し、検索ランキングが安定し、すべての機能が検証されてからのみ、古いスタックを廃止してください。移行が適切なロールバック手順で計画されていて、私たちのチームに連絡したい場合――そんなことが起こる危険はどこにあるかを知るには十分なほど多くをしてきました。