前言

WordPress 的佈景主題市場上有成千上萬的選擇,但對一個後端工程師來說,用別人的主題總覺得不夠痛快。你想要的版面配置可能要裝十個外掛才能實現,而且那些主題的 CSS 通常肥到不行。

所以我決定自己寫一個。

這篇文章紀錄我從零開始開發 WordPress 自訂主題的完整過程——從最基本的檔案結構到最終的暗色主題實作。如果你有基本的 HTML/CSS 能力和一點 PHP 基礎,跟著這篇走完,你也能做出自己的 WordPress 主題。


WordPress 主題的最小結構

一個 WordPress 主題最少只需要兩個檔案:

my-theme/
├── style.css        # 必須,包含主題元資訊
└── index.php        # 必須,預設模板

style.css 的主題元資訊

/*
Theme Name: TechBlog
Theme URI: https://example.com/techblog
Author: 你的名字
Author URI: https://example.com
Description: 暗色極簡技術部落格主題
Version: 1.0.0
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: techblog
Tags: dark, minimal, blog, code
*/

WordPress 就是靠這段註解來辨識主題的。Theme Name 是唯一必填的欄位。

最陽春的 index.php

<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
    <meta charset="<?php bloginfo('charset'); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
    <h1><?php bloginfo('name'); ?></h1>

<?php if (have_posts()) : while (have_posts()) : the_post(); ?> <article> <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2> <div><?php the_excerpt(); ?></div> </article> <?php endwhile; endif; ?>

<?php wp_footer(); ?> </body> </html>

這就是一個能動的 WordPress 主題了!當然,它醜得要命,但這就是起點。


Template Hierarchy:WordPress 的模板選擇邏輯

WordPress 最強大的概念之一是 Template Hierarchy(模板層級)。當使用者訪問不同頁面時,WordPress 會按照優先順序尋找對應的模板檔案:

首頁:        front-page.php → home.php → index.php
單篇文章:    single-{post_type}.php → single.php → singular.php → index.php
靜態頁面:    page-{slug}.php → page-{id}.php → page.php → singular.php → index.php
分類頁:      category-{slug}.php → category-{id}.php → category.php → archive.php → index.php
搜尋結果:    search.php → index.php
404 頁面:    404.php → index.php

理解這個機制後,你就知道該建立哪些檔案了。我的主題最終有這些模板:

techblog/
├── style.css
├── functions.php      # 主題功能核心
├── header.php         # 共用頁首
├── footer.php         # 共用頁尾
├── front-page.php     # 首頁(文章卡片牆)
├── index.php          # 預設列表
├── single.php         # 單篇文章
├── page.php           # 靜態頁面
├── archive.php        # 分類/標籤列表
├── search.php         # 搜尋結果
└── 404.php            # 404 頁面

functions.php:主題的大腦

functions.php 是主題的核心設定檔。所有的功能註冊、資源載入、自訂設定都在這裡:

<?php
// === 主題基本設定 ===
function techblog_setup() {
    // 支援文章特色圖片
    add_theme_support('post-thumbnails');

// 支援標題標籤 add_theme_support('title-tag');

// 支援 HTML5 add_theme_support('html5', array( 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption', ));

// 註冊選單 register_nav_menus(array( 'primary' => '主選單', 'footer' => '頁尾選單', ));

// 設定內容寬度 $GLOBALS['content_width'] = 960; } add_action('after_setup_theme', 'techblog_setup');

// === 載入 CSS 和 JavaScript === function techblog_scripts() { // 主樣式表 wp_enqueue_style( 'techblog-style', get_stylesheet_uri(), array(), filemtime(get_stylesheet_directory() . '/style.css') );

// Prism.js 語法高亮 wp_enqueue_style( 'prism-css', get_template_directory_uri() . '/assets/css/prism-tomorrow.css' ); wp_enqueue_script( 'prism-js', get_template_directory_uri() . '/assets/js/prism.js', array(), null, true // 放在 footer );

// MathJax 數學公式 wp_enqueue_script( 'mathjax', 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js', array(), null, true ); } add_action('wp_enqueue_scripts', 'techblog_scripts');

// === 自訂摘要長度 === function techblog_excerpt_length($length) { return 30; // 字數 } add_filter('excerpt_length', 'techblog_excerpt_length');

// === 自訂摘要結尾 === function techblog_excerpt_more($more) { return '...'; } add_filter('excerpt_more', 'techblog_excerpt_more');

// === 註冊側邊欄 === function techblog_widgets() { register_sidebar(array( 'name' => '側邊欄', 'id' => 'sidebar-1', 'before_widget' => '<div class="widget">', 'after_widget' => '</div>', 'before_title' => '<h3 class="widget-title">', 'after_title' => '</h3>', )); } add_action('widgets_init', 'techblog_widgets');

幾個重要的概念

add_actionadd_filter:WordPress 的 Hook 系統。action 是在特定時機執行某個函式,filter 是修改某個值再傳回去。這跟後端框架的 middleware 概念很像。

wp_enqueue_style / wp_enqueue_script:正確的載入 CSS/JS 方式。不要直接在 header 裡寫 <link><script>,用 enqueue 系統才能處理好相依性和避免重複載入。

filemtime:用檔案修改時間當版本號,這樣修改 CSS 後瀏覽器會自動載入新版,不用手動清 cache。


Header 和 Footer

header.php

<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
    <meta charset="<?php bloginfo('charset'); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>

<header class="site-header"> <div class="container"> <a href="<?php echo home_url('/'); ?>" class="site-logo"> <?php bloginfo('name'); ?> </a>

<nav class="main-nav"> <?php wp_nav_menu(array( 'theme_location' => 'primary', 'container' => false, 'menu_class' => 'nav-list', 'fallback_cb' => false, )); ?>

<form class="search-form" action="<?php echo home_url('/'); ?>" method="get"> <input type="text" name="s" placeholder="搜尋..." value="<?php echo get_search_query(); ?>"> </form> </nav> </div> </header>

<main class="site-content"> <div class="container">

footer.php

</div><!-- .container -->
</main>

<footer class="site-footer"> <div class="container"> <p>&copy; <?php echo date('Y'); ?> <?php bloginfo('name'); ?>. 用 WordPress + Docker 搭建。</p> </div> </footer>

<?php wp_footer(); ?> </body> </html>


暗色主題的 CSS 設計

暗色主題的重點不只是「把背景變黑」,還要注意對比度、可讀性、和層次感

色彩系統

:root {
    / 背景層次 /
    --bg-primary: #1e1e1e;      / 最底層背景 /
    --bg-card: #383838;          / 卡片背景 /
    --bg-hover: #404040;         / 懸停狀態 /
    --bg-code: #2d2d2d;          / 程式碼區塊 /

/ 文字層次 / --text-primary: #e0e0e0; / 主要文字 / --text-secondary: #a0a0a0; / 次要文字 / --text-muted: #666666; / 弱化文字 /

/ 強調色 / --accent: #ff4040; / 紅色強調 / --accent-hover: #ff6060; / 強調色懸停 / --link: #6db3f2; / 連結色 / --link-hover: #90c8ff; / 連結懸停 /

/ 間距 / --spacing-sm: 0.5rem; --spacing-md: 1rem; --spacing-lg: 2rem; --spacing-xl: 3rem;

/ 圓角 / --radius: 6px;

/ 最大寬度 / --max-width: 960px; }

基礎樣式

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background-color: var(--bg-primary); color: var(--text-primary); line-height: 1.7; font-size: 16px; }

.container { max-width: var(--max-width); margin: 0 auto; padding: 0 var(--spacing-lg); }

a { color: var(--link); text-decoration: none; transition: color 0.2s ease; }

a:hover { color: var(--link-hover); }

卡片式文章列表

.post-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--spacing-lg);
}

@media (max-width: 900px) { .post-grid { grid-template-columns: repeat(2, 1fr); } }

@media (max-width: 600px) { .post-grid { grid-template-columns: 1fr; } }

.post-card { background: var(--bg-card); border-radius: var(--radius); padding: var(--spacing-lg); transition: background 0.2s ease, transform 0.2s ease; }

.post-card:hover { background: var(--bg-hover); transform: translateY(-2px); }

.post-card .post-title { font-size: 1.1rem; margin-bottom: var(--spacing-sm); }

.post-card .post-title a { color: var(--text-primary); }

.post-card .post-title a:hover { color: var(--accent); }

.post-card .post-meta { color: var(--text-muted); font-size: 0.85rem; margin-bottom: var(--spacing-sm); }

.post-card .post-excerpt { color: var(--text-secondary); font-size: 0.95rem; }

程式碼區塊樣式

技術部落格最重要的就是程式碼的呈現。搭配 Prism.js 的 Tomorrow Night 主題:

pre[class*="language-"] {
    background: var(--bg-code);
    border-radius: var(--radius);
    padding: var(--spacing-lg);
    overflow-x: auto;
    font-size: 0.9rem;
    line-height: 1.5;
    margin: var(--spacing-lg) 0;
}

code { font-family: 'Fira Code', 'JetBrains Mono', 'Consolas', monospace; }

/ 行內程式碼 / :not(pre) > code { background: var(--bg-code); padding: 0.2em 0.4em; border-radius: 3px; font-size: 0.9em; }


首頁模板:文章卡片牆

<?php get_header(); ?>

<div class="page-header"> <h1 class="page-title"><?php bloginfo('description'); ?></h1> </div>

<div class="post-grid"> <?php $args = array( 'posts_per_page' => 12, 'post_status' => 'publish', ); $query = new WP_Query($args);

if ($query->have_posts()) : while ($query->have_posts()) : $query->the_post(); ?> <div class="post-card"> <?php if (has_post_thumbnail()) : ?> <div class="post-thumbnail"> <a href="<?php the_permalink(); ?>"> <?php the_post_thumbnail('medium'); ?> </a> </div> <?php endif; ?>

<div class="post-meta"> <time datetime="<?php echo get_the_date('c'); ?>"> <?php echo get_the_date('Y.m.d'); ?> </time> <?php $categories = get_the_category(); if ($categories) { echo ' / ' . esc_html($categories[0]->name); } ?> </div>

<h2 class="post-title"> <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a> </h2>

<div class="post-excerpt"> <?php the_excerpt(); ?> </div> </div> <?php endwhile; wp_reset_postdata(); endif; ?> </div>

<?php get_footer(); ?>


單篇文章模板

<?php get_header(); ?>

<?php if (have_posts()) : while (have_posts()) : the_post(); ?>

<article class="single-post"> <header class="post-header"> <div class="post-meta"> <time datetime="<?php echo get_the_date('c'); ?>"> <?php echo get_the_date('Y 年 n 月 j 日'); ?> </time> <?php $categories = get_the_category(); if ($categories) { echo ' — '; foreach ($categories as $cat) { echo '<a href="' . get_category_link($cat) . '">' . esc_html($cat->name) . '</a> '; } } ?> </div> <h1 class="post-title"><?php the_title(); ?></h1> </header>

<div class="post-content"> <?php the_content(); ?> </div>

<footer class="post-footer"> <?php $tags = get_the_tags(); if ($tags) { echo '<div class="post-tags">'; foreach ($tags as $tag) { echo '<a href="' . get_tag_link($tag) . '" class="tag">' . '#' . esc_html($tag->name) . '</a> '; } echo '</div>'; } ?>

<nav class="post-navigation"> <div class="prev-post"> <?php previous_post_link('%link', '← %title'); ?> </div> <div class="next-post"> <?php next_post_link('%link', '%title →'); ?> </div> </nav> </footer> </article>

<?php endwhile; endif; ?>

<?php get_footer(); ?>


開發小技巧

1. 用 WP_DEBUG 幫助除錯

wp-config.php 中開啟:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);

錯誤訊息會寫入 wp-content/debug.log,不會顯示在前台嚇到訪客。

2. 善用 Template Tags

WordPress 內建大量的 Template Tags,不要自己去查資料庫:

// 好的做法
the_title();
the_permalink();
the_excerpt();
get_the_date();

// 不好的做法(除非真的有特殊需求) global $wpdb; $wpdb->get_results("SELECT post_title FROM wp_posts...");

3. 響應式設計的斷點

我用的斷點策略是 Mobile First,但對技術部落格來說,桌面版才是主要使用場景:

/ 預設:手機 /
.post-grid { grid-template-columns: 1fr; }

/ 平板 / @media (min-width: 600px) { .post-grid { grid-template-columns: repeat(2, 1fr); } }

/ 桌面 / @media (min-width: 900px) { .post-grid { grid-template-columns: repeat(3, 1fr); } }


小結

自訂 WordPress 主題的門檻其實不高。只要你理解 Template Hierarchy 的概念,會基本的 PHP 和 CSS,就能做出一個功能完整、外觀獨特的主題。

對後端工程師來說,WordPress 的 Hook 系統(action/filter)應該會覺得很親切——它的概念跟我們熟悉的 middleware 和 event system 非常類似。而 PHP 嘛⋯⋯ 雖然常被嘲笑,但能用就好,不要有偏見。

最重要的是:自訂主題讓你的部落格真正成為「你的」部落格。不只是內容,連外觀和體驗都是你精心設計的。

延伸閱讀