零停機 CMS 遷移:WordPress 到 Next.js 轉換指南
我已在超過一打 WordPress 網站上進行過 Next.js 生產遷移,我要告訴你一件可能會讓你驚訝的事:實際的程式碼遷移並不是難點。難點是切換。那個令人恐懼的時刻,當你翻動開關,祈禱一切不會出問題。好消息?有了正確的方案,你可以讓那一刻變得無聊。在運維中,無聊正是你想要的。
這是我們在 Social Animal 用於生產切換的方案。這不是理論上的——它來自於真實的遷移,其中真實的收入處於危險中。我們說的是日收入 $50K 的電商網站、月度瀏覽量達數百萬的內容發布商,以及 SaaS 行銷網站,其中 5 分鐘的停機時間意味著 CEO 會打你的手機。
目錄
- 為什麼零停機時間比你想的更重要
- 遷移架構概述
- 第 1 階段:內容遷移和 API 層
- 第 2 階段:建構 Next.js 應用程式
- 第 3 階段:平行部署設定
- 第 4 階段:藍綠部署設定
- 第 5 階段:DNS 切換策略
- 第 6 階段:切換檢查清單
- 第 7 階段:切換後監控和回滾
- 常見失敗模式及其預防方法
- 常見問題

為什麼零停機時間比你想的更重要
讓我們用數字說話。Google 2024 年的研究表明,頁面加載延遲 1 秒大約會導致轉換率下降 7%。現在想像你的網站根本……不存在。即使只有 5 分鐘。
以下是真正的風險:
| 停機時長 | 收入影響(對於日均 $10K 的網站) | SEO 影響 | 用戶信任影響 |
|---|---|---|---|
| 5 分鐘 | 約 $35 損失 | 最小(如果是孤立事件) | 低 |
| 30 分鐘 | 約 $208 損失 | Googlebot 可能會注意到 | 中等 |
| 2 小時 | 約 $833 損失 | GSC 中的爬蟲錯誤 | 高 |
| 24 小時 | $10,000+ 損失 | 去索引風險 | 嚴重 |
但這不僅僅是收入問題。搜尋引擎在不斷爬取。如果 Googlebot 在遷移期間訪問你的網站並收到 500 錯誤,這些 URL 可能在幾小時內從索引中丟失。我見過有網站因為有人在午餐時間做了一次「快速遷移」而失去了 40% 的自然流量。
目標不僅僅是零停機時間。而是在過渡期間對用戶和爬蟲零可見變化。
遷移架構概述
在進入各個階段之前,讓我們看看我們要達到的架構。基本的模式是同時運行兩個系統,然後以原子方式切換流量。
┌─────────────────┐
│ Cloudflare / │
│ Load Balancer │
└────────┬────────┘
│
┌────────┴────────┐
│ Traffic Router │
│ (weight-based) │
└────┬───────┬────┘
│ │
┌──────────┴──┐ ┌──┴──────────┐
│ WordPress │ │ Next.js │
│ (Blue) │ │ (Green) │
│ Origin │ │ on Vercel │
└──────────┬──┘ └──┬──────────┘
│ │
┌──────────┴──┐ ┌──┴──────────┐
│ MySQL DB │ │ Headless CMS │
│ │ │ (Sanity/etc) │
└─────────────┘ └─────────────┘
關鍵見解:你不僅僅是在遷移前端。你是在遷移內容層、渲染層和傳遞層——每一層都可以獨立遷移。
第 1 階段:內容遷移和 API 層
這是大多數團隊開始出錯的地方。他們試圖先構建 Next.js 前端,然後再弄清楚內容。不要這樣做。從內容開始。
選擇你的無頭 CMS
你的 WordPress 內容需要一個新家。選擇對遷移複雜性影響很大:
| CMS | 從 WP 遷移的難度 | 實時同步可能性 | 定價(2025) | 最適合 |
|---|---|---|---|---|
| Sanity | 高(結構化內容映射良好) | 可以,通過 webhook | 免費層,然後 $99/月 | 複雜的內容模型 |
| Contentful | 中等(需要欄位映射) | 可以 | $300/月(Team) | 企業團隊 |
| Strapi | 高(與資料庫支持的模型相似) | 可以 | 自託管免費,雲端從 $29/月開始 | 完全控制 |
| WordPress REST API | 不適用(保持為無頭) | 已同步 | 現有託管成本 | 快速勝利 |
| Payload CMS | 高 | 可以 | 自託管免費 | 開發者優先的團隊 |
我們在無頭 CMS 開發功能頁面中深入介紹了無頭 CMS 選擇,但簡短版本是:對於大多數 WordPress 遷移,Sanity 或 Payload CMS 提供最佳遷移路徑。
設定內容同步
這是關鍵部分:在平行運行階段,內容需要存在於兩個系統中。你有兩種策略:
策略 A:一次性遷移 + 凍結 將所有內容遷移到新 CMS,然後凍結 WordPress 編輯。這適用於小型網站,但當編輯人員需要繼續發佈時就會崩潰。
策略 B:持續同步(推薦) 設定一個同步管線,將 WordPress 變更近即時地複製到新 CMS。
// 示例:WordPress webhook 處理器,同步到 Sanity
// 這作為無伺服器函數運行(Vercel/AWS Lambda)
import { createClient } from '@sanity/client';
const sanity = createClient({
projectId: process.env.SANITY_PROJECT_ID,
dataset: 'production',
token: process.env.SANITY_WRITE_TOKEN,
apiVersion: '2025-01-01',
useCdn: false,
});
export async function POST(request) {
const payload = await request.json();
const { post_id, post_title, post_content, post_status } = payload;
if (post_status !== 'publish') return new Response('Skipped', { status: 200 });
try {
await sanity.createOrReplace({
_id: `wp-${post_id}`,
_type: 'post',
title: post_title,
body: convertGutenbergToPortableText(post_content),
migratedFrom: 'wordpress',
wpId: post_id,
_updatedAt: new Date().toISOString(),
});
return new Response('Synced', { status: 200 });
} catch (error) {
console.error('Sync failed:', error);
return new Response('Sync error', { status: 500 });
}
}
你還需要 WordPress 端。我們使用一個在 save_post 上觸發的簡單外掛:
// wp-content/plugins/headless-sync/headless-sync.php
add_action('save_post', function($post_id, $post) {
if (wp_is_post_revision($post_id)) return;
wp_remote_post(SYNC_ENDPOINT_URL, [
'body' => json_encode([
'post_id' => $post_id,
'post_title' => $post->post_title,
'post_content' => $post->post_content,
'post_status' => $post->post_status,
]),
'headers' => [
'Content-Type' => 'application/json',
'X-Sync-Secret' => SYNC_SECRET,
],
]);
}, 10, 2);
在切換前至少運行此同步 2 週。你想要捕捉邊界情況——奇怪的短代碼、自訂貼文類型、不能很好映射的 ACF 欄位。

第 2 階段:建構 Next.js 應用程式
我不會在這裡介紹完整的 Next.js 構建過程——那值得單獨一篇文章(我們在Next.js 開發中有深厚的專業知識)。但有些對零停機時間遷移很重要的特定遷移考量。
URL 對等性是不可協商的
你的 WordPress 網站上存在的每個 URL 在你的 Next.js 網站上都必須解析為相同的內容。每一個。
這意味著:
/blog/my-post-slug/必須有效(包括尾部斜杠,如果 WordPress 使用了的話)/category/technology/必須有效或重定向/wp-content/uploads/2024/03/image.jpg必須重定向到你的新圖像 CDN/feed/仍必須返回有效的 RSS/Atom- 分頁 URL 如
/blog/page/2/必須有效
早期構建一個 URL 審計指令碼:
# 使用 WP-CLI 匯出所有 WordPress URL
wp post list --post_type=post,page --post_status=publish \
--fields=ID,post_name,post_type,guid --format=csv > urls.csv
# 還要從任何 SEO 外掛獲取重定向
wp db query "SELECT * FROM wp_redirection_items" --format=csv > redirects.csv
然後驗證它們是否對應你的 Next.js 構建:
// validate-urls.mjs
import { readFileSync } from 'fs';
import { parse } from 'csv-parse/sync';
const urls = parse(readFileSync('./urls.csv'), { columns: true });
const NEXT_BASE = 'https://staging.yoursite.com';
for (const row of urls) {
const res = await fetch(`${NEXT_BASE}/${row.post_name}/`, {
redirect: 'manual'
});
if (res.status >= 400) {
console.error(`BROKEN: /${row.post_name}/ → ${res.status}`);
}
}
效能基線
在切換之前,你的 Next.js 網站至少需要與 WordPress 一樣快。這聽起來很明顯,但我見過團隊對新堆棧如此興奮以至於忘記了基準測試。
使用 CrUX 資料或 Lighthouse CI 從 WordPress 網站捕捉核心網頁生命週期指標,然後達到或超過它們。如果你的 WordPress 網站的 LCP 為 2.1 秒,而你的 Next.js 網站為 3.4 秒,你還沒有準備好。
第 3 階段:平行部署設定
現在我們來看實現零停機時間的基礎設施。
平行運行
兩個系統同時運行,提供真實流量。但在任何給定時間,只有一個是「主要」的。
對於 Vercel 上的 Next.js(我們最常見的設定),你將 Next.js 應用程式部署到自訂網域如 next.yoursite.com 或使用 Vercel 的預覽 URL。你的 WordPress 網站繼續在 yoursite.com 上運行。
# 如果你使用 Nginx 作為反向代理
# 這是平行運行配置
upstream wordpress {
server wordpress-origin.internal:80;
}
upstream nextjs {
server next-yoursite.vercel.app:443;
}
server {
listen 443 ssl;
server_name yoursite.com;
# 在平行運行期間:鏡像流量到兩者,
# 但只從 WordPress 服務回應
location / {
mirror /mirror;
proxy_pass http://wordpress;
}
location = /mirror {
internal;
proxy_pass https://nextjs$request_uri;
}
}
這個鏡像配置將每個請求發送到兩個後端,但只返回 WordPress 回應。你獲得真實流量擊中你的 Next.js 應用,而無需用戶看到它。檢查你的 Next.js 日誌中的錯誤、404 和緩慢回應。
合成監控
設定持續驗證兩個系統返回等效內容的監控:
// canary-check.mjs — 每 5 分鐘通過 cron 運行
const CRITICAL_URLS = [
'/',
'/blog/',
'/about/',
'/contact/',
'/blog/most-popular-post/',
];
for (const path of CRITICAL_URLS) {
const [wpRes, nextRes] = await Promise.all([
fetch(`https://yoursite.com${path}`),
fetch(`https://next.yoursite.com${path}`),
]);
if (wpRes.status !== nextRes.status) {
alert(`Status mismatch on ${path}: WP=${wpRes.status} Next=${nextRes.status}`);
}
// 比較標題標籤作為內容對等性檢查
const wpTitle = (await wpRes.text()).match(/<title>(.*?)<\/title>/)?.[1];
const nextTitle = (await nextRes.text()).match(/<title>(.*?)<\/title>/)?.[1];
if (wpTitle !== nextTitle) {
alert(`Title mismatch on ${path}`);
}
}
至少運行一週,沒有任何警告,然後再進行切換。
第 4 階段:藍綠部署設定
藍綠部署意味著有兩個相同的生產環境——藍色(目前 WordPress)和綠色(新 Next.js)——並在它們之間以原子方式切換。
使用 Cloudflare Workers 進行流量路由
這是我們首選的方法,因為它提供即時的全球流量切換,沒有 DNS 傳播延遲。
// 用於藍綠路由的 Cloudflare Worker
export default {
async fetch(request, env) {
const url = new URL(request.url);
// 從 KV 存儲讀取活動環境
const activeEnv = await env.CONFIG.get('active_environment') || 'blue';
// 可選:基於百分比的金絲雀路由
const canaryPercent = parseInt(await env.CONFIG.get('canary_percent') || '0');
const useGreen = activeEnv === 'green' ||
(canaryPercent > 0 && Math.random() * 100 < canaryPercent);
const origin = useGreen
? 'https://next-yoursite.vercel.app'
: 'https://wp-origin.yoursite.com';
const originUrl = new URL(url.pathname + url.search, origin);
const response = await fetch(originUrl, {
method: request.method,
headers: {
...Object.fromEntries(request.headers),
'Host': new URL(origin).hostname,
'X-Forwarded-Host': url.hostname,
},
body: request.method !== 'GET' ? request.body : undefined,
});
const newResponse = new Response(response.body, response);
newResponse.headers.set('X-Served-By', useGreen ? 'green-nextjs' : 'blue-wordpress');
return newResponse;
}
};
這個方法的好處:從 WordPress 切換到 Next.js 是一次 KV 存儲寫入。沒有 DNS 更改。沒有傳播。即時的。
# 切換到綠色(Next.js)
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/values/active_environment" \
-H "Authorization: Bearer ${CF_TOKEN}" \
--data "green"
# 如果出錯,回滾到藍色(WordPress)
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/values/active_environment" \
-H "Authorization: Bearer ${CF_TOKEN}" \
--data "blue"
金絲雀路由
不要一次性翻轉 100% 的流量。從金絲雀開始:
- 1% 流量到 Next.js — 監控 1 小時尋找錯誤
- 10% 流量 — 監控 2 小時
- 50% 流量 — 監控 4 小時
- 100% 流量 — 監控 24 小時
- 停用 WordPress — 在 100% 運行 1 週後
第 5 階段:DNS 切換策略
如果你無法使用 Cloudflare Workers(或類似的邊緣路由解決方案),你需要在 DNS 層級處理切換。這更棘手,因為 TTL 傳播。
切換前 DNS 準備
在切換前至少 48 小時:
- 將你的 DNS TTL 降低到 60 秒(從典型的 3600 或 86400)
- 等待舊 TTL 過期
- 驗證低 TTL 是否有效:
dig yoursite.com +short應顯示 TTL ~60
# 檢查目前 TTL
dig yoursite.com A +noall +answer
# 應顯示類似:
# yoursite.com. 60 IN A 76.76.21.21
DNS 切換
使用 60 秒 TTL,更新你的 DNS A/CNAME 記錄意味著全球傳播約 5-10 分鐘(一些解析器忽略低 TTL,但大多數在 2025 年尊重它們)。
# 如果移動到 Vercel
# 更新 CNAME 指向 cname.vercel-dns.com
# 或更新 A 記錄到 Vercel 的 IP 地址:76.76.21.21
陷阱:SSL 憑證計時
有一件事會讓人煩惱。當你將 DNS 切換到 Vercel(或任何新主機)時,你的域名的 SSL 憑證需要在切換之前在新主機上配置。否則,你會有一個 HTTPS 無法工作的窗口。
在 Vercel 上,在 DNS 切換之前在專案設定中添加你的自訂網域。Vercel 將嘗試通過 HTTP-01 或 DNS-01 質詢來配置證書。如果你使用 Cloudflare 代理的 DNS,你可能需要暫時禁用代理(橙色雲 → 灰色雲)以使證書配置工作。
第 6 階段:切換檢查清單
這是我們在切換日使用的檢查清單。列印出來。檢查每一項。
切換前(T-24 小時)
- 所有內容已同步並在新 CMS 中驗證
- URL 對等性驗證通過 100%
- SSL 憑證在新主機上已配置
- DNS TTL 確認在 60 秒
- 回滾程序已記錄並測試
- 來自 WordPress SEO 外掛的所有重定向已遷移到
next.config.js或邊緣中介軟體 -
robots.txt和sitemap.xml在 Next.js 上正確生成 - 分析追蹤在 Next.js 上驗證(GA4、GTM 等)
- 表單提交經過端到端測試
- RSS 源已驗證
- 關鍵頁面上的 OpenGraph 標籤已驗證
- 404 頁面已測試
切換(T-0)
- 通知利益相關者:"遷移開始"
- 設定金絲雀為 1% → 監控 30 分鐘
- 增加到 10% → 監控 1 小時
- 檢查 Google Search Console 以查看爬蟲錯誤
- 增加到 50% → 監控 2 小時
- 增加到 100%
- 向 Google Search Console 提交更新的網站地圖
- 驗證 Googlebot 可以訪問所有關鍵頁面(使用 URL 檢查工具)
- 測試所有表單、電子商務流程、身份驗證流程
切換後(T+24 小時)
- 在 CrUX 儀表板中監控核心網頁生命週期指標
- 檢查 Google Search Console 以查看覆蓋範圍問題
- 驗證所有分析資料正確流動
- WordPress 源仍在運行(保留至少 2 週)
- 針對新網站使用 Screaming Frog 運行完整網站爬取
第 7 階段:切換後監控和回滾
監控什麼
在你的監控工具中設定儀表板(我們使用 Vercel Analytics、Datadog 和 Google Search Console 的組合)追蹤:
- 錯誤率:有 5xx 回應嗎?4xx 有增加嗎?
- 回應時間:P50、P95、P99 延遲
- 核心網頁生命週期指標:LCP、FID/INP、CLS
- Search Console:爬蟲統計、覆蓋範圍報告、索引狀態
- 業務指標:轉換率、反彈率、每個工作階段的頁面數
回滾計劃
你的回滾需要是一個命令。不是 15 步的程序。一個命令。
使用 Cloudflare Worker 方法:
# 一個命令回滾
wrangler kv:key put --namespace-id=$NS_ID "active_environment" "blue"
使用基於 DNS 的切換:
# 通過 Cloudflare API 預編寫的 DNS 回滾
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{record_id}" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"content": "old-wordpress-ip-address"}'
在切換後至少保持 WordPress 運行 2 週。不要做英雄。你停止 WordPress 的時刻就是你發現忘記遷移的頁面的時刻。
常見失敗模式及其預防方法
在做過幾十次這種事情後,以下是我最常看到的失敗:
1. 忘記的 WordPress 外掛,帶有前端路由
那個聯絡表單外掛創建 /wp-json/contact-form-7/ 端點。那個 WooCommerce 安裝有 /my-account/ 和 /cart/。映射每個外掛的 URL 足跡。
2. 內容中的硬編碼 wp-content URL
你的內容中的圖像引用 /wp-content/uploads/。你需要重定向或重寫規則指向你的新資產 CDN。
// next.config.js
module.exports = {
async redirects() {
return [
{
source: '/wp-content/uploads/:path*',
destination: 'https://cdn.yoursite.com/uploads/:path*',
permanent: true,
},
];
},
};
3. 忘記 XML 網站地圖
Google Search Console 指向 /sitemap.xml。你的 Next.js 應用需要生成一個。使用 next-sitemap 或在你的應用的路由處理器中構建它。
4. 身份驗證和工作階段問題 如果你的 WordPress 網站有已登入的使用者,他們的 Cookie 在新堆棧上將無法工作。分別計劃用戶遷移。
5. 過渡期間的 CDN 快取中毒 如果 Cloudflare 正在快取回應,在切換到 Next.js 後你可能服務舊的 WordPress 頁面。在切換後立即清空 CDN 快取。
# 切換後清空整個 Cloudflare 快取
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"purge_everything": true}'
如果你正在計劃遷移並想讓專家處理繁重工作,請查看我們的定價頁面或直接聯絡我們。我們已經做過足夠多次,我們的方案在所有正確的地方都經過了實戰檢驗。
對於使用其他框架構建的網站,我們也進行基於 Astro 的遷移,對於不需要太多互動性的內容密集型網站來說可能更快。
常見問題
WordPress 到 Next.js 遷移通常需要多長時間? 對於中等複雜程度的網站(100-500 頁、自訂貼文類型、一些動態功能),計劃端到端 8-12 週。內容遷移和平行運行階段單獨需要 3-4 週。實際切換,如果你做過準備工作,需要大約 4 小時的主動工作。不要讓任何人告訴你這可以在一個週末內完成。
我能否使用 WordPress 作為無頭 CMS 而不是遷移內容? 絕對可以,對於某些團隊來說這是正確的選擇。你保持 WordPress 作為你的 CMS,使用 REST API 或 WPGraphQL 將內容提供給 Next.js,並只遷移前端。這大大縮短了遷移時間,因為你完全跳過了內容遷移階段。權衡是你仍然維護著一個 WordPress 安裝及其所有的更新和安全開銷。
遷移期間我的 SEO 排名會發生什麼? 進行適當的零停機時間遷移,你的排名應該保持穩定。Google 的 John Mueller 已確認更改你的 CMS 不應影響排名,如果內容、URL 和技術 SEO 元素保持等效。最大的風險是損壞的 URL(導致 404)、更改的內部連結結構和降低的頁面速度。我們的方案特別涵蓋了全部三個。
我如何在 Next.js 中處理 WordPress 表單? 你有幾個選項:使用表單服務如 Formspree 或 Basin,在 Next.js 中構建直接處理提交的 API 路由,或使用你的無頭 CMS 的表單功能(Sanity 沒有本機表單,但 Payload CMS 有)。對於具有條件邏輯的複雜表單,我們通常構建自訂 API 路由並在前端使用 React Hook Form。
我應該為 Next.js 部署使用 Vercel、Netlify 還是自託管?
對於大多數團隊,Vercel 是 Next.js 最簡單的方法。它由同一個團隊構建,ISR、中介軟體和圖像優化等功能在那裡效果最好。Vercel Pro 計劃每個使用者每月 $20,涵蓋大多數生產需求。如果你有特定的合規要求或需要保持在 AWS 上,你可以使用 standalone 輸出模式自託管。Netlify 也可以工作,但在歷史上在 Next.js 功能支持方面一直滯後。
藍綠部署和金絲雀部署之間的區別是什麼? 藍綠是二進制的:所有流量都進入舊系統(藍色)或新系統(綠色)之一。金絲雀部署逐漸將流量百分比從舊系統轉移到新系統。實際上,我們將兩者結合起來。我們設定藍綠基礎設施(兩個完整環境),但在實際切換期間使用金絲雀式基於百分比的路由。這提供了逐步推出的安全性以及只管理兩個環境的簡單性。
我如何將 WordPress 重定向遷移到 Next.js?
從你使用的任何 WordPress 外掛(Redirection、Yoast、RankMath)匯出你的重定向。將它們轉換為 next.config.js 中的 Next.js 重定向格式。對於有數百個重定向的網站,改用中介軟體——它更高效,可以處理模式匹配。注意 next.config.js 重定向在 Vercel 上有實際限制,大約 1,000 個條目之前構建時間會出現問題。
如果切換後出了問題,我能否回滾到 WordPress? 可以,這是不可協商的。在切換後至少保持你的 WordPress 實例運行 2 週。使用本文描述的 Cloudflare Worker 方法,回滾是一個 API 呼叫,在全球範圍內秒內生效。使用基於 DNS 的切換,回滾需要 1-10 分鐘,取決於 TTL 傳播。在確信新系統穩定之前,永遠不要停用舊系統。