我已在超過一打 WordPress 網站上進行過 Next.js 生產遷移,我要告訴你一件可能會讓你驚訝的事:實際的程式碼遷移並不是難點。難點是切換。那個令人恐懼的時刻,當你翻動開關,祈禱一切不會出問題。好消息?有了正確的方案,你可以讓那一刻變得無聊。在運維中,無聊正是你想要的。

這是我們在 Social Animal 用於生產切換的方案。這不是理論上的——它來自於真實的遷移,其中真實的收入處於危險中。我們說的是日收入 $50K 的電商網站、月度瀏覽量達數百萬的內容發布商,以及 SaaS 行銷網站,其中 5 分鐘的停機時間意味著 CEO 會打你的手機。

目錄

Zero Downtime CMS Migration: WordPress to Next.js Cutover Playbook

為什麼零停機時間比你想的更重要

讓我們用數字說話。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 欄位。

Zero Downtime CMS Migration: WordPress to Next.js Cutover Playbook - architecture

第 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. 1% 流量到 Next.js — 監控 1 小時尋找錯誤
  2. 10% 流量 — 監控 2 小時
  3. 50% 流量 — 監控 4 小時
  4. 100% 流量 — 監控 24 小時
  5. 停用 WordPress — 在 100% 運行 1 週後

第 5 階段:DNS 切換策略

如果你無法使用 Cloudflare Workers(或類似的邊緣路由解決方案),你需要在 DNS 層級處理切換。這更棘手,因為 TTL 傳播。

切換前 DNS 準備

在切換前至少 48 小時:

  1. 將你的 DNS TTL 降低到 60 秒(從典型的 3600 或 86400)
  2. 等待舊 TTL 過期
  3. 驗證低 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.txtsitemap.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 傳播。在確信新系統穩定之前,永遠不要停用舊系統。