2026年5月29日

2026年5月29日

WordPressに関連記事を表示する方法

はじめに

「投稿の下に関連記事を表示してユーザーの回遊率を上げたい」「プラグインを使わずに軽量な関連記事を実装したい」「タグとカテゴリー両方で関連度を判定したい」——関連記事はユーザーの滞在時間と内部リンクSEOに直結する重要な要素です。

症状・原因

デフォルトのWordPressには関連記事機能がないため、WP_Queryget_posts()を使って同一タグ・カテゴリーの投稿を取得する必要があります。プラグインは手軽ですが重くなりがちで、自前実装なら表示ロジックとデザインを完全制御できます。

解決手順

ステップ1:タグベースの関連記事を取得する

// functions.php: タグベースの関連記事取得関数
function get_related_posts( $post_id = null, $count = 4 ) {
    $post_id = $post_id ?: get_the_ID();

    // 現在の投稿のタグを取得
    $tags = wp_get_post_tags( $post_id, [ 'fields' => 'ids' ] );

    if ( empty( $tags ) ) {
        // タグがない場合はカテゴリーでフォールバック
        $cats = wp_get_post_categories( $post_id );
        if ( empty( $cats ) ) {
            return [];
        }
        $tax_query = [ [
            'taxonomy' => 'category',
            'field'    => 'term_id',
            'terms'    => $cats,
        ] ];
    } else {
        $tax_query = [ [
            'taxonomy' => 'post_tag',
            'field'    => 'term_id',
            'terms'    => $tags,
        ] ];
    }

    $related = get_posts( [
        'post__not_in'   => [ $post_id ],   // 現在の投稿を除外
        'posts_per_page' => $count,
        'orderby'        => 'rand',          // ランダム順(キャッシュ利用時は date 推奨)
        'tax_query'      => $tax_query,
        'post_status'    => 'publish',
        'no_found_rows'  => true,            // ページネーション不要なのでカウント省略
    ] );

    return $related;
}

ステップ2:タグ+カテゴリーで関連度スコアを付けて取得する

// functions.php: スコアベースの関連記事(より精度が高い)
function get_scored_related_posts( $post_id = null, $count = 4 ) {
    $post_id = $post_id ?: get_the_ID();
    $tags    = wp_get_post_tags( $post_id, [ 'fields' => 'ids' ] );
    $cats    = wp_get_post_categories( $post_id );

    $tax_query = [ 'relation' => 'OR' ];

    if ( ! empty( $tags ) ) {
        $tax_query[] = [
            'taxonomy' => 'post_tag',
            'field'    => 'term_id',
            'terms'    => $tags,
        ];
    }

    if ( ! empty( $cats ) ) {
        $tax_query[] = [
            'taxonomy' => 'category',
            'field'    => 'term_id',
            'terms'    => $cats,
        ];
    }

    if ( count( $tax_query ) === 1 ) {
        return []; // タグもカテゴリーもない場合
    }

    $posts = get_posts( [
        'post__not_in'   => [ $post_id ],
        'posts_per_page' => $count * 3, // 多めに取得してスコアでフィルタ
        'orderby'        => 'date',
        'tax_query'      => $tax_query,
        'post_status'    => 'publish',
        'no_found_rows'  => true,
    ] );

    // スコアリング:共通タグ数 × 2 + 共通カテゴリー数
    $scored = [];
    foreach ( $posts as $post ) {
        $post_tags = wp_get_post_tags( $post->ID, [ 'fields' => 'ids' ] );
        $post_cats = wp_get_post_categories( $post->ID );

        $tag_overlap = count( array_intersect( $tags, $post_tags ) );
        $cat_overlap = count( array_intersect( $cats, $post_cats ) );
        $score       = $tag_overlap * 2 + $cat_overlap;

        if ( $score > 0 ) {
            $scored[] = [ 'post' => $post, 'score' => $score ];
        }
    }

    // スコア順にソート
    usort( $scored, fn( $a, $b ) => $b['score'] - $a['score'] );

    return array_column( array_slice( $scored, 0, $count ), 'post' );
}

ステップ3:関連記事をテンプレートに表示する

<?php
// single.php: 関連記事セクション
$related_posts = get_related_posts( get_the_ID(), 4 );

if ( ! empty( $related_posts ) ) : ?>
    <section class="related-posts" aria-label="関連記事">
        <h2 class="related-posts__title">関連記事</h2>
        <div class="related-posts__grid">
            <?php foreach ( $related_posts as $related ) : ?>
                <article class="related-card">
                    <?php if ( has_post_thumbnail( $related->ID ) ) : ?>
                        <a class="related-card__thumb" href="<?php echo esc_url( get_permalink( $related->ID ) ); ?>">
                            <?php echo get_the_post_thumbnail( $related->ID, 'medium', [
                                'loading' => 'lazy',
                                'alt'     => esc_attr( get_the_title( $related->ID ) ),
                            ] ); ?>
                        </a>
                    <?php endif; ?>

                    <div class="related-card__body">
                        <?php
                        $cats = get_the_category( $related->ID );
                        if ( $cats ) : ?>
                            <a class="related-card__cat" href="<?php echo esc_url( get_category_link( $cats[0]->term_id ) ); ?>">
                                <?php echo esc_html( $cats[0]->name ); ?>
                            </a>
                        <?php endif; ?>

                        <h3 class="related-card__title">
                            <a href="<?php echo esc_url( get_permalink( $related->ID ) ); ?>">
                                <?php echo esc_html( get_the_title( $related->ID ) ); ?>
                            </a>
                        </h3>

                        <time class="related-card__date" datetime="<?php echo esc_attr( get_the_date( 'c', $related->ID ) ); ?>">
                            <?php echo esc_html( get_the_date( '', $related->ID ) ); ?>
                        </time>
                    </div>
                </article>
            <?php endforeach; ?>
        </div>
    </section>
<?php endif; ?>

ステップ4:トランジェントキャッシュで高速化する

// functions.php: キャッシュ付き関連記事取得
function get_related_posts_cached( $post_id = null, $count = 4 ) {
    $post_id   = $post_id ?: get_the_ID();
    $cache_key = 'related_posts_' . $post_id . '_' . $count;
    $cached    = get_transient( $cache_key );

    if ( false !== $cached ) {
        return $cached;
    }

    $posts = get_related_posts( $post_id, $count );
    set_transient( $cache_key, $posts, 12 * HOUR_IN_SECONDS );

    return $posts;
}

// 投稿が更新されたらキャッシュをクリア
add_action( 'save_post', function( $post_id ) {
    if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
        return;
    }
    $count = 4;
    delete_transient( 'related_posts_' . $post_id . '_' . $count );
} );

ステップ5:CSSでカード型グリッドレイアウトを実装する

/* 関連記事セクション */
.related-posts {
    margin: 3rem 0;
    padding-top: 2rem;
    border-top: 2px solid #dcdcde;
}

.related-posts__title {
    font-size: 1.25rem;
    font-weight: 700;
    margin: 0 0 1.5rem;
    color: #1d2327;
}

.related-posts__grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1.25rem;
}

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

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

/* 関連記事カード */
.related-card {
    background: #fff;
    border-radius: 6px;
    overflow: hidden;
    box-shadow: 0 1px 4px rgba(0,0,0,0.08);
    transition: transform 0.2s, box-shadow 0.2s;
}

.related-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}

.related-card__thumb img {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    display: block;
}

.related-card__body {
    padding: 0.875rem;
}

.related-card__cat {
    display: inline-block;
    font-size: 0.7rem;
    font-weight: 600;
    color: #2271b1;
    text-decoration: none;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 0.4rem;
}

.related-card__title {
    font-size: 0.9rem;
    font-weight: 600;
    line-height: 1.5;
    margin: 0 0 0.5rem;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.related-card__title a {
    color: #1d2327;
    text-decoration: none;
}

.related-card__title a:hover {
    color: #2271b1;
}

.related-card__date {
    font-size: 0.75rem;
    color: #a7aaad;
}

注意事項

  • orderby => 'rand'はページが表示されるたびに異なる結果を返すため、キャッシュと組み合わせる場合はorderby => 'date'orderby => 'comment_count'など安定した順序に変更してください。
  • no_found_rows => trueを設定するとSQL_CALC_FOUND_ROWSが省略され、件数が不要な場面でのクエリが高速化されます。
  • 関連記事が多いサイトではJetpackの関連記事機能やYet Another Related Posts Plugin (YARPP)も選択肢ですが、自前実装の方がページスピードへの影響が少なく、デザインも自由に制御できます。

まとめ

関連記事の実装は「get_posts()でタグ・カテゴリーを条件にした関連投稿を取得→スコアリングで関連度を計算→single.phpにカード型グリッドで表示→トランジェントキャッシュで高速化→CSSのgrid + hover効果で仕上げ」の流れで整備します。関連記事:WordPressの抜粋文(excerpt)をカスタマイズする方法WordPressにソーシャルシェアボタンを設置する方法

お気軽にご相談ください

お見積りへ お問い合わせへ