子主題覆寫模板的正確做法與常見陷阱

把父主題的 single.php 複製到子主題、改個幾行就上線,本來以為穩穩的客製,結果父主題一更新就被洗掉,或是改了半天前台根本沒變化。子主題覆寫模板看起來只是「把檔案放對位置」,實際操作時卻藏著一堆讓人卡住的細節:檔案放了卻沒生效、get_template_part 找不到子主題的檔、區塊主題改了 HTML 前台不理你、include 的路徑指錯地方。

這篇會把子主題覆寫父主題模板的完整邏輯講清楚,從 WordPress 到底怎麼決定載入哪支檔案、傳統主題與區塊主題的覆寫方式差在哪,到實務上最常踩的幾個坑怎麼避開。看完你會知道哪些檔能覆寫、哪些不能、為什麼放了沒反應,以及覆寫之外什麼時候該改用掛鉤(hook)。

WordPress 怎麼決定載入子主題還是父主題的模板

核心規則只有一條:同名同路徑的模板檔,WordPress 一律優先載入子主題裡的那一份,找不到才回頭去父主題拿。這就是整個覆寫機制的基礎,也是為什麼「複製檔案、保留資料夾結構」就能改外觀而不動到父主題。

子主題本質上是父主題的延伸。啟用子主題後,網站預設會繼承父主題的所有設計與功能;你在子主題裡放了哪支模板檔,就只覆寫那一支,其餘照舊用父主題的。這種做法的好處很實際:父主題更新時,你的客製化全部安然無恙,因為改的東西都在子主題資料夾裡,父主題檔案從頭到尾沒被碰過。

要讓覆寫成立,子主題至少需要一個 style.css,而且檔頭裡的 Template 欄位必須跟父主題的資料夾名稱「100% 一致」。舉例來說,父主題位於 wp-content/themes/twentytwentyfour,子主題的 style.css 檔頭就得寫 Template: twentytwentyfour,大小寫、連字號全都不能差。這個欄位填錯,WordPress 不會把它認成子主題,覆寫自然失效。

/**
 * Theme Name: My Child
 * Template: twentytwentyfour
 */

傳統主題的模板覆寫該怎麼放檔案

傳統主題(classic theme)的覆寫原則是:在子主題裡建出跟父主題「相同的相對路徑」,再放進同名的 .php 檔,WordPress 就會自動改用子主題那一份。

最單純的情況是覆寫主目錄下的模板。父主題根目錄有 single.php,你想改單篇文章的版型,就把它複製到子主題根目錄,改子主題裡這份即可,父主題原檔不動。page.phparchive.php404.phpfooter.php 都是同樣邏輯。

比較容易出錯的是放在子資料夾裡的模板片段。假設父主題的結構是這樣:

父主題
mytheme

template-parts/content.php
子主題
mytheme-child

template-parts/content.php(覆寫)

要覆寫 template-parts/content.php,就得在子主題裡先建一個同名的 template-parts 資料夾,再把 content.php 放進去。路徑必須一字不差,少建一層資料夾、或資料夾名稱拼錯,WordPress 就找不到,前台還是顯示父主題的版本。這是新手最常見的「檔案放了卻沒生效」原因。

get_template_part 在覆寫時為什麼會找不到子主題的檔

get_template_part() 載入模板片段時,本身就會先找子主題、再找父主題,所以多數情況覆寫會自動成立。會出問題,通常是路徑寫法或資料夾結構對不上。

這個函式背後真正做事的是 locate_template(),它負責依序搜尋子主題與父主題資料夾。get_template_part() 只是幫你組出一份「要找哪些檔名」的清單交給它。當你呼叫 get_template_part( 'template-parts/content', 'page' ),它會去找 template-parts/content-page.php,先看子主題有沒有,沒有才用父主題的。

兩個常見地雷要避開。第一、呼叫子資料夾裡的片段,要把資料夾名稱接在 slug 前面,例如 get_template_part( 'template-parts/content', 'single' );漏掉資料夾前綴,就會跑去主目錄找而抓不到。第二、子主題的覆寫檔資料夾結構必須跟父主題完全一致,父主題放在 template-parts/ 底下,子主題就不能放到 parts/ 或根目錄,否則 locate_template() 比對不到同路徑,覆寫不會發生。

區塊主題的模板與區塊片段怎麼覆寫

區塊主題(block theme,即全站編輯 FSE)的覆寫邏輯跟傳統主題一致,差別在於檔案是 .html 而非 .php,而且放在 templates/parts/ 這兩個固定資料夾裡。

要覆寫區塊主題的某支模板,先看父主題裡那支檔案的相對路徑,再在子主題建出相同路徑的同名檔。例如覆寫 single.html,正確位置就是 wp-content/themes/yourtheme-child/templates/single.html;覆寫頁首區塊片段,就放到子主題的 parts/header.html。子主題還能新增父主題沒有的模板、片段與版面樣式(pattern),不一定只能覆寫既有的。

區塊主題有一個傳統主題不會遇到的陷阱:如果你曾在「網站編輯器」(Site Editor)裡改過某個模板,那份改動會存進資料庫,而資料庫的客製化優先權高於主題檔案。這時候你在子主題改了 .html,前台卻紋風不動,因為 WordPress 先吃資料庫那份。解法是進網站編輯器把該模板的客製化清除(clear customizations),讓它退回讀主題檔,子主題的 HTML 改動才會生效。覆寫版面樣式時還要留意,pattern 必須註冊相同的 Slug 欄位才會被當成同一個來覆寫。

functions.php 為什麼不能用覆寫的方式來改

這是最容易誤解的一點:子主題的 functions.php 「不會」覆寫父主題的 functions.php,兩支檔案都會被載入,而且子主題那份先執行、父主題那份緊接著執行。

正因為兩份都跑,functions.php 反而是改父主題功能最安全的入口。你想加一個 PHP 函式或調整某段行為,直接寫進子主題的 functions.php 就好,父主題更新時這段程式不會消失。但有個動作千萬別做:不要把父主題 functions.php 裡的程式整段複製到子主題。父主題那份還是會載入,同名函式被定義兩次,WordPress 會直接拋出致命錯誤(fatal error)讓整站掛掉。

如果你的目的是「改掉父主題某個函式的行為」,正確做法不是複製覆寫,而是用掛鉤。父主題若把功能掛在 wp_enqueue_scriptsinit 這類動作上,你可以在子主題用 remove_action() 把它移除再重掛自己的版本;若父主題用了 add_filter,就用對應的 filter 改輸出。能用掛鉤調整的,就不要用整段覆寫,維護起來乾淨得多。

子主題啟用後樣式沒生效的常見原因

子主題能不能正確載入自己的 CSS,取決於父主題是怎麼載入樣式表的,沒有一體適用的規則,必須先看父主題的程式碼。

理想情況是父主題會同時載入自己和子主題的 style.css,那子主題什麼都不用做,CSS 自動生效。但不是每個主題都這樣寫。區塊主題多半透過 theme.json 處理樣式,根本不會載入 style.css;像 Twenty Twenty-Four 就完全不載入樣式表。遇到這種情況,子主題得自己在 functions.php 裡掛上載入:

add_action( 'wp_enqueue_scripts', 'my_child_enqueue_styles' );
function my_child_enqueue_styles() {
    wp_enqueue_style( 'my-child-style', get_stylesheet_uri() );
}

判斷標準很簡單:去翻父主題的程式碼,看它 wp_enqueue_style() 載入了哪些樣式表。父主題若只載入自己的 style.css,子主題就用上面這段把自己的也載進來;父主題若是用 get_stylesheet_uri() 載入「當前啟用主題」的樣式,那它載到的其實已經是子主題的 style.css,這時你反而可能需要額外把父主題的樣式表也載進來,才不會掉樣式。先讀父主題、再決定怎麼掛,比盲目複製貼上一段網路上的範例可靠。

include 與資源路徑指錯目錄的陷阱

覆寫模板時最隱蔽的坑,是在程式裡引用檔案或圖片時用錯了取路徑的函式,導致明明在子主題,路徑卻指回父主題。

關鍵在於分清楚四個函式的差別。get_template_directory()get_template_directory_uri() 永遠指向「父主題」資料夾,是用來確保不被子主題覆寫的;get_stylesheet_directory()get_stylesheet_directory_uri() 則指向「當前啟用主題」,啟用子主題時就是子主題資料夾。差一個字,路徑就差了一整個目錄。

實務上記住兩條原則就不太會錯。第一、PHP 端 includerequire 子主題裡的檔案,要用回傳「伺服器路徑」的函式而非 _uri(URL)版本,現行建議直接用 get_theme_file_path(),它會優先回傳子主題的檔,沒有才回父主題,例如 require_once get_theme_file_path( 'inc/helpers.php' )。第二、要引用圖片、CSS 這類用 URL 指向的資源,改用 get_theme_file_uri(),例如取子主題 assets/images/logo.png 就寫 get_theme_file_uri( 'assets/images/logo.png' )。父主題裡若把路徑寫死成 get_template_directory_uri(),子主題就改不動那個資源,這也是「覆寫了檔案但圖還是舊的」的元兇之一。

覆寫沒生效時怎麼確認 WordPress 實際載入了哪支檔案

不確定前台到底吃了子主題還是父主題的模板時,與其反覆猜測,不如直接讓 WordPress 告訴你它載入了哪一支。

最快的方式是裝 Query Monitor 這個外掛,它會在工具列直接顯示當前頁面正在使用的模板檔案完整路徑,一眼就能看出是子主題還是父主題那份。同時記得在 wp-config.php 開啟 WP_DEBUG,路徑寫錯或函式呼叫有問題時,錯誤訊息才會浮出來而不是靜默失敗。

確認時照這個順序排查:模板檔在子主題的相對路徑跟父主題一不一樣、style.cssTemplate 欄位有沒有對到父主題資料夾名、子資料夾的片段有沒有用對 get_template_part 的路徑前綴、區塊主題的話有沒有殘留網站編輯器的資料庫客製化。多數「放了沒反應」的問題,都能在這幾步裡找到原因。

把覆寫用在對的地方,父主題更新才不會再洗掉你的客製

子主題覆寫的價值,在於讓客製與父主題徹底分離,父主題隨它更新,你的版型與功能都留在子主題裡安然無事。但用對方法是前提:模板與版面樣式靠「同名同路徑複製檔案」覆寫,功能調整靠 functions.php 加程式或掛鉤,兩者機制完全不同,混用就會踩雷。

實際動手前先想清楚你要改的是「外觀結構」還是「功能行為」。前者複製對應的 .php.html 模板到子主題對應路徑;後者優先用 remove_actionadd_filter 這類掛鉤,而不是整段複製父主題的 functions.php。覆寫完習慣用 Query Monitor 驗一次實際載入的檔案,再用瀏覽器無痕視窗或清快取確認前台。當子主題的覆寫多到難以管理、幾乎重寫了大半個父主題時,那就是該考慮直接 fork 出一個自己的完整主題的訊號了。

相關文章
標籤: template hierarchy, WordPress 主題開發, 模板覆寫, 區塊主題, 子主題