2026年6月3日
2026年6月3日
WordPressのページネーションをカスタマイズする方法(paginate_links・カスタムクエリ・もっと見る)
WordPress のページネーション(ページ送り)は、投稿一覧を複数ページに分けたときに「1 2 3 … 次へ」のようなリンクを出す仕組みです。クラシックテーマでは the_posts_pagination() や paginate_links() で出力し、ブロックテーマでは「クエリーループ」ブロックの中の「ページネーション」ブロックで出します。
この記事のコードは、2026年11月時点の WordPress 7.1 と PHP 8.3 で、表示されるリンクと URL を確かめています。PHP のコードは子テーマの functions.php か独自プラグインに書きます。
表示件数を変える
1ページに出す投稿の数は、管理画面の「設定」→「表示設定」→「アーカイブページに表示する最新の投稿数」で変えます。ページ数はこの値と投稿数から決まります。
ページの種類ごとに件数を変えたいときは、pre_get_posts でメインクエリの posts_per_page を書き換えます。
<?php
// カテゴリーは12件、検索結果は20件ずつ表示する
add_action( 'pre_get_posts', function ( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
if ( $query->is_category() ) {
$query->set( 'posts_per_page', 12 );
}
if ( $query->is_search() ) {
$query->set( 'posts_per_page', 20 );
}
} );
is_main_query() で絞らないと、サイドバーなどほかのクエリの件数まで変わります。
投稿一覧(メインクエリ)にページ番号を出す
ブログのトップ、カテゴリー、タグ、検索結果などの一覧は、WordPress が URL から作る「メインクエリ」で表示されています。クラシックテーマのテンプレート(index.php・archive.php など)のループの後に、次のように書きます。
<?php
the_posts_pagination(
array(
'mid_size' => 2, // 現在のページの前後に出すページ番号の数
'prev_text' => '前のページ',
'next_text' => '次のページ',
'screen_reader_text' => '投稿のページ送り', // 見出しと aria-label に使われる
)
);
出力は <nav class="navigation pagination"> の中に、page-numbers クラスの付いたリンクが並ぶ形です。現在のページは <span aria-current="page" class="page-numbers current"> になるので、CSS はこれらのクラスに当てます。
- ページが1ページしかないときは何も出力しません。
the_posts_pagination()は文字列の HTML を出す関数なので、'type' => 'array'を渡しても無視され、通常の形で出力されます。- 番号を出さず「前へ」「次へ」だけにするなら
the_posts_navigation()を使います。
投稿ページで「前の記事」「次の記事」を出すのは the_post_navigation() です。%title が記事の題名に置き換わります。
<?php
the_post_navigation(
array(
'prev_text' => '<span class="nav-label">前の記事</span> %title',
'next_text' => '<span class="nav-label">次の記事</span> %title',
)
);
paginate_links()でマークアップを自由に組む
paginate_links() に 'type' => 'array' を渡すと、リンクを1つずつの文字列の配列で受け取れます。好きなタグで包めるので、デザインに合わせた HTML を作れます。'type' => 'list' にすると <ul class="page-numbers"> の形で返ります。
次の関数は、渡した WP_Query のページ番号リンクを <ul> にして返します。メインクエリにもカスタムクエリにも使えます。
<?php
/**
* WP_Query のページ番号リンクを ul で返す
*/
function myprefix_pagination( WP_Query $query ) {
$total = (int) $query->max_num_pages;
if ( $total <= 1 ) {
return '';
}
$links = paginate_links(
array(
'current' => max( 1, (int) $query->get( 'paged' ) ),
'total' => $total,
'mid_size' => 2, // 現在のページの前後に出す数
'end_size' => 1, // 先頭と末尾に出す数
'prev_text' => '前へ',
'next_text' => '次へ',
'type' => 'array',
)
);
if ( empty( $links ) ) {
return '';
}
$html = '<nav class="pagination" aria-label="ページ送り"><ul>';
foreach ( $links as $link ) {
$html .= '<li>' . $link . '</li>';
}
$html .= '</ul></nav>';
return $html;
}
テンプレートでは echo myprefix_pagination( $GLOBALS['wp_query'] ); のように呼びます。
base と format を省略すると、paginate_links() は今の URL から「/page/2/」の形のリンクを作ります。試したところ、固定ページ(例: /news-list/)でも静的フロントページでも、1ページ目へのリンクは /page/1/ ではなく元の URL になりました。パーマリンク設定が「基本」(?p=123 の形)のサイトでは、?paged=2 の形になります。
カスタムクエリ(WP_Query)にページネーションを付ける
固定ページのテンプレートなどで new WP_Query() を使って一覧を出す場合は、今のページ番号を paged に渡します。渡さないと、リンクは出ても何ページ目を開いても1ページ目の内容が表示されます。
注意点は、ページ番号が入るクエリ変数が場所によって違うことです。試したところ、次のようになりました。
| 表示しているページ | URL の例 | 番号が入る変数 |
|---|---|---|
| 通常の固定ページ | /news-list/page/2/ | paged |
| 静的フロントページ(「表示設定」でホームページに固定ページを指定) | /page/2/ | page |
| 投稿一覧・アーカイブ | /category/news/page/2/ | paged |
両方を見る関数を用意しておくと、どこで使っても動きます。
<?php
/**
* 今のページ番号を返す(静的フロントページでは 'page' に入る)
*/
function myprefix_get_paged() {
$paged = (int) get_query_var( 'paged' );
if ( 0 === $paged ) {
$paged = (int) get_query_var( 'page' );
}
return max( 1, $paged );
}
固定ページのテンプレートでの使い方です。
<?php
$news = new WP_Query(
array(
'post_type' => 'post',
'category_name' => 'news', // カテゴリーのスラッグ
'posts_per_page' => 5,
'paged' => myprefix_get_paged(),
)
);
if ( $news->have_posts() ) {
echo '<ul>';
while ( $news->have_posts() ) {
$news->the_post();
printf(
'<li><a href="%s">%s</a></li>',
esc_url( get_permalink() ),
esc_html( get_the_title() )
);
}
echo '</ul>';
echo myprefix_pagination( $news );
}
wp_reset_postdata();
the_posts_pagination() はメインクエリ(グローバルの $wp_query)のページ数しか見ないので、カスタムクエリには使えません。上のように max_num_pages を持つクエリを渡して paginate_links() で出します。
アーカイブでカスタムクエリを使うと404になる
カテゴリーなどのアーカイブのテンプレートで、メインクエリとは別に件数の違うカスタムクエリを作ると、後ろのほうのページが404になることがあります。WordPress は URL の /page/N/ をメインクエリに対して判定するため、メインクエリのページ数を超える番号は「存在しないページ」になるからです。試した環境(24件・1ページ10件で3ページ)でも、/page/3/ は表示され、/page/4/ は404でした。
アーカイブの件数や条件を変えたいときは、カスタムクエリを作らず、前の章の pre_get_posts でメインクエリを書き換え、the_posts_pagination() を使います。これでページ数と URL の判定が一致します。
「もっと見る」ボタンで続きを読み込む(Ajax)
ボタンを押すと次のページの投稿を同じページの下に足す方法です。admin-ajax.php を使います。
PHP(functions.php)
<?php
// スクリプトを読み込み、Ajax の URL とノンスを渡す(投稿一覧のトップだけ)
add_action( 'wp_enqueue_scripts', function () {
if ( ! is_home() ) {
return;
}
wp_enqueue_script(
'myprefix-load-more',
get_theme_file_uri( 'js/load-more.js' ),
array(),
'1.0',
array( 'strategy' => 'defer' )
);
wp_localize_script(
'myprefix-load-more',
'myprefixLoadMore',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'myprefix_load_more' ),
)
);
} );
// ログイン中(wp_ajax_)と未ログイン(wp_ajax_nopriv_)の両方で受け付ける
add_action( 'wp_ajax_myprefix_load_more', 'myprefix_load_more' );
add_action( 'wp_ajax_nopriv_myprefix_load_more', 'myprefix_load_more' );
function myprefix_load_more() {
check_ajax_referer( 'myprefix_load_more', 'nonce' );
$page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
$query = new WP_Query(
array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => (int) get_option( 'posts_per_page' ),
'paged' => max( 1, $page ),
)
);
ob_start();
while ( $query->have_posts() ) {
$query->the_post();
printf(
'<article><h2><a href="%s">%s</a></h2></article>',
esc_url( get_permalink() ),
esc_html( get_the_title() )
);
}
wp_reset_postdata();
wp_send_json_success(
array(
'html' => ob_get_clean(),
'hasMore' => $page < (int) $query->max_num_pages,
)
);
}
試したところ、24件・1ページ10件のサイトで page=2 を送ると10件と hasMore: true、page=3 で残りの4件と hasMore: false が返りました。ノンスが違うときは check_ajax_referer() が -1(HTTP 403)を返して処理を止めます。
テーマの記事カードと同じ見た目にしたいときは、printf() の代わりにテーマのテンプレートパーツを get_template_part() で読み込みます。
テンプレートに置くHTML
<div id="post-list">
<!-- 1ページ目の投稿はふだんどおりループで出す -->
</div>
<button type="button" id="load-more">もっと見る</button>
JavaScript(テーマの js/load-more.js)
document.addEventListener('DOMContentLoaded', function () {
const button = document.getElementById('load-more');
const list = document.getElementById('post-list');
if (!button || !list || typeof myprefixLoadMore === 'undefined') {
return;
}
let nextPage = 2; // 最初に1ページ目が表示されているので2から
button.addEventListener('click', async function () {
button.disabled = true;
button.textContent = '読み込み中…';
const body = new FormData();
body.append('action', 'myprefix_load_more');
body.append('nonce', myprefixLoadMore.nonce);
body.append('page', nextPage);
try {
const response = await fetch(myprefixLoadMore.ajaxUrl, {
method: 'POST',
body: body,
credentials: 'same-origin',
});
const json = await response.json();
if (!json.success) {
throw new Error('request failed');
}
list.insertAdjacentHTML('beforeend', json.data.html);
nextPage++;
if (json.data.hasMore) {
button.disabled = false;
button.textContent = 'もっと見る';
} else {
button.remove();
}
} catch (error) {
button.disabled = false;
button.textContent = '読み込めませんでした。もう一度試す';
}
});
});
Ajaxで読み込むときの注意
- ページキャッシュを使っているサイトでは、キャッシュされた HTML に古いノンスが残り、時間がたつと
-1が返って読み込めなくなります。ノンスの有効期間は標準で最長24時間です。キャッシュの保存期間をそれより短くするか、公開済みの投稿を返すだけの処理ならノンスの確認を外す判断もあります。 - ボタンで足した投稿には個別の URL がありません。検索エンジンや、JavaScript が動かない環境のために、通常のページ番号リンク(/page/2/ など)も残しておくと安全です。
ブロックテーマの場合
Twenty Twenty-Five などのブロックテーマでは、PHP のテンプレートではなくサイトエディターで一覧を作ります。「クエリーループ」ブロックの中に「ページネーション」ブロックを置くと、前へ・ページ番号・次へが表示されます。クエリーループの設定の「クエリータイプ」が「デフォルト」(そのページのメインクエリを使う)のときは、表示件数は「表示設定」と pre_get_posts の値に従います。「カスタム」にするとブロックの設定で件数を決めます。