WordPressオリジナルテーマの作り方|ゼロから自作する完全手順とコードサンプル【2026年版】

合同会社InnoMark
WEB制作からWEBマーケティングまでをご支援

WordPressで独自テーマ(オリジナルテーマ)を自作すると、デザインの自由度が格段に上がり、不要なコードを排除した軽量で高速なサイトを構築できます。本記事では、開発環境の構築から、必須ファイルの作成、テンプレート階層の活用、SEO対策、さらに2025年以降のトレンドであるブロックテーマ対応まで、WordPressテーマ開発の全工程をコード例つきで解説します。

この記事で得られること:

  • WordPressオリジナルテーマを構成するファイルの役割と作成手順
  • 実際にコピー&ペーストで使えるコード一式
  • 自作テーマと既存テーマの選び分け基準
  • SEOとパフォーマンスを最適化するための実装テクニック
  • クラシックテーマとブロックテーマの違いと将来の対応方針

この記事は「WEBサイト制作の完全ガイド|企画・設計から公開・運用まで」の個別テーマ解説です。制作の全体像はガイドからご覧ください。


目次

WordPress独自テーマ(オリジナルテーマ)とは?基本を理解しよう

独自テーマの定義と仕組み

WordPress独自テーマとは、開発者がゼロからコードを書いて作成するオリジナルのテーマファイル一式を指します。市販テーマや無料テーマのように第三者が開発したものとは異なり、サイトの要件に合わせて完全にカスタマイズされたテーマです。

WordPressのテーマは、PHPファイル、CSS、JavaScript、画像などのリソースファイルで構成されます。これらのファイルがWordPressのテンプレート階層(Template Hierarchy)に従って読み込まれ、サイトの各ページが表示される仕組みです。WordPress公式ドキュメント(参照:Template Hierarchy|WordPress Developer Resources)によると、最低限必要なファイルはstyle.cssindex.phpの2つだけで、この2ファイルがあればWordPressはテーマとして認識します。

なお、現在のWordPressには「クラシックテーマ」と「ブロックテーマ」の2種類があります。クラシックテーマはPHPテンプレートファイルを使用する従来型のテーマで、ブロックテーマはHTMLテンプレートとtheme.jsonを中心に構成されるフルサイト編集(FSE)対応のテーマです。本記事では、まずクラシックテーマの作り方を詳しく解説し、後半でブロックテーマへの対応方法についても触れます。

WordPressテーマを自作するメリット4つ

WordPressの独自テーマ作成には、大きく分けて4つのメリットがあります。

1つめは、完全オリジナルのデザインを実現できることです。市販テーマでは他のサイトと似たような見た目になりがちですが、独自テーマなら自分のブランドイメージやビジネス要件に100%合致したデザインを作り込めます。

2つめは、軽量で高速なサイトを構築できることです。市販テーマは多くのユーザーに対応するため汎用的な機能が多数含まれており、自サイトでは使わない機能のコードまで読み込まれます。独自テーマなら必要な機能だけを実装するため、ページの読み込み速度が大幅に改善されます。Googleが公開しているWeb Vitals(参照:Web Vitals|Google Developers)でも、ページ速度はランキング要因の一つとして明示されています。

3つめは、SEO対策の自由度が高いことです。metaタグの出力ロジック、構造化データ(JSON-LD)の配置、見出しタグの階層構造など、検索エンジン最適化に関わるHTMLの構造を完全にコントロールできます。

4つめは、将来の拡張性が高いことです。ビジネスの成長やサイトの方向性の変化に合わせて、テーマの制約なく機能追加やレイアウト変更に柔軟に対応できます。

独自テーマのデメリット・注意点

一方、独自テーマの開発にはデメリットもあります。事前に把握しておくことで、プロジェクトの計画を適切に立てられます。

技術的知識が必要である点が最大のハードルです。HTML、CSS、PHPの基礎に加え、WordPressのテンプレート階層やフック(アクション・フィルター)の仕組みへの理解が求められます。ただし、本記事で解説する範囲の基礎知識があれば、最小構成のテーマ作成は十分に可能です。

メンテナンスを自分で行う必要がある点も考慮すべきです。WordPressコアのアップデートに伴う互換性の確認や、セキュリティパッチの適用は自己責任になります。市販テーマのように開発元がアップデートを配信してくれるわけではありません。

セキュリティ対策も自身で実装する必要があります。入力値のサニタイズ(esc_html()esc_attr()など)や、SQLインジェクション対策($wpdb->prepare())を適切に行わないと、脆弱性の原因になります。

開発時間の目安として、最小構成のテーマなら1〜2時間で動作確認まで完了しますが、レスポンシブ対応・SEO最適化・カスタム機能まで含めた実用的なテーマの場合、初心者で20〜40時間、経験者で10〜20時間程度の作業を見込んでおきましょう。


WordPressテーマ作成の事前準備|開発環境とスキル

ローカル開発環境の構築

WordPressテーマの開発は、本番サーバーではなくローカル環境(自分のPC上の仮想環境)で行うのが基本です。コードのミスがあっても公開サイトに影響しないため、安全にテスト・修正を繰り返せます。

ローカル開発環境を構築するための代表的なツールは3つあります。

Local(旧 Local by Flywheel)は、WordPress専用に設計されたローカル開発ツールです。ボタンクリックだけでWordPressサイトを立ち上げられるため、初心者に最もおすすめです。複数サイトの同時管理やPHPバージョンの切り替えにも対応しています(参照:Local公式サイト)。

XAMPPは、Apache・MySQL・PHPをまとめてインストールできるクロスプラットフォーム対応のツールです。WordPress以外のPHP開発にも汎用的に使えます(参照:XAMPP公式サイト)。

MAMPは、Mac向けのローカルサーバー環境です。GUIで操作しやすく、シンプルな構成が特徴です。Windows版も提供されています(参照:MAMP公式サイト)。

本記事では、最もセットアップが簡単なLocalを使って解説を進めます。Localをインストールしたら、新しいサイトを作成し、WordPressの管理画面にログインできる状態にしてください。管理画面の「設定」→「一般」で、サイトの言語を「日本語」に、タイムゾーンを「UTC+9」に変更しておくとスムーズです。

コードエディタの準備

テーマ開発には、コードエディタが必要です。WordPress開発ではVisual Studio Code(VS Code)が最も広く使われています。無料で利用でき、コード補完・シンタックスハイライト・Git統合など開発に必要な機能が揃っています(参照:Visual Studio Code公式サイト)。

VS Codeに以下の拡張機能をインストールしておくと、テーマ開発の効率が上がります。

  • PHP Intelephense:PHPのコード補完と型チェック
  • WordPress Snippets:WordPressテンプレートタグのスニペット
  • Live Server:ファイル保存時にブラウザをリロード

必要な基礎知識の確認

WordPress独自テーマの作成に必要な技術知識は、大きく3つに分類できます。

HTML/CSSの基礎は必須です。セマンティックなHTML5の記述(<header><nav><main><article><footer>などの使い分け)、CSSによるレイアウト設計(Flexbox、Gridの基本)、メディアクエリを使ったレスポンシブ対応が理解できていれば十分です。

PHPの基本構文も必要ですが、WordPressテーマ開発に必要な範囲は限定的です。変数の宣言と代入、配列、if文による条件分岐、whileやforeachのループ処理、関数の定義と呼び出しが理解できれば、基本的なテーマは作れます。

WordPressテンプレート階層の理解が効率的なテーマ開発の鍵になります。WordPressは、表示しようとしているページの種類(トップページ、投稿ページ、固定ページ、カテゴリーページなど)に応じて、読み込むテンプレートファイルを自動的に決定します。例えば、個別投稿ページでは single-{post_type}.phpsingle.phpsingular.phpindex.php の順に探索され、最初に見つかったファイルが使われます。


WordPressテーマのファイル構成と各ファイルの役割

WordPressテーマは複数のPHPファイルとリソースファイルで構成されます。ここでは、各ファイルの役割と実際に記述するコードを解説します。

必須ファイル:style.css・index.php・functions.php

style.cssはテーマの「身分証明書」のような存在で、テーマの基本情報をコメントブロックで定義します。この情報がWordPress管理画面の「外観」→「テーマ」に表示されます。

/*
Theme Name: My Original Theme
Theme URI: https://example.com/my-original-theme
Description: WordPress独自テーマのサンプル
Author: Your Name
Author URI: https://example.com
Version: 1.0.0
License: GPL v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: my-original-theme
Requires at least: 6.0
Tested up to: 6.7
Requires PHP: 8.0
*/

Theme Nameがテーマ名として管理画面に表示され、Text Domainは翻訳対応のための識別子です。Requires at leastTested up toでWordPressの動作要件を明示しておくと、互換性の管理がしやすくなります(参照:Main Stylesheet (style.css)|WordPress Developer Resources)。

index.phpはテーマの「最終防衛ライン」とも呼べるファイルで、他の専用テンプレートファイルが存在しない場合に使用されるフォールバックテンプレートです。すべてのページタイプの表示に対応できる柔軟な構造にする必要があります。

functions.phpはテーマの「司令塔」で、テーマが利用する機能の有効化、スタイルやスクリプトの読み込み登録、カスタム関数の定義などを行います。このファイルはWordPressが読み込む際に自動で実行されるため、構文エラーがあるとサイト全体が表示されなくなる可能性があります。編集前のバックアップを習慣にしましょう。

推奨ファイルの役割一覧

最低限の2ファイルだけでも動作しますが、実用的なテーマには以下のファイルを追加します。

header.phpには、<!DOCTYPE html>宣言からナビゲーションメニューまでの共通ヘッダー部分を記述します。wp_head()関数の呼び出しが必須で、これによりWordPressコアやプラグインが必要なCSSやJavaScriptを<head>内に出力します。

footer.phpには、フッター情報から</html>までの共通フッター部分を記述します。wp_footer()関数の呼び出しが必須で、管理バーの表示やJavaScriptの読み込みに使われます。

single.phpは個別投稿ページ用のテンプレートです。投稿タイトル、本文、公開日、カテゴリー、タグ、前後の投稿へのリンク、コメント欄などを配置します。

page.phpは固定ページ用のテンプレートです。「会社概要」「お問い合わせ」「プライバシーポリシー」など、日付やカテゴリーに依存しない静的コンテンツの表示に使います。

archive.phpはカテゴリー・タグ・日付別などの一覧ページ用テンプレートです。記事のサムネイルと抜粋を表示し、ページネーションを実装します。

home.phpはブログ投稿一覧ページ(トップページが投稿一覧の場合)用テンプレートです。front-page.phpは固定ページをトップページに設定した場合に使用されるファイルで、用途が異なります。

404.phpはページが見つからない場合のエラーページです。サイト内検索フォームや人気記事へのリンクを設置して、ユーザーの離脱を防ぎます。

search.phpはサイト内検索結果の表示用テンプレートです。

sidebar.phpはサイドバーの表示用テンプレートで、ウィジェットエリアの出力に使います。

screenshot.pngは管理画面のテーマ一覧に表示されるプレビュー画像です。推奨サイズは1200×900ピクセルで、テーマのデザインがわかるスクリーンショットを配置します。


WordPress独自テーマの作成手順【実践コード付き】

ここからは、実際にWordPressテーマを作成する手順を、コードを示しながらステップバイステップで解説します。

Step1:テーマフォルダの作成

WordPressのテーマファイルは、wp-content/themes/ディレクトリ内に専用フォルダを作って格納します。Localを使っている場合、「Go to site folder」からサイトのフォルダを開き、app/public/wp-content/themes/に移動してください。

ここに新しいフォルダを作成します。フォルダ名は半角英数字とハイフンのみを使い、スペースや日本語は使用しないでください。

wp-content/themes/my-original-theme/
├── style.css
├── index.php
├── functions.php
├── header.php
├── footer.php
├── single.php
├── page.php
├── archive.php
├── home.php
├── 404.php
├── search.php
├── sidebar.php
├── screenshot.png
└── assets/
    ├── css/
    ├── js/
    └── images/

Tips: assetsフォルダを設けて、テーマ固有のCSS・JavaScript・画像ファイルを整理するのがベストプラクティスです。ファイル数が増えても管理しやすくなります。

Step2:style.cssにテーマ情報とCSSを記述

先ほど解説したテーマ情報のコメントブロックをstyle.cssの冒頭に記述した上で、基本的なCSSを追加します。

/*
Theme Name: My Original Theme
Description: WordPress独自テーマのサンプル
Author: Your Name
Version: 1.0.0
License: GPL v2 or later
Text Domain: my-original-theme
*/

/* === リセットCSS === */
*,
*::before,
*::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

/* === CSS変数の定義 === */
:root {
    --color-primary: #1a73e8;
    --color-text: #333333;
    --color-text-light: #666666;
    --color-bg: #ffffff;
    --color-bg-gray: #f5f5f5;
    --color-border: #e0e0e0;
    --font-base: 'Noto Sans JP', 'Hiragino Sans', sans-serif;
    --max-width: 1200px;
    --content-width: 800px;
}

body {
    font-family: var(--font-base);
    color: var(--color-text);
    background-color: var(--color-bg);
    line-height: 1.8;
    -webkit-font-smoothing: antialiased;
}

/* === レイアウト === */
.site-container {
    max-width: var(--max-width);
    margin: 0 auto;
    padding: 0 20px;
}

.content-area {
    display: flex;
    gap: 40px;
    padding: 40px 0;
}

.main-content {
    flex: 1;
    min-width: 0;
}

.sidebar {
    width: 300px;
    flex-shrink: 0;
}

/* === レスポンシブ(モバイルファースト) === */
@media (max-width: 768px) {
    .content-area {
        flex-direction: column;
    }
    .sidebar {
        width: 100%;
    }
}

CSS設計のポイントとして、CSS変数(カスタムプロパティ)を:rootで定義しておくと、サイト全体の色やフォントを一括で変更できます。また、モバイルファーストアプローチ(小さい画面を基準にして、大きい画面用のスタイルをmin-widthで追加していく)で記述すると、パフォーマンスと保守性の両面で有利です。

Step3:header.phpの作成

header.phpにはHTML文書の開始部分からサイトヘッダーまでを記述します。

<!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(); ?>>
<?php wp_body_open(); ?>

<div class="site-container">
    <header class="site-header">
        <div class="site-header__inner">
            <div class="site-branding">
                <?php if (is_front_page() && is_home()) : ?>
                    <h1 class="site-title">
                        <a href="<?php echo esc_url(home_url('/')); ?>">
                            <?php bloginfo('name'); ?>
                        </a>
                    </h1>
                <?php else : ?>
                    <p class="site-title">
                        <a href="<?php echo esc_url(home_url('/')); ?>">
                            <?php bloginfo('name'); ?>
                        </a>
                    </p>
                <?php endif; ?>

                <?php
                $description = get_bloginfo('description', 'display');
                if ($description || is_customize_preview()) : ?>
                    <p class="site-description"><?php echo esc_html($description); ?></p>
                <?php endif; ?>
            </div>

            <nav class="global-nav" aria-label="メインナビゲーション">
                <?php
                if (has_nav_menu('primary')) {
                    wp_nav_menu(array(
                        'theme_location' => 'primary',
                        'menu_class'     => 'global-nav__list',
                        'container'      => false,
                        'depth'          => 2,
                        'fallback_cb'    => false,
                    ));
                }
                ?>
            </nav>
        </div>
    </header>

コードのポイントを解説します。language_attributes()はWordPressの言語設定に基づいてlang="ja"などを自動出力します。wp_head()はテーマやプラグインが<head>内に出力する必要があるタグ(CSS、JavaScript、metaタグなど)を呼び出すための必須関数です。body_class()はページの種類に応じたクラス名(homesingle-postpage-id-2など)をbody要素に付与し、CSS設計に活用できます。wp_body_open()はWordPress 5.2で追加されたフック(参照:wp_body_open|WordPress Developer Resources)で、<body>直後にコードを挿入するためのものです。

SEO上のポイントとして、トップページでのみサイト名を<h1>タグで囲み、他のページでは<p>タグにしています。これにより、各ページのメインコンテンツの見出しを<h1>として適切に使用できます。

Step4:footer.phpの作成

    <footer class="site-footer">
        <div class="site-footer__inner">
            <p class="copyright">&copy; <?php echo date('Y'); ?> <?php bloginfo('name'); ?></p>
        </div>
    </footer>
</div><!-- .site-container -->

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

wp_footer()は必ず</body>の直前に配置してください。この関数がないと、管理バーが表示されない、プラグインのJavaScriptが読み込まれないなどの問題が発生します。

Step5:index.phpの作成(WordPressループの実装)

index.phpにはフォールバックテンプレートとして、投稿一覧を表示するWordPressループを実装します。

<?php get_header(); ?>

<div class="content-area">
    <main class="main-content" id="main-content">
        <?php if (have_posts()) : ?>
            <div class="post-list">
                <?php while (have_posts()) : the_post(); ?>
                    <article <?php post_class('post-card'); ?>>
                        <?php if (has_post_thumbnail()) : ?>
                            <a href="<?php the_permalink(); ?>" class="post-card__thumb">
                                <?php the_post_thumbnail('medium', array(
                                    'alt'     => esc_attr(get_the_title()),
                                    'loading' => 'lazy',
                                )); ?>
                            </a>
                        <?php endif; ?>
                        <div class="post-card__body">
                            <h2 class="post-card__title">
                                <a href="<?php the_permalink(); ?>">
                                    <?php the_title(); ?>
                                </a>
                            </h2>
                            <time class="post-card__date" datetime="<?php echo get_the_date('Y-m-d'); ?>">
                                <?php echo get_the_date(); ?>
                            </time>
                            <div class="post-card__excerpt">
                                <?php the_excerpt(); ?>
                            </div>
                        </div>
                    </article>
                <?php endwhile; ?>
            </div>

            <nav class="pagination" aria-label="ページナビゲーション">
                <?php
                echo paginate_links(array(
                    'type'      => 'list',
                    'prev_text' => '&laquo; 前へ',
                    'next_text' => '次へ &raquo;',
                ));
                ?>
            </nav>

        <?php else : ?>
            <div class="no-posts">
                <p>記事が見つかりませんでした。</p>
                <?php get_search_form(); ?>
            </div>
        <?php endif; ?>
    </main>

    <?php get_sidebar(); ?>
</div>

<?php get_footer(); ?>

WordPressループの仕組みを補足します。have_posts()は表示すべき投稿が残っているかを判定する関数です。the_post()はループ内で次の投稿データをセットアップする関数で、これを呼ぶことでthe_title()the_content()などのテンプレートタグが現在の投稿の情報を返すようになります。ループは管理画面の「設定」→「表示設定」で指定した「1ページに表示する最大投稿数」の分だけ繰り返されます(デフォルトは10件)。

post_class()body_class()と同様に、投稿の種類やカテゴリーに応じたクラス名を出力する関数で、CSS設計に便利です。

Step6:functions.phpでテーマ機能を有効化

<?php
/**
 * My Original Theme functions and definitions
 */

// テーマの直接アクセスを防止
if (!defined('ABSPATH')) {
    exit;
}

/**
 * テーマのセットアップ
 */
function my_theme_setup() {
    // タイトルタグの自動出力
    add_theme_support('title-tag');

    // アイキャッチ画像の有効化
    add_theme_support('post-thumbnails');

    // HTML5マークアップのサポート
    add_theme_support('html5', array(
        'comment-form',
        'comment-list',
        'gallery',
        'caption',
        'style',
        'script',
        'search-form',
        'navigation-widgets',
    ));

    // 埋め込みコンテンツのレスポンシブ対応
    add_theme_support('responsive-embeds');

    // 自動フィードリンクの追加
    add_theme_support('automatic-feed-links');

    // カスタムロゴの有効化
    add_theme_support('custom-logo', array(
        'height'      => 100,
        'width'       => 400,
        'flex-height' => true,
        'flex-width'  => true,
    ));

    // ナビゲーションメニューの登録
    register_nav_menus(array(
        'primary' => 'グローバルナビゲーション',
        'footer'  => 'フッターメニュー',
    ));

    // コンテンツ幅の設定
    if (!isset($content_width)) {
        $content_width = 800;
    }
}
add_action('after_setup_theme', 'my_theme_setup');

/**
 * CSS・JavaScriptの読み込み
 */
function my_theme_enqueue_scripts() {
    $theme   = wp_get_theme(get_template());
    $version = $theme->Version;

    // メインスタイルシート
    wp_enqueue_style(
        'my-theme-style',
        get_stylesheet_uri(),
        array(),
        $version
    );

    // Google Fonts(Noto Sans JP)
    wp_enqueue_style(
        'google-fonts',
        'https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap',
        array(),
        null
    );

    // メインJavaScript
    wp_enqueue_script(
        'my-theme-script',
        get_template_directory_uri() . '/assets/js/main.js',
        array(),
        $version,
        true // フッターで読み込み
    );
}
add_action('wp_enqueue_scripts', 'my_theme_enqueue_scripts');

/**
 * ウィジェットエリアの登録
 */
function my_theme_widgets_init() {
    register_sidebar(array(
        'name'          => 'サイドバー',
        'id'            => 'sidebar-1',
        'description'   => 'メインサイドバーのウィジェットエリア',
        'before_widget' => '<div id="%1$s" class="widget %2$s">',
        'after_widget'  => '</div>',
        'before_title'  => '<h3 class="widget-title">',
        'after_title'   => '</h3>',
    ));
}
add_action('widgets_init', 'my_theme_widgets_init');

/**
 * 抜粋の文字数を変更
 */
function my_theme_excerpt_length($length) {
    return 80;
}
add_filter('excerpt_length', 'my_theme_excerpt_length');

/**
 * 抜粋の末尾文字を変更
 */
function my_theme_excerpt_more($more) {
    return '...';
}
add_filter('excerpt_more', 'my_theme_excerpt_more');

コードのポイントを補足します。add_theme_support()はWordPressの標準機能をテーマで有効化する関数です。指定できる機能の一覧は公式ドキュメント(参照:add_theme_support()|WordPress Developer Resources)で確認できます。

CSS/JavaScriptの読み込みには必ずwp_enqueue_style()wp_enqueue_script()を使います。<link>タグや<script>タグをテンプレートに直接記述する方法は、プラグインとの競合やキャッシュ管理の問題を引き起こすため避けてください。

関数名にはmy_theme_のようなプレフィックスを付けて、WordPressコアやプラグインの関数と名前が衝突しないようにします。

Step7:single.phpで投稿ページを作成

<?php get_header(); ?>

<div class="content-area">
    <main class="main-content">
        <?php while (have_posts()) : the_post(); ?>
            <article <?php post_class('single-post'); ?>>
                <header class="single-post__header">
                    <div class="single-post__meta">
                        <time datetime="<?php echo get_the_date('Y-m-d'); ?>">
                            <?php echo get_the_date(); ?>
                        </time>
                        <?php
                        $categories = get_the_category();
                        if (!empty($categories)) : ?>
                            <span class="single-post__category">
                                <a href="<?php echo esc_url(get_category_link($categories[0]->term_id)); ?>">
                                    <?php echo esc_html($categories[0]->name); ?>
                                </a>
                            </span>
                        <?php endif; ?>
                    </div>
                    <h1 class="single-post__title"><?php the_title(); ?></h1>
                </header>

                <?php if (has_post_thumbnail()) : ?>
                    <figure class="single-post__thumbnail">
                        <?php the_post_thumbnail('large', array(
                            'alt' => esc_attr(get_the_title()),
                        )); ?>
                    </figure>
                <?php endif; ?>

                <div class="single-post__content entry-content">
                    <?php the_content(); ?>
                </div>

                <footer class="single-post__footer">
                    <?php
                    $tags = get_the_tags();
                    if (!empty($tags)) : ?>
                        <div class="single-post__tags">
                            <?php the_tags('<span class="tag">', '</span><span class="tag">', '</span>'); ?>
                        </div>
                    <?php endif; ?>
                </footer>
            </article>

            <nav class="post-navigation" aria-label="記事ナビゲーション">
                <div class="nav-previous"><?php previous_post_link('%link', '&laquo; %title'); ?></div>
                <div class="nav-next"><?php next_post_link('%link', '%title &raquo;'); ?></div>
            </nav>

            <?php
            if (comments_open() || get_comments_number()) :
                comments_template();
            endif;
            ?>

        <?php endwhile; ?>
    </main>

    <?php get_sidebar(); ?>
</div>

<?php get_footer(); ?>

Step8:page.phpで固定ページを作成

<?php get_header(); ?>

<div class="content-area">
    <main class="main-content">
        <?php while (have_posts()) : the_post(); ?>
            <article <?php post_class('page-content'); ?>>
                <h1 class="page-content__title"><?php the_title(); ?></h1>
                <div class="page-content__body entry-content">
                    <?php the_content(); ?>
                </div>
            </article>
        <?php endwhile; ?>
    </main>

    <?php get_sidebar(); ?>
</div>

<?php get_footer(); ?>

固定ページはブログ投稿と異なり、日付やカテゴリーの表示は通常不要です。企業情報、お問い合わせフォーム、プライバシーポリシーなど時間に依存しないコンテンツに使います。

Step9:404.phpでエラーページを作成

<?php get_header(); ?>

<div class="content-area">
    <main class="main-content">
        <div class="error-404">
            <h1>404 - ページが見つかりません</h1>
            <p>お探しのページは移動または削除された可能性があります。以下の方法をお試しください。</p>

            <div class="error-404__search">
                <h2>サイト内検索</h2>
                <?php get_search_form(); ?>
            </div>

            <div class="error-404__links">
                <h2>人気の記事</h2>
                <ul>
                    <?php
                    $popular_posts = new WP_Query(array(
                        'posts_per_page' => 5,
                        'orderby'        => 'comment_count',
                        'order'          => 'DESC',
                    ));
                    if ($popular_posts->have_posts()) :
                        while ($popular_posts->have_posts()) : $popular_posts->the_post(); ?>
                            <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
                        <?php endwhile;
                        wp_reset_postdata();
                    endif;
                    ?>
                </ul>
            </div>

            <p><a href="<?php echo esc_url(home_url('/')); ?>">トップページに戻る</a></p>
        </div>
    </main>
</div>

<?php get_footer(); ?>

404ページは離脱率を下げるために重要です。検索フォームと人気記事リンクを設置して、ユーザーがサイト内の別のコンテンツにたどり着けるように設計します。

テーマの有効化と動作確認

すべてのファイルを作成したら、WordPress管理画面の「外観」→「テーマ」に移動し、作成したテーマが表示されていることを確認します。テーマを「有効化」し、サイトを表示して各ページの動作を確認してください。

デバッグモードの有効化も忘れずに行いましょう。wp-config.phpに以下を追記すると、PHPエラーが表示されるようになります。

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

注意: 上記の設定は開発環境でのみ有効化してください。本番環境ではWP_DEBUGfalseに設定し、エラー情報が外部に漏れないようにします。

Query Monitorプラグイン(参照:Query Monitor|WordPress.org)をインストールすると、テンプレートの読み込み順序、データベースクエリ、フックの実行状況を詳細に確認でき、デバッグ効率が大幅に向上します。


独自テーマの高度なカスタマイズテクニック

基本テーマが動作するようになったら、より実用的な機能を追加していきましょう。

カスタム投稿タイプの作成

通常の「投稿」「固定ページ」以外に、独自のコンテンツタイプを定義できます。例えば「制作実績」「商品情報」「お客様の声」などのコンテンツを、通常の投稿とは分離して管理したい場合に使います。

functions.phpに以下のコードを追加します。

/**
 * カスタム投稿タイプ「制作実績」の登録
 */
function my_theme_register_post_types() {
    register_post_type('works', array(
        'labels' => array(
            'name'               => '制作実績',
            'singular_name'      => '制作実績',
            'add_new'            => '新規追加',
            'add_new_item'       => '新しい制作実績を追加',
            'edit_item'          => '制作実績を編集',
            'view_item'          => '制作実績を表示',
            'search_items'       => '制作実績を検索',
            'not_found'          => '制作実績が見つかりませんでした',
        ),
        'public'       => true,
        'has_archive'  => true,
        'rewrite'      => array('slug' => 'works'),
        'supports'     => array('title', 'editor', 'thumbnail', 'excerpt'),
        'menu_icon'    => 'dashicons-portfolio',
        'show_in_rest' => true, // ブロックエディタ対応に必須
    ));
}
add_action('init', 'my_theme_register_post_types');

show_in_resttrueにするのが重要です。この設定がないとブロックエディタ(Gutenberg)でカスタム投稿タイプを編集できません。

カスタム投稿タイプ専用のテンプレートはsingle-works.phparchive-works.phpというファイル名で作成すると、テンプレート階層に従って自動的に読み込まれます(参照:register_post_type()|WordPress Developer Resources)。

カスタムタクソノミーの追加

カスタム投稿タイプに対して、専用のカテゴリー分類を追加できます。

/**
 * カスタムタクソノミー「制作カテゴリー」の登録
 */
function my_theme_register_taxonomies() {
    register_taxonomy('works_category', 'works', array(
        'labels' => array(
            'name'          => '制作カテゴリー',
            'singular_name' => '制作カテゴリー',
            'add_new_item'  => '新しい制作カテゴリーを追加',
        ),
        'hierarchical' => true,  // true:カテゴリー型、false:タグ型
        'public'       => true,
        'rewrite'      => array('slug' => 'works-category'),
        'show_in_rest' => true,
    ));
}
add_action('init', 'my_theme_register_taxonomies');

テーマカスタマイザーの実装

テーマカスタマイザーを使うと、管理者がリアルタイムプレビューを見ながらサイトの外観設定を変更できる機能を提供できます。

/**
 * テーマカスタマイザーの設定
 */
function my_theme_customize_register($wp_customize) {
    // セクションの追加
    $wp_customize->add_section('my_theme_colors', array(
        'title'    => 'テーマカラー設定',
        'priority' => 30,
    ));

    // 設定の追加
    $wp_customize->add_setting('primary_color', array(
        'default'           => '#1a73e8',
        'sanitize_callback' => 'sanitize_hex_color',
        'transport'         => 'postMessage',
    ));

    // コントロールの追加
    $wp_customize->add_control(new WP_Customize_Color_Control($wp_customize, 'primary_color', array(
        'label'   => 'メインカラー',
        'section' => 'my_theme_colors',
    )));
}
add_action('customize_register', 'my_theme_customize_register');

sanitize_callbacksanitize_hex_colorを指定することで、不正な値の入力を防いでいます。セキュリティ対策としてサニタイズコールバックは必ず設定してください。


SEOに強いWordPressテーマの作り方

独自テーマの最大の強みは、SEOに最適化されたHTML構造を自分で設計できることです。ここでは、テーマ開発で押さえるべきSEO対策を解説します。

セマンティックHTML構造の設計

検索エンジンがコンテンツの意味を正しく理解するためには、セマンティック(意味論的)なHTML構造が重要です。

<h1>タグは各ページに1つだけ配置し、ページの主題を示します。投稿ページでは記事タイトル、トップページではサイト名を<h1>にします。<h2><h6>は本文中の見出しレベルに応じて階層的に使用し、見出しレベルを飛ばさないようにします(h2の次にh4を使わない)。

<main>要素は1ページに1つだけ配置し、メインコンテンツ領域を示します。<article>要素で個別の投稿コンテンツを囲み、<nav>要素でナビゲーション領域を明示し、<aside>でサイドバーなどの補助コンテンツを囲みます。

構造化データ(JSON-LD)の実装

構造化データを実装すると、検索結果にリッチスニペット(パンくずリスト、FAQ、レビュー星評価など)が表示される可能性が高まります。

functions.phpに以下のコードを追加して、投稿ページにArticleスキーマとBreadcrumbスキーマを出力します。

/**
 * 構造化データの出力
 */
function my_theme_structured_data() {
    if (is_single()) {
        global $post;
        $schema = array(
            '@context'      => 'https://schema.org',
            '@type'         => 'Article',
            'headline'      => get_the_title(),
            'datePublished' => get_the_date('c'),
            'dateModified'  => get_the_modified_date('c'),
            'author'        => array(
                '@type' => 'Person',
                'name'  => get_the_author(),
            ),
            'publisher'     => array(
                '@type' => 'Organization',
                'name'  => get_bloginfo('name'),
            ),
        );

        if (has_post_thumbnail()) {
            $schema['image'] = get_the_post_thumbnail_url($post->ID, 'full');
        }

        echo '<script type="application/ld+json">' . wp_json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . '</script>' . "\n";
    }
}
add_action('wp_head', 'my_theme_structured_data');

構造化データの実装が正しいかどうかは、Googleのリッチリザルトテスト(参照:リッチリザルトテスト|Google Search Console)で確認できます。

metaタグの動的生成

SEOプラグイン(Yoast SEOやRank Mathなど)を使う場合はプラグインがmetaタグを管理しますが、テーマ側で基本的なmetaタグを動的生成する仕組みを用意しておくと、プラグインなしでも最低限のSEO対策が機能します。

/**
 * metaディスクリプションの出力
 */
function my_theme_meta_description() {
    if (is_single() || is_page()) {
        $description = get_the_excerpt();
    } elseif (is_category()) {
        $description = category_description();
    } else {
        $description = get_bloginfo('description');
    }

    if (!empty($description)) {
        $description = wp_strip_all_tags($description);
        $description = mb_substr($description, 0, 120, 'UTF-8');
        echo '<meta name="description" content="' . esc_attr($description) . '">' . "\n";
    }
}
add_action('wp_head', 'my_theme_meta_description');

パフォーマンス最適化とCore Web Vitals対策

ページ速度はSEOのランキング要因であると同時に、ユーザー体験に直結します。WordPressテーマ開発で実装すべきパフォーマンス最適化を解説します。

画像の最適化

WordPress 5.8以降はWebP形式の画像アップロードが標準でサポートされています。さらにWordPress 6.1以降ではAVIF形式にも対応が進んでいます。テーマ側では、the_post_thumbnail()wp_get_attachment_image()を使って画像を出力すると、srcset属性によるレスポンシブ画像の配信が自動的に行われます。

遅延読み込み(Lazy Loading)はWordPress 5.5以降で自動付与されますが、ファーストビューに表示されるメイン画像(LCPの対象になりやすい画像)には遅延読み込みを無効化し、代わりにプリロードを設定するのがベストプラクティスです。

/**
 * LCP画像のプリロード
 */
function my_theme_preload_featured_image() {
    if (is_single() && has_post_thumbnail()) {
        $image_url = get_the_post_thumbnail_url(get_the_ID(), 'large');
        if ($image_url) {
            echo '<link rel="preload" as="image" href="' . esc_url($image_url) . '">' . "\n";
        }
    }
}
add_action('wp_head', 'my_theme_preload_featured_image', 1);

CSS/JavaScriptの最適化

パフォーマンス最適化の基本は、不要なリソースを読み込まないことと、必要なリソースを最適なタイミングで読み込むことです。

JavaScriptファイルは可能な限りフッター(</body>直前)で読み込みます。wp_enqueue_script()の第5引数をtrueにすることで、フッターでの読み込みを指定できます。

クリティカルCSS(ファーストビューの表示に必要なCSS)をインラインで<head>に配置し、その他のCSSを非同期で読み込む手法も有効です。

/**
 * クリティカルCSSをインライン出力
 */
function my_theme_critical_css() {
    $critical_css_path = get_template_directory() . '/assets/css/critical.css';
    if (file_exists($critical_css_path)) {
        echo '<style>' . file_get_contents($critical_css_path) . '</style>' . "\n";
    }
}
add_action('wp_head', 'my_theme_critical_css', 1);

Core Web Vitalsの改善ポイント

GoogleのCore Web Vitalsは、LCP(Largest Contentful Paint)、INP(Interaction to Next Paint。FIDの後継指標)、CLS(Cumulative Layout Shift)の3指標で構成されます(参照:Web Vitals|web.dev)。

LCPの改善には、メイン画像のプリロード、サーバーレスポンスタイムの短縮(キャッシュ活用)、レンダリングブロッキングリソースの削減が有効です。

INPの改善には、JavaScriptの実行量を最小化し、長時間のメインスレッド占有を避けることが重要です。イベントリスナーの最適化や、不要なサードパーティスクリプトの削減を行います。

CLSの改善には、画像や動画にwidthheight属性を明示し、レイアウトシフトを防止します。Webフォントの読み込みによるテキストの再レイアウトを防ぐため、font-display: swapを設定します。

/* Webフォントの表示最適化 */
@font-face {
    font-family: 'Noto Sans JP';
    font-display: swap;
    /* ... */
}

ブロックテーマ(FSE)への対応|WordPress開発の最新トレンド

WordPress 5.9で導入されたフルサイト編集(Full Site Editing / FSE)は、サイト全体をブロックエディタで編集できる仕組みです。FSEに完全対応したテーマを「ブロックテーマ」と呼びます。2024年11月リリースのWordPress 6.7でデフォルトテーマとなった「Twenty Twenty-Five」はブロックテーマとして設計されており、WordPressの開発方針がブロックテーマに向かっていることを示しています。

クラシックテーマとブロックテーマの違い

クラシックテーマ(本記事で解説してきた従来型テーマ)はPHPテンプレートとfunctions.phpを中心に構成されます。一方、ブロックテーマはHTMLテンプレートとtheme.jsonを中心に構成され、テンプレート内のレイアウトはWordPressのブロックマークアップで記述します。

ブロックテーマの最小構成は以下の通りです。

wp-content/themes/my-block-theme/
├── style.css
├── theme.json
└── templates/
    └── index.html

theme.jsonはテーマのデザインシステムを定義するファイルで、カラーパレット、フォントサイズ、余白の設定、ブロックごとのスタイル定義などをJSONで記述します。これにより、管理者がサイトエディタでデザインを調整する際の選択肢を制御できます。

ハイブリッドテーマという選択肢

現在のWordPressエコシステムでは、クラシックテーマにtheme.jsonを追加してブロックエディタの一部機能を取り込む「ハイブリッドテーマ」というアプローチも現実的です(参照:Kinsta – WordPressでハイブリッドテーマに移行するという選択肢)。

既存のクラシックテーマにtheme.jsonを追加するだけで、ブロックエディタのカラーパレットやフォントサイズの制御、レイアウト設定の一部を利用できるようになります。完全なブロックテーマへの移行を段階的に進められるため、2025年時点では多くのテーマ開発者がこのアプローチを採用しています。

{
    "$schema": "https://schemas.wp.org/trunk/theme.json",
    "version": 3,
    "settings": {
        "color": {
            "palette": [
                {
                    "slug": "primary",
                    "color": "#1a73e8",
                    "name": "Primary"
                },
                {
                    "slug": "dark",
                    "color": "#333333",
                    "name": "Dark"
                }
            ]
        },
        "typography": {
            "fontFamilies": [
                {
                    "fontFamily": "'Noto Sans JP', sans-serif",
                    "slug": "noto-sans-jp",
                    "name": "Noto Sans JP"
                }
            ]
        },
        "layout": {
            "contentSize": "800px",
            "wideSize": "1200px"
        }
    }
}

クラシックテーマの開発スキルはブロックテーマにも直結するため、まずはクラシックテーマの開発をしっかりマスターし、その後ブロックテーマやハイブリッドテーマに段階的に移行するのが現実的なロードマップです。


よくある質問(FAQ)

Q1. WordPress独自テーマの作成にかかる時間は?

最小構成のテーマ(style.css + index.php)であれば30分〜1時間で動作確認まで完了します。ただし、header/footer分離、レスポンシブ対応、SEO最適化、カスタム投稿タイプ、ページネーションなどを含む実用的なテーマの場合は、初心者で20〜40時間、経験者で10〜20時間程度の作業を見込んでください。効率化のコツは、事前にワイヤーフレームを作成し、ファイル構成を設計してから着手することです。

Q2. プログラミング初心者でもテーマを自作できる?

HTML/CSSの基礎知識があれば十分に可能です。PHPはWordPressテーマ開発で使う範囲に限れば、変数・配列・条件分岐・ループ・関数呼び出しを理解すれば対応できます。最初から完璧を目指さず、まず最小構成(style.css + index.php)で動作するテーマを作り、段階的にファイルを追加していくアプローチが成功の鍵です。WordPress公式のTheme Developer Handbook(参照:Theme Developer Handbook|WordPress Developer Resources)も体系的な学習リソースとして役立ちます。

Q3. 独自テーマと市販テーマ(既存テーマ)、どちらを選ぶべき?

判断基準は「カスタマイズの必要度」「開発リソース」「保守体制」の3つです。自社のブランドイメージに完全に合致したデザインが必要、パフォーマンスを細かく最適化したい、長期的にサイトを拡張していく予定がある場合は独自テーマが適しています。一方、短期間でサイトを公開したい、社内に開発・保守できる人材がいない、継続的なセキュリティアップデートを開発元に任せたい場合は市販テーマが適しています。中間的な選択肢として、市販テーマの子テーマを作成してカスタマイズする方法もあります。

Q4. セキュリティ対策で最低限やるべきことは?

WordPressテーマ開発で必ず実装すべきセキュリティ対策は5つあります。出力時のエスケープ処理(esc_html()esc_attr()esc_url())でXSS攻撃を防止すること。データベースクエリに$wpdb->prepare()を使ったプリペアドステートメントを実装してSQLインジェクションを防止すること。各PHPファイルの先頭にif (!defined('ABSPATH')) exit;を記述して直接アクセスを遮断すること。ユーザー権限のチェックにcurrent_user_can()を使用すること。フォーム送信時にnonceフィールド(wp_nonce_field())を使用してCSRF攻撃を防止すること。これらはWordPress公式のセキュリティガイド(参照:Theme Security|WordPress Developer Resources)にも記載されている基本事項です。

Q5. テーマが表示されない・正常に動作しない場合の対処法は?

まずWP_DEBUGを有効化してエラーメッセージを確認します。wp-config.phpdefine('WP_DEBUG', true);を設定してください。よくある原因は、PHPの構文エラー(閉じタグの抜け、セミコロン忘れ)、style.cssのテーマ情報コメントブロックの書式ミス(Theme Nameが未記入)、wp_head()wp_footer()の呼び忘れ、ファイルパスの記述ミスです。ブラウザの開発者ツール(F12キー)でコンソールエラーやネットワークエラーも確認しましょう。また、プラグインとの競合が疑われる場合は、すべてのプラグインを一時的に無効化して問題が解消されるか確認する切り分け手法が有効です。

Q6. クラシックテーマとブロックテーマ、今から学ぶならどちら?

2025年時点では、まずクラシックテーマの開発を学ぶことをおすすめします。理由は、クラシックテーマの知識(テンプレート階層、フック、テンプレートタグなど)がブロックテーマ開発の基礎にもなること、既存サイトの大多数がクラシックテーマで運用されていること、クラシックテーマにtheme.jsonを追加するハイブリッドアプローチで段階的にブロックテーマへ移行できることの3点です。クラシックテーマをマスターした上で、ブロックテーマの仕組みを学ぶのが最も効率的なスキルアップの道筋です。


まとめ:WordPress独自テーマ開発のロードマップ

WordPress独自テーマ(オリジナルテーマ)の作成は、適切な手順に沿って進めれば、初心者でも確実にマスターできるスキルです。本記事で解説した内容を振り返ると、開発のロードマップは以下のようになります。

最初のステップは、ローカル環境の構築とコードエディタの準備です。Localを使えば数分でWordPressの開発環境が整います。

次に、テーマフォルダを作成し、style.cssindex.phpの2ファイルで最小構成のテーマを動作させます。ここで「自分が作ったテーマがWordPressで動く」という成功体験を得ることが、モチベーション維持の鍵です。

その後、header.phpfooter.phpで共通パーツを分離し、functions.phpでテーマ機能を有効化し、single.phppage.phpなどの専用テンプレートを追加して、段階的にテーマを拡充します。

テーマの基本が完成したら、SEO対策(セマンティックHTML、構造化データ、metaタグ)とパフォーマンス最適化(画像最適化、CSS/JS軽量化、Core Web Vitals対策)を実装します。

そして将来的には、theme.jsonの導入やブロックテーマへの対応を検討し、WordPressの最新のエコシステムに適応していきます。

重要なのは、「最初から完璧を目指さない」ことです。まずは動作する最小限のテーマを作り、一つずつ機能を追加しながらスキルを磨いていくアプローチが、WordPress独自テーマ開発を成功させる最大のポイントです。

WEBサイト制作の全工程を体系的に把握したい方は「WEBサイト制作完全ガイド」をご覧ください。


関連記事

WordPressプラグインおすすめ40選【2026年最新】目的別に厳選して紹介

【2026年版】CMS比較ランキング|WordPress比較徹底検証

【2026年最新】WordPressフォームプラグイン完全ガイド:おすすめ7選と選び方

完全無料

無料診断サービス

専門家があなたのサイト・マーケティングを徹底分析。改善提案を無料でお届けします

無料

GA4の無料分析診断

Google Analytics 4の設定状況やデータ活用方法を無料で診断します

今すぐ診断を受ける
無料

WEBマーケティング無料診断

現在のマーケティング施策を分析し、改善提案を無料で提供します

準備中
無料

WEBサイト無料診断

サイトの使いやすさやSEO対策状況を無料で診断します

準備中