已經超越 WordPress?Next.js + Supabase 遷移手冊
我已經數不清有多少次聽到這句話了:「WordPress 在我們剛開始時還不錯,但現在……」然後就來了一大堆問題。網站載入需要 6 秒。聯絡表單外掛在最後一次更新後壞掉了。有一個外掛存在重大漏洞,自 2022 年以來就沒有人維護了。建立原始主題的開發者已經消失無蹤。這聽起來很熟悉嗎?
話說回來——WordPress 驅動了超過 40% 的網路,而且是有原因的。它易於上手,有著龐大的生態系統,而且讓許多企業快速上線。但是,啟動你的工具和與你一起成長的工具之間是有區別的。如果你在讀這篇文章,你可能已經撞到了那道牆。讓我帶你了解從 WordPress 遷移到無頭式 Next.js + Supabase 架構的真實樣貌——不是行銷版本,而是實際的工程手冊。
目錄
- 你真的已經超越了 WordPress 的跡象
- WordPress 稅:外掛地獄的真實成本
- 為什麼 Next.js + Supabase 是合理的技術堆棧
- 遷移手冊:分階段進行
- 資料遷移:從 WordPress 取出你的內容
- 使用 Next.js 構建新的前端
- 將 Supabase 設定為你的後端
- 處理身份驗證和使用者資料
- SEO 保護:不要失去你已經建立的
- 效能基準:前後對比
- 成本比較:WordPress vs 無頭式堆棧
- 常見問題

你真的已經超越了 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 遷移後所改進的方法。這不是一個週末專案——根據網站複雜性預算 4-12 週——但如果你遵循各個階段,它是可預測且風險低的。
第 1 階段:審計和架構(第 1 週)
在你寫一行程式碼之前:
- 匯出完整外掛列表,使用
wp plugin list --status=active(WP-CLI) - 將每個外掛對應到新堆棧中的替代品
- 匯出你的完整 URL 結構,包括所有文章、頁面、分類和自訂文章類型
- 記錄所有表單、整合和第三方連接
- 識別存在於你主題
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),但你可以:
- 將使用者電子郵件和個人檔案匯入到 Supabase
- 在首次登入時為所有使用者觸發「重設密碼」流程
- 或使用魔法連結身份驗證,這樣就不需要密碼
Supabase 開箱支援電子郵件/密碼、Google、GitHub、Apple 和數十個其他 OAuth 提供商。不需要外掛。
SEO 保護:不要失去你已經建立的
這是不能協商的。遷移失敗可能會在一夜之間摧毀多年的 SEO 權益。以下是檢查清單:
將每個舊 URL 對應到新 URL。 WordPress 預設使用
/2024/01/post-title/。你的新網站可能使用/blog/post-title。每個舊 URL 都需要 301 重新導向。在 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,
},
]
},
}
- 保護所有中繼標題、描述和結構化資料。 在遷移前從 Yoast 匯出它們。
- 在啟動後立即將新網站地圖提交到 Google 搜尋控制台。
- 在子域名(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 天。只有在確認所有重新導向有效、搜尋排名穩定且所有功能都已驗證後才停用它。如果你想幫助規劃帶有適當回滾程序的遷移,聯絡我們的團隊——我們已經進行過足夠多次,知道陷阱在哪裡。