閉じる

技術ブログ

【WordPress】カテゴリごとに最新5件を並べ、「もっと見る」で続きを読み込む ── ブログのトップ・カテゴリページを作ってみた

2026.11.04

WordPress 実践シリーズ #11
カテゴリごとに最新5件を並べ、
「もっと見る」で続きを読み込む
ブログのトップに親カテゴリごと、カテゴリページに子カテゴリごとの一覧を出し、
ボタンを押すとページを移らずに5件ずつ追加する仕組みを、クラシックテーマで作って動かしました

「ブログのトップにカテゴリごとの最新記事を5件ずつ並べ、もっと見るを押したら続きをその場で読み込みたい」──WordPress の標準のテンプレートには、この形の一覧はありません。
本記事では、この一覧をテーマに部品を4つ足すだけで作り、WordPress 7.1.4 の検証環境(クラシックテーマ)で動かしました。押し切ったときに抜けや重複が出ないかを、正解の並びと1件ずつ突き合わせて確かめています。

✅ 作り方の要点
① 最初の5件は PHP(WP_Query)で出す。カテゴリを cat で指定すると子カテゴリの記事も含まれる(→ 4章)
② 続きは REST API の独自の窓口から、同じテンプレートで作った HTML を受け取って足す(→ 4章・5章)
③ もっと見るはふつうのリンクにしておく。JavaScript が動かなくてもカテゴリページへ移れる(→ 5章)

1. 結論

ご要望 答え
ブログのトップにカテゴリ一覧、カテゴリページに子カテゴリ一覧を出したい できます。get_categories() の parent に、トップでは 0(親カテゴリ)、カテゴリページでは今のカテゴリの ID(その子カテゴリ)を渡します。同じ関数1つで両方に使えます。
カテゴリごとに最新5件を出したい できます。カテゴリごとに WP_Query で5件取ります。cat で指定すると、親カテゴリの欄には子カテゴリの記事も入ります。
もっと見るで追加表示したい(非同期) できます。REST API に「カテゴリ ID と何件目から」を渡すと、続きの5件の HTML を返す窓口を作り、JavaScript で一覧の末尾に足します。押し切ったときの並びが、正解(日付の新しい順の全件)と1件も違わないことを確かめました。
ブログのトップ。上にカテゴリのボタン、下に「お知らせ」「コラム」「採用」の欄があり、各欄に最新5件と「もっと見る」ボタンが並ぶ
ブログのトップ。親カテゴリごとに最新5件が並び、子カテゴリへのリンクも出ます(見た目は見本の CSS)

2. 仕組みとファイルの構成

[ページを開いたとき] PHP で、カテゴリごとに最新5件を出す
   │  5件より多いカテゴリだけ「もっと見る」を付ける(中身はカテゴリページへのリンク)
   ▼
[もっと見るを押す] JavaScript がリンクの移動を止め、API に問い合わせる
   │  GET /wp-json/bit/v1/category-posts?cat=3&offset=5
   ▼
[サーバ] 同じ条件で6件目から5件取り、最初の5件と同じテンプレートで HTML にして返す
   │  { "html": "<li>…", "next_offset": 10, "has_more": true }
   ▼
[画面] 一覧の末尾に足す。もう無ければボタンを消す
ファイル 役割
template-parts/content/post-item.php 記事1件分の HTML。最初の5件と、あとから足す分の両方で使う
inc/jnm-function-category-sections.php 一覧を出す関数 bit_category_sections() と、続きを返す API
js/category-more.js もっと見るを押したときの処理
css/category-sections.css 見た目の見本
home.php・category.php・functions.php 呼び出しを足す(6章)

3. 記事1件分のテンプレート

日付・子カテゴリ名・タイトル(35文字で切る)を出します。WordPress はタイトルの & や引用符を &#038; のような書き方に置き換えて返すので、ふつうの文字に戻してから35文字で切ります(そのまま切ると、書き方の途中で切れて「Q&A “まとめ” と &#8216…」のように化けました)。最初の5件も、あとから足す5件も、このファイル1つで作るのがポイントです。JavaScript 側で HTML を組み立てないので、見た目を変えるときに直す場所が1か所で済みます。

<?php
/**
 * 記事1件分(カテゴリ別一覧の最初の5件と、MORE で足す分の両方で使う)
 * category.php の「ここから記事」と同じ見た目
 */
?>
<li class="catSection__item">
  <dl>
    <dd class="date"><?php echo esc_html( get_post_time( 'Y.m.d' ) ); ?></dd>
    <?php foreach ( get_the_category() as $cat ) : ?>
      <?php if ( $cat->parent ) : ?>
        <span class="category"><?php echo esc_html( $cat->name ); ?></span>
      <?php endif; ?>
    <?php endforeach; ?>
    <dt>
      <a href="<?php the_permalink(); ?>">
        <?php
        // get_the_title() は & や引用符を &#038; &#8220; などに置き換えて返すので、ふつうの文字に戻してから切る
        $title = html_entity_decode( get_the_title(), ENT_QUOTES | ENT_HTML5, 'UTF-8' );
        echo esc_html( mb_strlen( $title, 'UTF-8' ) > 35 ? mb_substr( $title, 0, 35, 'UTF-8' ) . '…' : $title );
        ?>
      </a>
    </dt>
  </dl>
</li>

4. 一覧を出す関数と、続きを返す API

<?php
/**
 * カテゴリごとに最新5件を並べ、「もっと見る」で5件ずつ追加する(非同期)
 *
 * 使い方:
 *   ブログのトップ(home.php)   … bit_category_sections( 0 );                         → 親カテゴリごと
 *   カテゴリページ(category.php)… bit_category_sections( get_queried_object_id() );  → 子カテゴリごと
 */

define( 'BIT_CAT_PER_PAGE', 5 );

/**
 * 1カテゴリ分の記事を取る(子カテゴリの記事も含む)
 */
function bit_category_posts_query( $cat_id, $offset ) {
    return new WP_Query( array(
        'post_type'           => 'post',
        'post_status'         => 'publish',
        'cat'                 => $cat_id,      // 子カテゴリの記事も含む
        'posts_per_page'      => BIT_CAT_PER_PAGE,
        'offset'              => $offset,
        'orderby'             => 'date',
        'order'               => 'DESC',
        'ignore_sticky_posts' => true,
    ) );
}

/**
 * 記事の <li> を並べた HTML を返す
 */
function bit_category_posts_html( WP_Query $q ) {
    ob_start();
    while ( $q->have_posts() ) {
        $q->the_post();
        get_template_part( 'template-parts/content/post-item' );
    }
    wp_reset_postdata();
    return ob_get_clean();
}

/**
 * カテゴリの一覧と、カテゴリごとの最新5件を出す
 * 出したら true、出す欄が無ければ false を返す
 */
function bit_category_sections( $parent_id = 0 ) {
    $cats = get_categories( array(
        'parent'     => $parent_id,
        'hide_empty' => false,
        'exclude'    => array( get_option( 'default_category' ) ), // 未分類は出さない
    ) );
    // 先に各カテゴリの最新5件を取り、公開記事があるカテゴリだけを残す(一覧と欄をそろえる)
    $sections = array();
    foreach ( $cats as $cat ) {
        $q = bit_category_posts_query( $cat->term_id, 0 );
        if ( $q->have_posts() ) {
            $sections[] = array( $cat, $q );
        }
    }
    if ( ! $sections ) {
        return false;
    }
    wp_enqueue_script( 'bit-category-more' );
    wp_enqueue_style( 'bit-category-sections' );

    // カテゴリ一覧(ページ内リンク)
    echo '<nav class="catNav"><ul>';
    foreach ( $sections as list( $cat ) ) {
        // 飛び先はカテゴリの番号で作る(日本語名のカテゴリはスラッグが %e6… になり、テーマのスクロール処理が読めない)
        printf( '<li><a href="#cat-%d">%s</a></li>', (int) $cat->term_id, esc_html( $cat->name ) );
    }
    echo '</ul></nav>';

    foreach ( $sections as list( $cat, $q ) ) {
        $children = get_categories( array( 'parent' => $cat->term_id ) );
        ?>
        <section class="catSection" id="cat-<?php echo (int) $cat->term_id; ?>">
            <h3 class="catSection__title"><a href="<?php echo esc_url( get_category_link( $cat ) ); ?>"><?php echo esc_html( $cat->name ); ?></a></h3>
            <?php if ( $children ) : ?>
                <ul class="catSection__children">
                    <?php foreach ( $children as $child ) : ?>
                        <li><a href="<?php echo esc_url( get_category_link( $child ) ); ?>"><?php echo esc_html( $child->name ); ?></a></li>
                    <?php endforeach; ?>
                </ul>
            <?php endif; ?>
            <ul class="catSection__list" aria-live="polite">
                <?php echo bit_category_posts_html( $q ); // phpcs:ignore -- post-item.php で個別にエスケープ済み ?>
            </ul>
            <?php if ( $q->found_posts > BIT_CAT_PER_PAGE ) : ?>
                <?php // JavaScript が動かないときはカテゴリページへのふつうのリンクとして働く ?>
                <a class="catSection__more" href="<?php echo esc_url( get_category_link( $cat ) ); ?>"
                    data-cat="<?php echo (int) $cat->term_id; ?>" data-offset="<?php echo (int) BIT_CAT_PER_PAGE; ?>">もっと見る</a>
            <?php endif; ?>
        </section>
        <?php
    }
    return true;
}

/**
 * 追加分を返す API: GET /wp-json/bit/v1/category-posts?cat=3&offset=5
 */
add_action( 'rest_api_init', function () {
    register_rest_route( 'bit/v1', '/category-posts', array(
        'methods'             => 'GET',
        'permission_callback' => '__return_true', // 公開記事だけを返すので誰でも可
        'args'                => array(
            'cat'    => array( 'required' => true, 'type' => 'integer', 'minimum' => 1 ),
            'offset' => array( 'required' => true, 'type' => 'integer', 'minimum' => 0, 'maximum' => 10000 ),
        ),
        'callback'            => function ( WP_REST_Request $req ) {
            $cat = get_category( (int) $req['cat'] );
            if ( ! $cat || is_wp_error( $cat ) ) {
                return new WP_Error( 'bit_no_category', 'カテゴリがありません。', array( 'status' => 404 ) );
            }
            $offset = (int) $req['offset'];
            $q      = bit_category_posts_query( $cat->term_id, $offset );
            $next   = $offset + $q->post_count;
            return array(
                'html'        => bit_category_posts_html( $q ),
                'next_offset' => $next,
                'has_more'    => $next < $q->found_posts,
            );
        },
    ) );
} );

add_action( 'wp_enqueue_scripts', function () {
    wp_register_script(
        'bit-category-more',
        get_stylesheet_directory_uri() . '/js/category-more.js',
        array(),
        '1.1.0',
        array( 'in_footer' => true, 'strategy' => 'defer' )
    );
    wp_register_style( 'bit-category-sections', get_stylesheet_directory_uri() . '/css/category-sections.css', array(), '1.0.0' );
    if ( is_home() || is_category() ) {
        wp_enqueue_style( 'bit-category-sections' ); // <head> で読む(画面のちらつきを防ぐ)
    }
    wp_localize_script( 'bit-category-more', 'bitCategoryMore', array(
        'endpoint' => esc_url_raw( rest_url( 'bit/v1/category-posts' ) ),
    ) );
} );

4-1. 子カテゴリの記事も含める

WP_Query の cat にカテゴリ ID を渡すと、子カテゴリの記事も含めて取れます。親カテゴリ「お知らせ」に記事を直接付けていなくても、子の「イベント」「メディア掲載」の記事が新しい順に並びます。親カテゴリの記事だけにしたいときは、cat の代わりに category__in を使います。

4-2. 公開記事のあるカテゴリだけを出す

先に各カテゴリの最新5件を取り、公開記事が1件以上あるカテゴリだけで、上のカテゴリ一覧と欄の両方を作ります。こうしないと、記事の無いカテゴリ(下書きしか無いカテゴリを含む)が一覧にだけ出て、押しても行き先がありません。

4-3. カテゴリ一覧の飛び先はカテゴリの番号で作る

上のカテゴリ一覧(ページ内リンク)の飛び先は、cat-3 のようにカテゴリの番号で作ります。カテゴリ名を日本語にするとスラッグが %e6%97%a5… のような文字列になり、それを飛び先の名前に使うと、bit-template のテーマのスクロール処理(jQuery)がエラーになって画面が動きませんでした(実測)。

4-4. 「もっと見る」を出すかどうか

$q->found_posts(条件に合う全件数)が5件を超えるときだけボタンを出します。API も「次は何件目から(next_offset)」と「まだあるか(has_more)」を返すので、最後まで読み込んだらボタンが消えます。

4-5. API の入口で値を確かめる

register_rest_route() の args に型と範囲を書いておくと、WordPress が受け取る前に確かめてくれます。今回の実測では、文字・負の数・大きすぎる値・offset の付け忘れはすべて 400、ないカテゴリは 404 になりました。取るのは公開済みの記事だけ(post_status を publish に固定)なので、誰でも呼べる窓口にしても下書きや非公開の記事は漏れません(管理者でログインした状態でも混ざらないことを確かめました)。

5. 「もっと見る」の JavaScript

/*
 * カテゴリ別一覧の「もっと見る」: 5件ずつ取ってきて一覧の末尾に足す
 * ボタンはふつうのリンク(カテゴリページ行き)。JavaScript が動けば、ページを移らずに追加する。
 */
document.addEventListener('click', function (e) {
    var btn = e.target.closest('.catSection__more');
    if (!btn) return;
    e.preventDefault();
    if (btn.getAttribute('aria-busy') === 'true') return; // 連打で同じ記事が二重に入らないように

    var list = btn.closest('.catSection').querySelector('.catSection__list');
    var url = new URL(window.bitCategoryMore.endpoint, location.href);
    url.searchParams.set('cat', btn.dataset.cat);
    url.searchParams.set('offset', btn.dataset.offset);

    btn.setAttribute('aria-busy', 'true');
    btn.textContent = '読み込み中…';

    fetch(url, { headers: { Accept: 'application/json' } })
        .then(function (res) {
            if (!res.ok) throw new Error(res.status);
            return res.json();
        })
        .then(function (data) {
            list.insertAdjacentHTML('beforeend', data.html); // サーバ側(post-item.php)でエスケープ済みの HTML
            btn.dataset.offset = data.next_offset;
            if (data.has_more) {
                btn.textContent = 'もっと見る';
                btn.removeAttribute('aria-busy');
            } else {
                btn.remove(); // もう無い
            }
        })
        .catch(function () {
            // 読み込めなかったら(API が止められている環境など)、リンク本来の行き先=カテゴリページへ移る
            btn.textContent = 'カテゴリのページへ移ります…';
            location.href = btn.href;
        });
});
💡 ボタンを「ふつうのリンク」にしておく理由
もっと見るは <a href="カテゴリページ"> で出しています。JavaScript が動けばリンクの移動を止めてその場で読み込み、動かなければカテゴリページに移って続きを読めます。検索エンジンもこのリンクをたどれます。
💡 読み込めなかったときは、カテゴリページへ移る
REST API を止めるセキュリティプラグインを入れている場合などは、続きを読み込めません。そのときはリンク本来の行き先(カテゴリページ)へ移るので、閲覧者は続きを読めます(API にわざと失敗を返させて確かめました)。その場で読み込ませたいときは、プラグインの設定で bit(この記事の API の名前空間)を例外に足してください。ログイン中の人には REST API を許す設定のプラグインもあり、その場合は作った本人には動いて見えて、閲覧者だけ失敗するので、ログアウトした状態でも試してください。
💡 連打で同じ記事が二重に入らないように
読み込み中はボタンに aria-busy="true" を付け、その間のクリックは無視します。ダブルクリックで試しても、同じ記事が2回入らないことを確かめました。

6. テンプレートへの組み込みと CSS

トップでは 0(親カテゴリごと)、カテゴリページでは今のカテゴリの ID(子カテゴリごと)を渡します。子カテゴリが無いカテゴリページでは何も出さず、いつもの一覧だけが出ます。

カテゴリページでは、子カテゴリごとの欄の下に、もとからある「いつもの一覧」も出ます。上の欄に出た記事が下の一覧にも出るので、欄を出したときは下の一覧に「すべての記事」という見出しを付け、欄は1ページ目だけに出しています(2ページ目以降は、いつもの一覧だけ)。下の一覧を消さないのは、親カテゴリに直接付けた記事(子カテゴリに属さない記事)がそこにしか出ないためです。

--- a/home.php
+++ b/home.php
@@ -28,6 +28,7 @@
       <div class="innerBasic">
         <h3>MainContents</h3>
         <section class="contSubBox">
+          <?php bit_category_sections( 0 ); // 親カテゴリごとに最新5件+もっと見る ?>
         </section>
       </div>
     </section>
--- a/category.php
+++ b/category.php
@@ -29,6 +29,13 @@
           <h3><?php echo esc_html($obj_cat->name); ?></h3>
         <?php } ?>
 
+        <?php
+        // 子カテゴリごとに最新5件+もっと見る(1ページ目だけ。子が無ければ何も出さない)
+        // 欄を出したときは、下のふつうの一覧に「すべての記事」と見出しを付ける(親カテゴリに直接付けた記事もここに出る)
+        if ( ! is_paged() && bit_category_sections( $obj_cat->term_id ) ) : ?>
+          <h3 class="catAll__title">すべての記事</h3>
+        <?php endif; ?>
+
         <?php if (have_posts()) : ?>
           <?php while (have_posts()) : the_post(); ?>
 
--- a/functions.php
+++ b/functions.php
@@ -94,6 +94,11 @@
   include get_stylesheet_directory() . '/inc/jnm-settings-othres.php';
 }
 
+/* jnm-function-category-sectionsはカテゴリごとの最新5件+もっと見る(非同期) */
+if (file_exists(get_stylesheet_directory() . '/inc/jnm-function-category-sections.php')) {
+  include get_stylesheet_directory() . '/inc/jnm-function-category-sections.php';
+}
+
 /* 管理画面でGoogleMapで使用する座標取得を行わせるための設定 */
 // if (file_exists(get_stylesheet_directory() . '/inc/jnm-settings-admin-googlemap.php')) {
 //   include get_stylesheet_directory() . '/inc/jnm-settings-admin-googlemap.php';

見た目の見本です。サイトのデザインに合わせて変えてください。

/* カテゴリ別一覧+もっと見る(見本。サイトの見た目に合わせて変える) */
.catNav ul { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 32px; padding: 0; list-style: none; }
.catNav a { display: block; padding: 6px 16px; border: 1px solid #1a365d; border-radius: 999px; color: #1a365d; text-decoration: none; }
.catNav a:hover { background: #1a365d; color: #fff; }

.catSection { margin: 0 0 48px; }
.catSection__title { margin: 0 0 8px; padding-bottom: 8px; border-bottom: 2px solid #1a365d; font-size: 1.3em; }
.catSection__title a { color: inherit; text-decoration: none; }
.catSection__children { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 0 0 12px; padding: 0; list-style: none; font-size: .9em; }
.catSection__children a::before { content: "› "; }

.catSection__list { margin: 0; padding: 0; list-style: none; }
.catSection__item dl { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; margin: 0; padding: 12px 0; border-bottom: 1px solid #e2e8f0; }
.catSection__item .date { margin: 0; color: #666; font-size: .9em; }
.catSection__item .category { padding: 0 8px; border-radius: 3px; background: #ebf4ff; color: #2a4365; font-size: .8em; }
.catSection__item dt { flex-basis: 100%; }

.catSection__more { display: block; width: 220px; margin: 20px auto 0; padding: 10px; border: 1px solid #1a365d; border-radius: 4px; color: #1a365d; text-align: center; text-decoration: none; }
.catSection__more:hover { background: #1a365d; color: #fff; }
.catSection__more[aria-busy="true"] { opacity: .6; pointer-events: none; }
「お知らせ」の欄で、もっと見るを1回押して10件に増えた状態
「もっと見る」を1回押したところ。ページを移らずに5件増えて10件になります

7. WordPress 標準の API で作る方法との違い

独自の窓口を作らずに、WordPress に最初からある記事の API(/wp-json/wp/v2/posts)を使う方法もあります。

独自の窓口(本記事) 標準の記事 API
子カテゴリの記事 cat 指定で含まれる categories=2 だけでは含まれない(実測で0件)。categories[terms]=2&categories[include_children]=true と書けば含まれる(実測で16件)
返ってくるもの そのまま足せる HTML 記事のデータ(JSON)。HTML は JavaScript で組み立てる
見た目を変えるとき post-item.php だけ直す PHP(最初の5件)と JavaScript(追加分)の2か所を直す
カテゴリ名などの付帯情報 テンプレートの中で自由に取れる 別に問い合わせるか、_embed などで含める必要がある

見た目を1か所で管理できるので、本記事では独自の窓口にしました。標準の API で作る場合は、タイトルなどを画面に入れるときに innerHTML ではなく textContent を使うなど、JavaScript 側で文字を安全に扱う必要があります。

8. 実測結果

試験用に、親カテゴリ6つ(子カテゴリあり2つ・日本語名1つ・下書きしか無いもの1つ・記事0件のもの1つ)と子カテゴリ4つ、記事48件(公開44件)を作りました。記事数が5件より少ない・ちょうど5件・多いカテゴリ、2つのカテゴリに属する記事、タイトルに & や引用符を入れた記事、出てはいけない記事(下書き・非公開・予約投稿)を混ぜています。正解の並びは wp-cli で別に出し、画面で「もっと見る」を押し切った結果と1件ずつ突き合わせました。

ページ・欄 正解の件数 最初 押すたびの件数 結果
トップ「お知らせ」(子カテゴリ2つ) 17 5 10 → 15 → 17(ボタン消える) ✅ 正解と一致・重複なし
トップ「コラム」(子カテゴリ2つ) 14 5 10 → 14(ボタン消える) ✅ 正解と一致・重複なし
トップ「採用」(子カテゴリなし) 8 5 8(ボタン消える) ✅ 正解と一致・重複なし
トップ「日本語のカテゴリ」 6 5 6(ボタン消える) ✅ 正解と一致・重複なし
カテゴリページ「お知らせ」→「イベント」欄(ダブルクリックで操作) 14 5 10 → 14(ボタン消える) ✅ 正解と一致・重複なし
カテゴリページ「お知らせ」→「メディア掲載」欄 3 3 ボタンなし ✅
カテゴリページ「コラム」→「デザイン」欄(ちょうど5件) 5 5 ボタンなし ✅
子カテゴリが無いカテゴリページ カテゴリ別の欄は出ず、いつもの一覧だけ ✅
確かめたこと 結果
下書き・非公開・予約投稿が出ない(画面・API、管理者でログインした状態の API も) ✅ 出ない
タイトルに <b> を入れた記事 ✅ 文字のまま表示
タイトルに &・引用符を入れた記事(35文字切りを含む) ✅ 化けずに表示
日本語名のカテゴリを含め、上のカテゴリ一覧の全リンクを押す ✅ それぞれの欄へ動く・エラーなし
下書きしか無いカテゴリ・記事0件のカテゴリ ✅ 一覧にも欄にも出ない(一覧と欄が一致)
カテゴリページの2ページ目 ✅ 欄と「すべての記事」の見出しは出ず、いつもの一覧だけ
API が失敗したときに「もっと見る」 ✅ カテゴリページへ移り、記事が表示される
JavaScript が動かない状態で「もっと見る」 ✅ カテゴリページへ移り、記事が表示される
API に、ないカテゴリ ✅ 404
API に、文字・負の数・大きすぎる値・offset なし ✅ 400

9. 作るときの注意点

① 読み込みの合間に記事が増えたり減ったりすると、重複や抜けが出る
続きは「何件目から」で取っています。一覧を開いてから「もっと見る」を押すまでの間に、
・記事が増える(新しく公開される)と、全体が後ろにずれて直前に出ていた記事がもう一度出ます(実測:1件公開してから押すと、5件目の記事が追加分の先頭にもう一度出ました)
・記事が減る(下書きに戻す・非公開にする・削除する・カテゴリを変える)と、全体が前にずれてまだ出ていない記事が1件飛ばされます(実測:表示中の3件目を下書きに戻してから押すと、6件目が出ませんでした)
ふだんは気にならない程度ですが、記事の公開や修正が頻繁なサイトでは気を付けてください。「最後に出した記事の日時と番号」を目印にして続きを取る作りにすると防げる見込みで、いま検証しています(結果が出たらこの記事に追記します)。
② 2つのカテゴリに属する記事は、両方の欄に出る
「イベント」と「WordPress」の両方に付けた記事は、「お知らせ」と「コラム」の両方の欄に出ます(実測)。1つの欄にだけ出したい場合は、記事に付けるカテゴリを1つにする運用にしてください。
③ 記事が1件もないカテゴリと「未分類」は出さない
空の欄が並ばないように、公開記事が無いカテゴリ(下書きしか無いカテゴリを含む)は、上のカテゴリ一覧にも欄にも出していません。「未分類」(既定のカテゴリ)も一覧から外しています。出したい場合は bit_category_sections() の該当箇所を外してください。
④ bit-template では、カテゴリを足したらパーマリンクを保存し直す
bit-template のテーマは、カテゴリごとに URL の規則を作ります。検証では、カテゴリを足した直後はそのカテゴリページが 404 になり、パーマリンクを作り直すと表示されました。「もっと見る」の行き先(JavaScript が動かないとき)もカテゴリページなので、カテゴリを足したら管理画面の「設定 → パーマリンク」で「変更を保存」を押してください。

10. まとめ(ご質問への回答)

Q. ブログのトップ/カテゴリページに、カテゴリ一覧/子カテゴリ一覧を出し、カテゴリごとに5件表示、MORE ボタンで非同期に追加表示したい
できます。テーマに部品を4つ(記事1件分のテンプレート・一覧の関数と API・JavaScript・CSS)足し、home.php・category.php・functions.php に呼び出しを書きます。トップでは親カテゴリごと、カテゴリページでは子カテゴリごとに最新5件が並び、「もっと見る」を押すとページを移らずに5件ずつ増えます。親カテゴリの欄には子カテゴリの記事も入ります。最初の5件も追加分も同じテンプレートで作るので、見た目を変えるときに直すのは1か所です(2章〜6章)。

参考(一次ソース)

※本記事の動作は、WordPress 7.1.4(日本語版)/PHP 8.3/MySQL 8.0 の検証環境で、bit-template のテーマ(クラシックテーマ)に部品を足して、2026年11月4日に確認しました。

作成: Seeds Brains(ジェイノーム業務支援AI)