2026年7月11日
2026年7月11日
WordPressのテーマでパンくずリストをプラグインなしで独自実装する方法
パンくずリストをプラグインなしで作るなら、「表示中のページがどの階層にあるか」をWordPressの条件分岐タグで判定し、ホームから現在地までのリンクを配列に組み立ててから出力する、という2段階に分けて実装するのが分かりやすい方法です。この記事では、投稿・固定ページ・カテゴリー・カスタム投稿タイプなどに対応した関数と、クラシックテーマ/ブロックテーマへの組み込み方、構造化データを付けるときの注意点までを解説します。
独自実装とプラグイン、どちらを選ぶか
Yoast SEO や Rank Math などのSEOプラグインには、パンくずリストの機能が含まれています。すでにこれらを使っていて、表示形式に不満がなければ、その機能を使うほうが保守の手間は少なくなります。
独自実装が向いているのは、SEOプラグインを使っていない、カテゴリーの扱いや表示文言を細かく決めたい、テーマのマークアップに合わせたい、といった場合です。自分で書くぶん、新しい投稿タイプやタクソノミーを追加したときには関数の更新が必要になります。
作業前の準備
コードは子テーマの functions.php に書きます。親テーマに書くとテーマ更新で消えるためです。functions.php の書き間違いはサイト全体の表示を止めてしまうので、編集前にバックアップを取り、FTPやファイルマネージャーで元に戻せる状態にしておきます。
手順1:パンくずの項目を配列で組み立てる
まず、表示中のページに応じて「ラベル」と「URL」の組を順に並べた配列を返す関数を作ります。HTMLの出力と分けておくと、後で構造化データを作るときにも同じ配列を使えます。
<?php
// 子テーマの functions.php
function mytheme_breadcrumb_items() {
if ( is_front_page() ) {
return array(); // トップページでは表示しない
}
$items = array();
$items[] = array( 'label' => 'ホーム', 'url' => home_url( '/' ) );
if ( is_home() ) {
// 「投稿ページ」に指定した固定ページ
$items[] = array( 'label' => get_the_title( get_option( 'page_for_posts' ) ), 'url' => '' );
} elseif ( is_singular( 'post' ) ) {
$categories = get_the_category( get_queried_object_id() );
if ( $categories ) {
$category = $categories[0];
$parents = array_reverse( get_ancestors( $category->term_id, 'category', 'taxonomy' ) );
foreach ( $parents as $parent_id ) {
$items[] = array( 'label' => get_cat_name( $parent_id ), 'url' => get_category_link( $parent_id ) );
}
$items[] = array( 'label' => $category->name, 'url' => get_category_link( $category->term_id ) );
}
$items[] = array( 'label' => get_the_title( get_queried_object_id() ), 'url' => '' );
} elseif ( is_page() ) {
$parents = array_reverse( get_post_ancestors( get_queried_object_id() ) );
foreach ( $parents as $parent_id ) {
$items[] = array( 'label' => get_the_title( $parent_id ), 'url' => get_permalink( $parent_id ) );
}
$items[] = array( 'label' => get_the_title( get_queried_object_id() ), 'url' => '' );
} elseif ( is_singular() ) {
// カスタム投稿タイプの個別ページ
$post_type = get_post_type_object( get_post_type( get_queried_object_id() ) );
if ( $post_type && $post_type->has_archive ) {
$items[] = array( 'label' => $post_type->labels->name, 'url' => get_post_type_archive_link( $post_type->name ) );
}
$items[] = array( 'label' => get_the_title( get_queried_object_id() ), 'url' => '' );
} elseif ( is_category() || is_tag() || is_tax() ) {
$term = get_queried_object();
if ( is_taxonomy_hierarchical( $term->taxonomy ) ) {
$parents = array_reverse( get_ancestors( $term->term_id, $term->taxonomy, 'taxonomy' ) );
foreach ( $parents as $parent_id ) {
$parent = get_term( $parent_id, $term->taxonomy );
if ( ! $parent instanceof WP_Term ) {
continue;
}
$link = get_term_link( $parent );
if ( ! is_wp_error( $link ) ) {
$items[] = array( 'label' => $parent->name, 'url' => $link );
}
}
}
$items[] = array( 'label' => $term->name, 'url' => '' );
} elseif ( is_post_type_archive() ) {
$items[] = array( 'label' => post_type_archive_title( '', false ), 'url' => '' );
} elseif ( is_search() ) {
$items[] = array( 'label' => sprintf( '「%s」の検索結果', get_search_query( false ) ), 'url' => '' );
} elseif ( is_404() ) {
$items[] = array( 'label' => 'ページが見つかりません', 'url' => '' );
} elseif ( is_archive() ) {
// 投稿者・日付アーカイブなど
$items[] = array( 'label' => wp_strip_all_tags( get_the_archive_title() ), 'url' => '' );
}
return $items;
}
組み立てで押さえておきたい点は次のとおりです。
- 親の並び順: get_ancestors() と get_post_ancestors() は「近い親から順」に返すため、array_reverse() で「最上位の親から順」に並べ替えています。
- 投稿が複数カテゴリーに属する場合: 上の例では get_the_category() の最初の1件を使っています。どのカテゴリーが先頭になるかは並び順の指定に左右されるため、「主カテゴリー」を決めたい場合は、カスタムフィールドに主カテゴリーのIDを保存して優先する、といった独自のルールを足します。
- 条件分岐の順番: is_singular() はすべての個別ページで真になるため、投稿・固定ページの判定より後に置いています。同様に is_archive() はカテゴリーや投稿タイプのアーカイブでも真になるので、最後に回しています。
手順2:HTMLとして出力する
次に、配列をHTMLに変換する関数を作ります。
<?php
function mytheme_breadcrumb() {
$items = mytheme_breadcrumb_items();
if ( count( $items ) < 2 ) {
return '';
}
$last = count( $items ) - 1;
$html = '<nav class="breadcrumb" aria-label="パンくずリスト"><ol class="breadcrumb__list">';
foreach ( $items as $i => $item ) {
if ( $i === $last || '' === $item['url'] ) {
$current = ( $i === $last ) ? ' aria-current="page"' : '';
$html .= '<li class="breadcrumb__item"><span' . $current . '>' . esc_html( $item['label'] ) . '</span></li>';
} else {
$html .= '<li class="breadcrumb__item"><a href="' . esc_url( $item['url'] ) . '">' . esc_html( $item['label'] ) . '</a></li>';
}
}
$html .= '</ol></nav>';
return $html;
}
add_shortcode( 'mytheme_breadcrumb', 'mytheme_breadcrumb' );
マークアップは nav 要素に aria-label を付け、順序に意味があるので ol(番号付きリスト)を使います。現在のページはリンクにせず、aria-current="page" を付けておくと、スクリーンリーダーでも現在地が伝わります。ラベルとURLは出力の直前で esc_html() と esc_url() を通しています。検索キーワードのように訪問者が入力した値もラベルに入るため、エスケープは省略しないでください。
区切り記号はHTMLに書かず、CSSで付けると管理が楽です。
.breadcrumb__list {
display: flex;
flex-wrap: wrap;
margin: 0 0 1.5em;
padding: 0;
list-style: none;
font-size: 13px;
}
.breadcrumb__item + .breadcrumb__item::before {
content: "›";
margin: 0 0.5em;
color: #888;
}
手順3:テーマに組み込む
クラシックテーマでは、親テーマの header.php を子テーマにコピーし、ヘッダーの直後など表示したい位置に次の1行を追加します。特定のテンプレート(single.php など)だけに出したい場合は、そのファイルをコピーして書きます。
<?php echo mytheme_breadcrumb(); ?>
ブロックテーマでは、サイトエディターで表示したいテンプレート(「単一投稿」「ページ」「アーカイブ」など)を開き、「ショートコード」ブロックを置いて [mytheme_breadcrumb] と入力します。複数のテンプレートに共通で出したいなら、ヘッダーのテンプレートパーツに入れる方法もあります。トップページでは関数が空を返すので、ヘッダーに置いても余計な表示は出ません。
構造化データ(BreadcrumbList)を付ける場合
検索結果にパンくずを表示させたい場合は、schema.org の BreadcrumbList を JSON-LD で出力します。手順1の配列をそのまま使えます。
<?php
add_action( 'wp_head', function () {
$items = mytheme_breadcrumb_items();
if ( count( $items ) < 2 ) {
return;
}
$list = array();
foreach ( $items as $i => $item ) {
$element = array(
'@type' => 'ListItem',
'position' => $i + 1,
// タイトルに含まれる文字参照(’ など)を元の文字に戻す
'name' => html_entity_decode( wp_strip_all_tags( $item['label'] ), ENT_QUOTES, get_bloginfo( 'charset' ) ),
);
if ( '' !== $item['url'] ) {
$element['item'] = $item['url'];
}
$list[] = $element;
}
$data = array(
'@context' => 'https://schema.org',
'@type' => 'BreadcrumbList',
'itemListElement' => $list,
);
echo '<script type="application/ld+json">' . wp_json_encode( $data ) . '</script>' . "\n";
} );
注意したいのは重複です。Yoast SEO や Rank Math などのSEOプラグインは、それ自体が BreadcrumbList を含む構造化データを出力する場合があります。同じページに内容の異なる BreadcrumbList が2つあると、検索エンジンがどちらを採用するか分かりにくくなるため、プラグイン側の出力を確認し、どちらか一方にまとめてください。
動作確認のポイント
- 投稿・固定ページ(親子関係あり)・カテゴリー(親子関係あり)・タグ・検索結果・存在しないURLを開き、それぞれ想定どおりの階層が出るかを確認します。
- 構造化データを出力した場合は、Google のリッチリザルトテストで、BreadcrumbList がエラーなく認識されるかを確かめます(2026年10月現在のツール名です)。
- ページキャッシュを使っている場合は、変更後にキャッシュを削除してからログアウト状態で確認します。
- 表示が崩れたり画面が真っ白になったりした場合は、WP_DEBUG_LOG を有効にして wp-content/debug.log を確認し、必要ならバックアップした functions.php に戻します。
まとめ
- パンくずリストは「項目の配列を作る関数」と「HTMLに変換する関数」に分けると、表示と構造化データの両方に使い回せます。
- 親の取得には get_ancestors() と get_post_ancestors() を使い、array_reverse() で上位から並べます。条件分岐は範囲の狭いものから順に書きます。
- 出力は nav と ol でマークアップし、現在地に aria-current="page" を付け、ラベルとURLは出力時にエスケープします。
- クラシックテーマは header.php などに1行追加、ブロックテーマはショートコードブロックで組み込みます。SEOプラグインと構造化データが重複しないようにしてください。