WordPress シングルルーター導入ガイド ― カスタム投稿タイプ詳細ページを1枚の single.php で管理する

WordPress で複数のカスタム投稿タイプ(CPT)を登録していると、個別記事ページごとに
single-cpt_news.phpsingle-cpt_event.php …とファイルが増えていき、テーマが散らかりがちです。

この問題を解決するのが 「シングルルーター」 です。
1 枚の single.php を入口にして、投稿タイプごとに適切なテンプレートへ振り分ける仕組みを構築します。


課題:CPT ごとに single-xxx.php が乱立

例として以下の投稿タイプを考えます。

  • cpt_news … ニュース記事
  • cpt_event … イベント情報
  • cpt_book … 書籍紹介

通常の方法では以下のファイルを作成する必要があります。

single-cpt_news.php
single-cpt_event.php
single-cpt_book.php

CPT が増えると同じ数だけ single-xxx.php が増えてしまい、保守が面倒になります。


解決策:シングルルーター

基本の考え方

  • 入口は single.php 1 枚だけ
  • 現在表示中の投稿タイプを判定し、事前に定義したマップから専用テンプレートを読み込む
  • マップに存在しなければ single.php 内の標準ループでフォールバック

ディレクトリ構成例

my-theme/
├─ functions.php
├─ single.php                         ← 入口は常にこれ1枚
├─ functions/
│   └─ single-router.php              ← シングルルーター本体
└─ templates/
   └─ single/
      ├─ news.php                     ← cpt_news 用
      ├─ event.php                    ← cpt_event 用
      └─ book.php                     ← cpt_book 用

コード例

1) functions.php

<?php
// シングルルーターを一度だけ読み込む
require_once get_theme_file_path('functions/single-router.php');

2) シングルルーター本体(functions/single-router.php)

<?php
/**
 * Single Router
 *
 * 目的:
 * - single.php を 1 枚に集約しつつ、CPT ごとに別置きテンプレへルーティング
 * - マッピング未定義時は null を返し、single.php 内の標準ループを実行
 */

if (!function_exists('my_get_single_template_for_query')) {
    function my_get_single_template_for_query(): ?string
    {
        if (!is_singular()) return null;

        $post_type = get_post_type(get_queried_object_id());

        $map = [
            'cpt_news'  => 'templates/single/news.php',
            'cpt_event' => 'templates/single/event.php',
            'cpt_book'  => 'templates/single/book.php',
        ];

        $map = apply_filters('my/single_template_map', $map);

        return $map[$post_type] ?? null;
    }
}

if (!function_exists('my_include_single_template')) {
    function my_include_single_template(string $relative_path): bool
    {
        $full = locate_template($relative_path);
        if (!$full) {
            $candidate = get_theme_file_path($relative_path);
            if (file_exists($candidate)) {
                $full = $candidate;
            }
        }
        if ($full && file_exists($full)) {
            require $full;
            return true;
        }
        return false;
    }
}

3) single.php

<?php
/**
 * single.php
 *
 * 1) 投稿タイプに対応するテンプレートがあれば読み込み
 * 2) 見つからなければ標準ループでフォールバック
 */

if ($template_rel = my_get_single_template_for_query()) {
    if (my_include_single_template($template_rel)) {
        return; // 終了
    }
}

get_header();

if (have_posts()) :
  while (have_posts()) : the_post(); ?>
    <main class="l-container">
      <article <?php post_class(); ?>>
        <h1><?php the_title(); ?></h1>
        <div><?php the_content(); ?></div>
      </article>
    </main>
  <?php endwhile;
endif;

get_footer();

4) 専用テンプレ雛形(templates/single/news.php)

投稿タイプ cpt_news 用の例。event.phpbook.php もこの雛形をコピーして調整してください。

<?php
/**
 * templates/single/news.php
 *
 * 投稿タイプ: cpt_news 用の個別記事テンプレート
 */

get_header();

if (have_posts()) :
  while (have_posts()) : the_post(); ?>
    <main class="l-container">
      <article <?php post_class('c-entry'); ?>>
        <header class="c-entry-header">
          <h1 class="c-entry-title"><?php the_title(); ?></h1>
          <time class="c-entry-date" datetime="<?php echo esc_attr(get_the_date('c')); ?>">
            <?php echo esc_html(get_the_date()); ?>
          </time>
        </header>

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

        <footer class="c-entry-footer">
          <?php
          // タグやカスタムフィールドなどを出力する例
          the_tags('<ul class="c-tags"><li>','</li><li>','</li></ul>');
          ?>
        </footer>
      </article>
    </main>
  <?php endwhile;
endif;

get_footer();

メリット

  1. ファイル数を削減
    → CPT が増えても single.php とマップに1行追加+専用テンプレを置くだけ。
  2. 拡張性が高い
    add_filter('my/single_template_map', …) で外部からマップを追加可能。
  3. 安全なフォールバック
    → 未定義のCPTでも single.php 内の標準ループで表示できる。
  4. 子テーマ対応
    locate_template() により子テーマのテンプレートが優先される。

まとめ

  • 従来は投稿タイプごとに single-xxx.php を作成していた
  • シングルルーターを導入すると、single.php 1 枚で一元管理できる
  • 新規CPTは マップに1行追記+専用テンプレを追加 するだけでOK

アーカイブルーターと併用すれば、一覧ページと詳細ページの両方を効率的に管理でき、テーマの保守性が大幅に向上します。

関連記事

コメント

タイトルとURLをコピーしました