我已經數不清有多少次聽到這句話了:「WordPress 在我們剛開始時還不錯,但現在……」然後就來了一大堆問題。網站載入需要 6 秒。聯絡表單外掛在最後一次更新後壞掉了。有一個外掛存在重大漏洞,自 2022 年以來就沒有人維護了。建立原始主題的開發者已經消失無蹤。這聽起來很熟悉嗎?

話說回來——WordPress 驅動了超過 40% 的網路,而且是有原因的。它易於上手,有著龐大的生態系統,而且讓許多企業快速上線。但是,啟動你的工具和與你一起成長的工具之間是有區別的。如果你在讀這篇文章,你可能已經撞到了那道牆。讓我帶你了解從 WordPress 遷移到無頭式 Next.js + Supabase 架構的真實樣貌——不是行銷版本,而是實際的工程手冊。

目錄

超越了 WordPress?Next.js + Supabase 遷移手冊

你真的已經超越了 WordPress 的跡象

不是每個人都需要離開 WordPress。我想預先表明這一點。如果你正在運行個人部落格或為本地企業運營宣傳網站,WordPress 搭配一個得體的主題和少量外掛可能仍然是正確的選擇。但有一些明確的信號表明你已經超越了它:

外掛衝突每月都在破壞事物

你更新 WooCommerce,頁面構建器就會壞掉。你更新頁面構建器,SEO 外掛就會發出警告。你將 PHP 更新到 8.2,因為你的主機要求這樣做,三個外掛就完全停止工作了。這不是 bug——這是架構本身。WordPress 外掛都共享同一個全域作用域、同樣的鉤子、同樣的資料庫。每個外掛都是與其他每個外掛的潛在衝突來源。

我審計過運行 30、40 甚至 60 多個活躍外掛的 WordPress 網站。在那一點上,你不是在維護一個網站。你是在維護一個「疊疊樂」遊戲。

效能已經成為全職工作

你的 PageSpeed 分數在 30 分以下。你已經安裝了快取外掛、影像最佳化外掛、縮小化外掛,以及 CDN 外掛——所有這些都是為了修復由其他 25 個外掛造成的效能問題。諷刺意味十足。

WordPress 在每次請求上都會動態生成頁面(除非快取)。每個外掛都可以注入自己的 CSS 和 JavaScript 檔案。一個典型的 WordPress 頁面搭配熱門外掛會載入 15-30 個單獨的渲染阻擋資源。Google 的 2024 Core Web Vitals 資料顯示,WordPress 網站在所有三個 CWV 指標上的通過率為 33%,而使用現代 JavaScript 框架構建的網站為 52%。

安全漏洞讓你夜不能寐

WPScan 的 2024 漏洞資料庫追蹤了超過 7,000 個新 WordPress 漏洞——絕大多數在外掛和主題中。如果你運行的網站處理使用者資料、付款或任何敏感資訊,每個外掛都是一個攻擊面。Patchstack 報告指出,2024 年 97% 的 WordPress 安全漏洞來自外掛。

你實質上是在信任數十個獨立開發者——其中許多人將外掛維護作為副業——來保護你的安全態勢。

你的開發團隊討厭在上面工作

這一點被低估了。優秀的開發者不再想在 WordPress 上工作。PHP 範本意大利麵加 ACF 欄位的工作流與現代元件型開發相比很痛苦。如果你想吸引和留住工程人才,你的技術堆棧是有重要的。

WordPress 稅:外掛地獄的真實成本

讓我為此列出一些數字。對於中等規模的 WordPress 網站(比如電子商務網站或 SaaS 行銷網站,包括部落格、使用者帳戶和自訂功能),以下是「WordPress 稅」通常看起來像什麼(年度):

成本類別 年度估計
進階外掛授權(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。

但對於 2025 年從 WordPress 遷移的團隊來說,Next.js + Supabase 在一個很難被打敗的甜蜜點上達到平衡。以下是原因。

Next.js:完成所有工作的前端

Next.js 15(2024 年 10 月起穩定)預設給你伺服器元件,這意味著你同時獲得靜態網站的效能和動態網站的靈活性。你可以在構建時靜態生成你的部落格文章,伺服器渲染你的動態頁面,並用戶端渲染互動元件——所有這些都在同一個應用中進行。

對於來自 WordPress 的團隊,關鍵好處是:

  • 內建影像最佳化——替代 2-3 個 WordPress 外掛
  • 自動程式碼分割——每個頁面只載入它需要的 JS
  • 邊緣中間件——在 CDN 層級處理重新導向、身份驗證和 A/B 測試
  • 增量靜態再生(ISR)——無需完整部署就能重新構建各個頁面
  • 使用 React 伺服器元件的應用路由器——大幅減少用戶端 JavaScript

我們在 Social Animal 構建了很多 Next.js 專案(請查看我們的 Next.js 開發能力),與 WordPress 相比的效能差異始終很大。

Supabase:WordPress 希望擁有的後端

Supabase 是基於 PostgreSQL 的開源 Firebase 替代品。它提供:

  • 一個完整的 Postgres 資料庫,可自動生成 REST 和 GraphQL API
  • 內建身份驗證(電子郵件、OAuth、魔法連結、SSO)
  • 用於細粒度存取控制的行級安全原則
  • 透過 WebSocket 的即時訂閱
  • 用於無伺服器後端邏輯的邊緣函式
  • 用於檔案上傳和 CDN 傳送的儲存空間

特別是對於 WordPress 遷移,Supabase 很出色,因為 WordPress 使用 MySQL,而你的資料模型驚人地對應到 PostgreSQL。自訂文章類型變成表。文章中繼資料變成 JSONB 列。使用者資料幾乎 1:1 對應。

Supabase 的免費層包括 500MB 資料庫、1GB 儲存空間和 50,000 個每月活躍使用者(在身份驗證上)。他們的 Pro 方案每月 $25,涵蓋大多數生產網站。相比之下,你僅為託管 WordPress 主機就已經花費 $30-$100/月。

超越了 WordPress?Next.js + Supabase 遷移手冊 - 架構

遷移手冊:分階段進行

這是我在進行了數十次 WordPress 遷移後所改進的方法。這不是一個週末專案——根據網站複雜性預算 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 邊緣函式
WooCommerce Saleor、Medusa.js 或 Shopify Storefront API
ACF / 自訂欄位 使用類型化架構的 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 資料

不要使用內建的 WordPress XML 匯出。它不完整且結構不佳。相反,使用 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 將 WordPress HTML 轉換為 MDX
  // 處理短代碼、嵌入和 Gutenberg 區塊
  // 這是遷移複雜性的 80% 所在
}

convertWpContentToMdx 函式是你會花費最多時間的地方。WordPress 內容是 HTML、短代碼、Gutenberg 區塊註解和嵌入式 oEmbed URL 的雜亂混合。像 turndown 這樣的函式庫處理基本的 HTML 到 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 應用路由器的典型部落格文章頁面:

// 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 結構。以下是更簡潔的架構:

-- 文章表
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 '{}'
);

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

-- 行級安全
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
  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 到乾淨的 slug
      {
        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. 在啟動後立即將新網站地圖提交到 Google 搜尋控制台。
  3. 在子域名(old.yoursite.com)上保持舊網站執行 30 天作為後備方案。

效能基準:前後對比

以下是我們在 Social Animal 所做遷移中的真實數字(這些是 2024-2025 年進行的 12 個遷移專案的平均值):

指標 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 渲染替換為全球 CDN 上的靜態/伺服器渲染 React 元件時,結果是可預測的。

如果你對這些類型的結果對你的專案可能是什麼樣子感到好奇,我們的定價頁面詳細介紹了無頭式遷移專案的典型成本。

成本比較:WordPress vs 無頭式堆棧

WordPress(年度) Next.js + Supabase(年度)
主機 $1,200 - $6,000(WP Engine/Kinsta) $0 - $240(Vercel Pro)
資料庫/後端 包含在主機中 $0 - $300(Supabase Pro)
外掛授權 $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 來在遷移後管理我的內容嗎? 不需要。大多數團隊將 Next.js 與無頭式 CMS(如 Sanity、Contentful,甚至 WordPress 本身用作純無頭式 CMS,透過其 REST API)配對。內容編輯器從不接觸程式碼。他們獲得乾淨的編輯介面,前端透過 API 拉取內容。如果你想保留你的團隊已經知道的 WordPress 編輯器,你完全可以——只需移除 WordPress 前端並將其用作內容後端。

典型的 WordPress 到 Next.js 遷移需要多長時間? 對於以內容為中心的網站,有部落格和標準頁面:4-6 週。對於有電子商務、使用者帳戶、自訂文章類型和複雜功能的網站:8-14 週。最大的變數是內容複雜性——高度依賴短代碼的網站或深度自訂 Gutenberg 區塊的網站需要更長時間來乾淨遷移。

在遷移期間我會失去我的 Google 排名嗎? 不會,如果你正確處理重新導向的話。301 重新導向保留約 90-99% 的連結權益。我們通常在遷移後的前 1-2 週看到排名略有下降(Google 需要重新抓取),然後是排名改進,由於更好的 Core Web Vitals 分數。關鍵是對應每個單一 URL,並且不要在你的重新導向對應完成前啟動。

Supabase 對於高流量網站的生產準備好了嗎? 是的。Supabase 在 AWS 基礎設施上運行,已被處理數百萬請求的公司在生產中使用。他們的資料庫就是 PostgreSQL——可能是存在最久經考驗的資料庫。截至 2025 年,Supabase 為超過 100 萬個資料庫服務,每天處理數十億個 API 請求。對於額外的擴展,他們的 Pro($25/月)和 Team($599/月)方案包括專用資源和優先支援。

我可以將 WooCommerce 遷移到此堆棧嗎? 你可以,但電子商務增加了重大複雜性。大多數從 WooCommerce 遷移的團隊轉向 Shopify(使用 Storefront API 與 Next.js 前端)或開源解決方案,如 Medusa.js 或 Saleor。Supabase 可以處理產品目錄和訂單管理,但你需要自己構建結帳、付款處理、庫存管理和稅計算。對於大多數企業,使用專用電子商務後端並將其連接到 Next.js 更有意義。

WordPress 多網站怎麼樣——此堆棧可以替代它嗎? 可以。Next.js 對於使用中間件和動態路由的多租戶架構有很好的支援。Supabase 的行級安全使得按租戶分割資料變得簡單。我們已將擁有 50 多個網站的 WordPress 多網站網路遷移到單個 Next.js 應用,具有特定於租戶的路由,運營簡化是巨大的。

我還需要 CMS,還是我可以直接使用 Supabase? Supabase 給你一個對開發者有效的表編輯器,但內容編輯器通常想要更精緻的東西。最常見的方法是:(1) 為內容使用專用無頭式 CMS(如 Sanity 或 Storyblok),為應用資料使用 Supabase,(2) 使用 Next.js + Supabase Auth 之類的工具構建簡單的管理 UI,或 (3) 保留 WordPress 作為無頭式 CMS 後端。對於內容豐富的網站,選項 1 最受歡迎。如果你在探索選項,我們在 Astro 開發無頭式 CMS 頁面上細分權衡。

如果遷移出錯怎麼辦——我可以回滾到 WordPress 嗎? 可以,你應該為此進行規劃。在整個遷移過程中在子域名上保持 WordPress 網站執行。使用 DNS 級別切換(更改你的 A 記錄或 CNAME),以便你可以在幾分鐘內回滾。我們建議在啟動後至少保留舊 WordPress 例項執行 30 天。只有在確認所有重新導向有效、搜尋排名穩定且所有功能都已驗證後才停用它。如果你想幫助規劃帶有適當回滾程序的遷移,聯絡我們的團隊——我們已經進行過足夠多次,知道陷阱在哪裡。