零停機 CMS 遷移:WordPress → Next.js 切換遊戲手冊
你的生產環境 WordPress 網站每天服務 80,000 次頁面瀏覽。你已經在 Next.js 中重建了它——更乾淨的數據層、更快的部署、沒有插件臃腫。現在你面臨切換。那五分鐘的窗口,DNS 傳播、CDN 快取失效、你的監控儀表板要麼保持綠色,要麼像聖誕樹一樣亮起來。我在生產環境中執行過這個流程 12 次。代碼遷移永遠不是風險。切換才是。但如果你像對待藍綠部署一樣對待它——舊環境運行、新環境預熱、流量逐漸轉移——你就能把一個令人緊張的事件變成週二下午的日常工作。以下是讓我們最後三次切換如此順利的遊戲手冊,客戶直到我發送 Slack 確認才意識到我們已上線。
這是我們在 Social Animal 用於生產切換的遊戲手冊。它不是理論性的——它是從真實遷移中構建的,其中真實的收入處於危險中。我們談論的是每天做 $50K 的電子商務網站、擁有數百萬月度頁面瀏覽量的內容發佈商,以及 5 分鐘停機時間意味著執行長會打你手機的 SaaS 行銷網站。
目錄
- 為什麼零停機時間比你想的更重要
- 遷移架構概述
- 第 1 階段:內容遷移和 API 層
- 第 2 階段:構建 Next.js 應用程式
- 第 3 階段:並行部署設置
- 第 4 階段:藍綠部署配置
- 第 5 階段:DNS 切換策略
- 第 6 階段:切換檢查清單
- 第 7 階段:切換後監控和回滾
- 常見故障模式以及如何防止它們
- 常見問題

為什麼零停機時間比你想的更重要
讓我們用一些數字來說明。Google 的研究表明,頁面加載延遲 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 遷移便利性 | 實時同步可能 | 定價(2026) | 最適合 |
|---|---|---|---|---|
| Sanity | 高(結構化內容映射良好) | 是,通過 Webhooks | 免費層,然後 $99/月 | 複雜的內容模型 |
| Contentful | 中等(需要字段映射) | 是 | $300/月的團隊 | 企業團隊 |
| Strapi | 高(類似基於 DB 的模型) | 是 | 自託管免費,雲端起價 $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: '2026-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 網站捕獲 Core Web Vitals,然後匹配或超過它們。如果你的 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}`);
}
}
在繼續切換前,至少運行此測試 1 週零報警。
第 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,但大多數在 2026 年尊重它們)。
# 如果移動到 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 儀表板中的 Core Web Vitals
- 檢查 Google Search Console 以查找覆蓋率問題
- 驗證所有分析數據正確流動
- WordPress 源仍在運行(保持至少 2 週)
- 使用 Screaming Frog 對新網站進行完整站點爬取
第 7 階段:切換後監控和回滾
監控內容
在你的監控工具中設置儀表板(我們使用 Vercel Analytics、Datadog 和 Google Search Console 的組合)跟蹤:
- 錯誤率:任何 5xx 響應?任何 4xx 的增加?
- 響應時間:P50、P95、P99 延遲
- Core Web Vitals: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"}'
在切換後至少 2 週內保持 WordPress 運行。不要做英雄。你關閉 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 網站有已登錄的用戶,他們的 cookies 將無法在新堆棧上工作。單獨計劃用戶遷移。
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 已確認,如果內容、URL 和技術 SEO 元素保持等效,更改你的 CMS 不應該影響排名。最大風險是 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 插件(重定向、Yoast、RankMath)導出你的重定向。將它們轉換為 next.config.js 中的 Next.js 重定向格式。對於擁有數百個重定向的網站,改用中間件——它更具效能,可以處理模式匹配。注意,在 Vercel 上,next.config.js 重定向的實際限制約為 1,000 個條目,超過此數值構建時間會受到影響。
如果切換後出現問題,我可以回滾到 WordPress 嗎? 可以,這是不可協商的。在切換後至少 2 週內保持 WordPress 實例運行。使用 Cloudflare Worker 方法,回滾是一個單個 API 呼叫,在全球範圍內秒內生效。使用基於 DNS 的切換,回滾需要 1-10 分鐘,取決於 TTL 傳播。在你確信新系統穩定後再停用舊系統。