【WordPress】最新記事を除外してもページネーションを崩さずに投稿一覧表示する

※本記事のコードは参考用です。使用前にご自身で動作確認をお願いします。

はじめに

WordPressで投稿一覧を表示する際に、「最新記事だけを特別に扱いたい」「1ページ目だけ特別なレイアウトにしたい」と感じることはありませんか?例えば、1ページ目は最新記事1件に加えてさらに8件の記事を表示し、合計9件とし、2ページ目以降は8件ずつ表示するといったケースです。つまり、1ページ目だけ表示件数が1件多くなるような状態を作ることです。

しかし、このように処理を行うとページネーションthe_posts_pagination()がずれてしまう問題に直面することも多いです。

この記事では、そんなときに設定すべきWordPressのoffsetパラメータの仕組みと、最新投稿1件を除外しながらも、ページネーションの動きを崩さずに投稿一覧を表示する方法について解説します。

間違った考え方

実際にやってしまった間違いを紹介します。例えば、「1件目だけ表示件数を最新記事1件+8件、2ページ目以降は8件」にしようとしてページごとにposts_per_pageだけを変更するのは間違いです。

以下が間違ったコードと、発生した問題です。pre_get_postsを利用してクエリが実行される前にposts_per_pageの数をページごとに書き換えようとしました。

  • 1ページ目のページネーションは全件数を9件で割ったものを表示し、それ以降は8件で割った数を表示してしまう。つまり、1ページ目とそれ以降でページネーションの数が変わる。
  • 2ページ目になると、1ページ目も8件で割った9件目から表示されるため、1ページ目の最後の記事と2ページ目の最初の記事が重複する。
/* 以下のコードは間違った例です。*/
function exclude_latest_post_in_blog_archive($query) {
  if ( $query->is_post_type_archive('blog') ) {

    // ページ番号を取得
    $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;

    // ✗最新の記事は固定のため、1ページ目だけ表示件数を変更
    if ( $paged == 1 ) {
      $query->set( 'posts_per_page', 9 );
    } else {
      $query->set( 'posts_per_page', 8 );
    }
  }
}
add_action( 'pre_get_posts', 'exclude_latest_post_in_blog_archive' );

正しく調整する

こういった問題を解決するには、調整が2点必要です。

  • クエリから最新記事を除外する(offset)
  • ページネーションが認識する投稿総数を修正する(found_posts)

また、これから解説するやり方では、最新記事は除外されて一覧表示されるので、最新記事は別でクエリを用意して表示するようにしてください。

最新記事を除外する(offset)

こういった問題を解決するには、そもそもpre_get_postsが全件を分割してページネーションを作る前に、その元となる全件から最新の記事を除外する必要があります。そのために必要なパラメーターがoffsetです。

offsetパラメータとは?

offsetとは、WordPressの投稿取得クラス(WP_Queryなど)で利用できるパラメータで、投稿の取得開始位置をずらすためのものです。
たとえば offset => 3 と指定すると、「最初の3件をスキップして、4件目から投稿を取得する」という意味になります。

そこで、offsetを使って、最新1件を常にスキップするよう調整します。

ページネーションを崩さずにoffsetを使う方法

offsetを使うときに注意したいのが、ページ数のズレが起きやすいことです。

WordPress のページネーション(the_posts_pagination() など)は、投稿の総数(found_posts)と posts_per_page の値をもとに、必要なページ数を自動計算します。ただし、offset の値はこの総数に影響しないため、ページ数にズレが生じる可能性があります。found_postsについては後述します)

そのため、offsetを固定値のまま使うと、2ページ目以降で表示がずれたり、表示件数が合わなくなる可能性があります。

これを防ぐには、ページ数に応じて offsetを動的に計算する必要があります。

$posts_per_page = 8; // 8件ずつ表示する
$paged = max( 1, get_query_var('paged') ); // 現在のページを取得
$offset = 1 + ( $paged - 1 ) * $posts_per_page; // 除外する件数(常に最新記事を除外)
  • 最新1件をスキップするため「1」足す
  • 2ページ目以降は (paged - 1) * 8 件ずつさらにスキップ

このようにすると、ページごとに取得開始位置が正しく変わり、ページネーションも正しく動作します。例えば、2ページ目の場合$offsetは「1 + ( 2 - 1 ) * 8 = 9」となり、9件分スキップされます。この場合、2ページ目は「全件数のうちの10件目」からスタートとなります。

これをクエリ自体にセットすることで問題を解決します。

ページ

offset の値

意味

1ページ目

1 + (1 - 1) * 8 = 1

最新1件を除いた 2件目以降から表示

2ページ目

1 + (2 - 1) * 8 = 9

最新記事1件+前の8件をスキップ(10件目から表示)

3ページ目

1 + (3 - 1) * 8 = 17

最新記事1件+前の16件をスキップ(18件目から表示)

完成形のコード

以下はカスタム投稿タイプblogのアーカイブページで最新記事を除外しつつ、8件ずつ投稿を表示する例です。

function exclude_latest_post_in_blog_archive( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return;
    }

    if ( ! is_post_type_archive('blog') ) {
        return;
    }

    $posts_per_page = 8;
    $paged = max( 1, get_query_var('paged') );
    $offset = 1 + ( $paged - 1 ) * $posts_per_page;

    // クエリにセット
    $query->set( 'posts_per_page', $posts_per_page );
    $query->set( 'offset', $offset );
}
add_action( 'pre_get_posts', 'exclude_latest_post_in_blog_archive' );

これで、記事が重複することなく最新記事を除外した投稿一覧が表示されます。

総投稿数(found_posts)を調整する

記事が重複するなどの問題はなくなりますが、WordPressのクエリに offset を使って最新記事を除外し、2ページ目以降で記事を表示する場合、ページネーションの挙動がおかしくなります。

先ほどoffsetの項目でも説明しましたが、WordPress のページネーションは、総投稿数(found_posts)の計算にoffsetを考慮しません。そのため、実際に表示される投稿件数とズレが生じ、存在しないページ番号が生成されてしまう可能性があります。

必要ページ数にズレが発生する

たとえば、今まで例としてきたように、最新記事を1件除外し、8件ずつ表示する場合、ページネーションはoffsetを考慮せず投稿総数をそのまま元の値でページ数を計算します。

例えば投稿総数が17件だったとします。しかし実際は最新記事を除いた16件が投稿総数となり、ちょうど2ページ分に相当します。ところがページネーションは「17件 ÷ 8件」を行い3ページ必要と判断し、3ページ目が存在すると誤認識します。

この結果、存在しない3ページ目を示すリンクが表示され、アクセスした際に404エラーが発生する問題が起こります。

found_postsフィルターによる総投稿数の調整

この問題を解決するために、found_posts フィルターを使って総投稿数を補正します。
具体的には、offset分だけ総投稿数を減らし、WordPressが正しいページ数を計算するようにします。

function adjust_found_posts_for_offset($found_posts, $query) {
    $offset = 1; // 除外する最新記事数

    if ($query->is_main_query() && !is_admin() && is_post_type_archive('blog')) {
        return max(0, $found_posts - $offset);
    }
    return $found_posts;
}
add_filter('found_posts', 'adjust_found_posts_for_offset', 10, 2);

このように投稿総数を補正することで、ページネーションのリンク数が実際の表示件数と一致し、ユーザーに存在しないページへのリンクを見せないようになります。

まとめ

最新記事を除外しても、ページネーションを崩さずに投稿一覧表示したい場合に気をつけること

  • offsetは、投稿取得時に何件スキップするかを指定できるパラメータ
  • ただしoffsetを使うと、ページネーションがずれる可能性があるため、ページ数に応じた動的な計算が必須
  • found_posts フィルターで総投稿数を補正し、正しいページ数を計算させる必要がある
  • pre_get_postsフックを使えば、テーマやテンプレートを変更せず、クエリだけを柔軟にカスタマイズできる

WordPressの投稿一覧をカスタマイズしたい場合、offsetfound_postsの理解はとても重要です。
ぜひこの記事を参考に、より自由で使いやすい投稿一覧を作ってみてください。

参考

Making Custom Queries using Offset and Pagination « WordPress Codex