前言
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_action 和 add_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>© <?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 嘛⋯⋯ 雖然常被嘲笑,但能用就好,不要有偏見。
最重要的是:自訂主題讓你的部落格真正成為「你的」部落格。不只是內容,連外觀和體驗都是你精心設計的。
延伸閱讀
- WordPress Theme Developer Handbook
- Template Hierarchy 視覺圖
- WordPress Coding Standards
- iquilezles.org — 本站的設計靈感來源
- Underscores (_s) — WordPress 官方推薦的 starter theme