前面我們從零做了一個完整的區塊佈景主題,但真實世界裡你手上更可能是一個跑了三年的傳統佈景主題:header.php、single.php、一支兩千行的 style.css、functions.php 塞滿了各種 hook。客戶不會給你打掉重練的預算,你也不敢一次全換。
這篇講漸進遷移,重點不是「怎麼把舊主題改成新主題」,而是怎麼分階段讓每一步都能單獨上線,隨時停在中間也不會壞。
很多人以為「區塊佈景主題」是一個要嘛全有、要嘛全無的狀態,實際看 WordPress 核心的判定,會發現至少有三個獨立的開關:
| 開關 | 判定依據 | 打開之後 |
|---|---|---|
讀不讀 theme.json |
檔案存不存在 | 外觀選單的「區塊版面配置」變成「設計」 |
| 吃不吃 HTML 範本 | current_theme_supports( 'block-templates' ) |
templates/*.html 開始參與範本階層 |
| 是不是區塊佈景主題 | templates/index.html 存不存在 |
網站編輯器全開,PHP 範本全面停用 |
這三個開關可以分開打開,中間的每一個狀態都是合法的、可以上線的。整個遷移策略就建立在這件事上。
動手之前先盤點以下四類:
header.php、footer.php、single.php、archive.php… 哪些真的有人用,哪些是三年前複製來就沒動過的functions.php 的內容:add_theme_support 開了什麼、enqueue 了什麼、掛了哪些 hookstyle.css 裡出現過幾種顏色、幾種字級、幾種間距register_nav_menus、register_sidebar 註冊了哪些第 3 項最適合交給 AI,做法跟 everything-wp 的 make-block 指令是同一套思路,只是掃描對象從別人的網站換成自己的舊 CSS,可以請它把所有色碼列出來、依出現次數排序、把相近的分群,最後產出一份「原始值 → 建議語意名稱」的對照表。
第一步只做一件事:把散在 CSS 裡的顏色、字級、間距彙整起來寫進 theme.json。
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"color": {
"palette": [
{ "slug": "primary", "color": "#ffd24d", "name": "Primary" },
{ "slug": "contrast", "color": "#060606", "name": "Contrast" }
]
},
"spacing": { "spacingSizes": [ … ] },
"typography": { "fontSizes": [ … ] }
}
}
這一步的風險極低,因為前台外觀不會變。你的 style.css 還在原地跑,theme.json 只是多提供了一組變數,這樣整理後有兩個好處:

第一個是區塊編輯器的顏色與字級面板會跟設計對齊,編輯不再從無限色盤裡挑一個接近的顏色。第二個比較意外:後台「外觀」選單底下原本那個「區塊版面配置」,會變成「設計」,傳統佈景主題只要有 theme.json,就能進網站編輯器看樣式手冊(Style Book):

這個樣式手冊是唯讀的,你在裡面改不了任何東西,換句話說,加 theme.json 換到的是「看得到」不是「改得動」,要調預設樣式仍然只有改 theme.json 一途,要能在後台改,得等到階段三把主題變成真正的區塊佈景主題才行。
另一個好處是只要主題有 theme.jsonWordPress 就自動開啟 block-templates 支援,也就是說你放進 templates/ 的 HTML 範本從這一刻起就會生效了,不需要另外宣告什麼。
第二步是把 header.php 和 footer.php 改成可以在網站編輯器裡編的區塊範本組件。先宣告支援:
add_theme_support( 'block-template-parts' ); // WP 6.1 起.
接著建 parts/header.html,內容就是區塊標記:
<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:site-logo /-->
<!-- wp:site-title /-->
<!-- wp:navigation /-->
</div>
<!-- /wp:group -->
然後把 header.php 的內容換掉:
<?php
// 舊的:一堆 HTML 加 wp_nav_menu().
// 新的:把頁首交給區塊範本組件.
block_header_area();
block_header_area() 和 block_footer_area() 是 WordPress 5.9 就有的函式(wp-includes/block-template-utils.php),底層都是 block_template_part( $part ),可以印出任何一個 parts/*.html。
做完這一步,網站擁有者已經可以在後台改頁首頁尾了,而其他頁面仍然由你的 PHP 範本渲染。主題到這裡還不是區塊佈景主題,wp_is_block_theme() 仍然回傳 false。
我建議從頁首頁尾開始,是因為它們是全站共用、改動效益最大,而且結構相對單純(logo、選單、幾個連結),翻成區塊標記的風險比 single.php 那種帶迴圈與條件判斷的低很多。
第三步才開始動範本,而且可以一個一個換。能這樣做的原因在 locate_block_template():
// wp-includes/block-template.php
function locate_block_template( $template, $type, array $templates ) {
if ( ! current_theme_supports( 'block-templates' ) ) {
return $template;
}
if ( $template ) {
// locate_template() 已經找到一個 PHP 範本,
// 所以只考慮「specificity 高於或等於它」的區塊範本.
$index = array_search( $relative_template_path, $templates, true );
$templates = array_slice( $templates, 0, $index + 1 );
}
$block_template = resolve_block_template( $type, $templates, $template );
…
}
看懂這段就懂了整個策略:PHP 範本和 HTML 範本可以共存。WordPress 先照傳統的範本階層找到 PHP 範本,再看有沒有同樣(或更精確)的區塊範本可以取代它。你放了 templates/single.html,single 就走區塊範本;沒放 templates/archive.html,archive 就繼續用 archive.php。
所以要遷移的話建議是從流量低、結構單純的開始:
404.php → templates/404.html(幾乎沒有邏輯,最好練手)search.php、archive.php → 用 Query Loop 取代 The Looppage.php、single.php → 帶精選圖片、meta、留言的,最複雜front-page.php → 首頁通常最多客製,留到最後每換一個就部署觀察一段時間,出事只會出在那一種頁面,回退也只要刪掉那一個 HTML 檔。
最後才處理 index.html,因為它是那個總開關:
// wp-includes/class-wp-theme.php
public function is_block_theme() {
$paths_to_index_block_template = array(
$this->get_file_path( '/templates/index.html' ),
$this->get_file_path( '/block-templates/index.html' ),
);
…
}
is_block_theme() 只看這個檔案存不存在。放上去的那一刻,主題正式成為區塊佈景主題:網站編輯器全開、所有 PHP 範本停止作用。所以這一步等於「確認前面每一個範本都已經有 HTML 版本」的最終驗收,不要提早放。
一、掛在 wp_head 與 get_header 上的邏輯。 區塊佈景主題沒有 header.php,get_header() 不會執行,掛在 get_header 這個 action 上的東西就跟著消失。wp_head 本身還在(範本渲染時仍會呼叫),但那些「假設一定會經過 header.php」的程式碼要一個個確認。這是遷移時最常見的「東西不見了但沒有錯誤訊息」。
二、選單的資料搬遷。 wp_nav_menu() 讀的是 nav_menu 分類法,Navigation 區塊存的是自己的區塊標記(在 wp_navigation 這個文章類型裡),兩者資料結構不同不會自動同步。第一次插入 Navigation 區塊時,編輯器會提供從既有選單匯入的選項,用它匯一次,之後就以區塊那份為準,舊選單留著不會有作用。
三、小工具區。 register_sidebar 註冊的側邊欄在區塊佈景主題裡沒有位置,內容要改放進範本或範本組件,如果客戶習慣自己拖小工具,這件事要先講清楚,因為這是操作習慣的改變,不是技術問題。
最適合交給 AI 的有三件:
style.css 列出所有色碼與尺寸、分群、命名,產出 theme.json。single.php 裡的 The Loop 對應 Query Loop、the_post_thumbnail() 對應 Post Featured Image 區塊,這種一對一的翻譯它做得又快又準。functions.php,列出所有掛在 get_header、get_footer、get_sidebar 上的函式,那就是上一節第一個坑的清單。而你要自己判斷的是這幾件:遷移順序怎麼排、哪些舊功能趁這次直接放棄、客戶的哪些操作習慣需要事先溝通、以及什麼時候按下 index.html 那個開關。這些都不是技術題,是專案判斷。
還有一個要提醒的:不要讓 AI 一次把整個主題做遷移。 它能做到,但你會拿到一個無法逐步驗證的大改動,出事時不知道是哪一步壞的,一次一個範本,換完部署,看兩天,再換下一個。漸進遷移的價值就在這裡,別為了快而放棄它。
雖然有 AI 協助但要完成遷移還是一項滿費工的作業,另一條路是把舊網站當成設計稿,用之前介紹的流程重新做一個區塊佈景主題,做好之後切換。
這兩條路的差別在成本什麼時候發生、風險集中在哪裡。
| 漸進遷移 | 照舊站重做 | |
|---|---|---|
| 成本發生的時間 | 分散在每個階段 | 集中在上線前 |
| 何時看得到成果 | 加完 theme.json 就有 |
全部做完才有 |
| 上線風險 | 一次換一個範本,可單獨回退 | 一次性切換,風險集中在同一天 |
| 舊技術債 | 跟著搬過去 | 這是清掉它的唯一機會 |
| 設計一致性 | 從舊 CSS 反推,容易連原本的不一致一起繼承 | 從第一天就是單一來源 |
| PHP 客製(WooCommerce 範本覆蓋、會員、自訂查詢) | 可以先留著,慢慢處理 | 要重寫一遍 |
| 中途可以停嗎 | 可以,混合式主題本身就是合法狀態 | 不行,沒有中間狀態 |
| 長期維護成本 | 較高,可能長期維持兩套邏輯 | 較低 |
**選漸進遷移:**如果站台正在營運而且不能停、舊主題有大量 woocommerce/ 範本覆蓋或會員邏輯、預算需要分批處理,這些情境的共同點是「不能出事」比「做得漂亮」重要。
**打掉重練:**如果那支 style.css 已經爛到沒人敢動、設計本來就要順便翻新、舊主題的 PHP 客製其實不多(只是版面)、或者這個站你要長期維護下去。共同點是「以後每次改動都會受益」的情境。
有一個判準我覺得特別好用:問自己「舊主題裡有多少東西是我想留的」。如果答案是「版面和配色要留,程式碼一行都不想留」,那你要的其實是重做,只是用舊站當設計稿而已。
過去重做的成本高得嚇人,因為要重看設計、重刻 CSS、重建整套元件。但上一篇那條流程改變了計算方式:/make-block 接受的輸入就是一個網址,而你的舊網站就是一個現成的網址。
/make-block https://your-old-site.com
它會掃過舊站的每個代表頁、抽出實際的 computed style、盤點元件清單,然後生成 theme.json 與一整組區塊,換句話說,「照舊站重做」以前是一個月的工作,現在前面 80% 是自動的,你的工作變成審 UI Library 那一頁、把不對的地方挑出來。
所以如果你三年前評估過「重做太貴」而選擇了將就,現在值得重新評估一次。
老實說我自己的做法不是二選一,而是用重做的方式產生資產,用遷移的節奏上線:
/make-block,拿到乾淨的 theme.json 與一整組區塊 — 這是重做的產物,沒有繼承舊 CSS 的歷史包袱theme.json 與區塊放進舊主題裡,然後照本篇的三個階段,一個範本一個範本替換templates/index.html
這樣前面拿到重做的乾淨資產,後面保留遷移的低風險節奏。代價是中間那段時間你要同時看著兩套東西,但比起「一次性切換的那一天」,我寧願分散這個壓力。
最後補一個誠實的提醒:漸進遷移最大的風險,是中間狀態變成永久狀態。 混合式主題可以跑得很好,好到你永遠不會去按 index.html 那個開關,於是三年後這個主題還是一半 PHP 一半 HTML,新來的人看不懂它到底是哪一種。要避免這件事,開始遷移的時候就把最後一步排進行事曆,而不是等「有空的時候」。
下一篇我們介紹如何用 AI 跑完程式碼規範、靜態分析、測試與多語系這四道關卡,把主題整理到可以交付的狀態。
文章目錄:https://oberonlai.blog/category/2026-ithome/