Laravel ページネーション完全ガイド|UIカスタマイズ・日本語化・検索条件引き継ぎ(withQueryString)まで徹底解説

基本文法・構文ガイド実装・応用テクニック

Webアプリケーションの一覧画面開発において、データを複数ページに分割して表示する「ページネーション(Pagination)」は必須の実装機能です。

Laravelには非常に強力で使いやすいページネーション機能が標準搭載されていますが、実際の開発現場では以下のような実装上の課題や疑問によく直面します。

  • 「ページ番号をクリックして遷移すると、検索フォームで絞り込んだ条件やキーワードがリセットされてしまう」
  • 「デフォルトで英語の『Previous / Next』が表示されるのを『前へ / 次へ』に日本語化したい」
  • 「Tailwind CSS ではなく Bootstrap 5 や自社独自のUIデザインを適用したい」
  • paginate()simplePaginate()cursorPaginate() の使い分けやパフォーマンスの違いが知りたい」
  • 「『全◯件中 ◯〜◯件目を表示』のような件数情報を表示するにはどうすればいい?」
  • Laravel 複数条件の絞り込み検索機能 実装ガイド|whenメソッド・動的WHERE句・ページネーション連携

この記事では、Laravelにおけるページネーションの基本構文から、Tailwind CSS / BootstrapへのUI切り替え、検索条件を引き継ぐ withQueryString() の実践テクニック、日本語化設定、完全オリジナルのカスタムBladeビュー作成、大規模データでの高速化対策(カーソルページネーション)やアンチパターンまで、実務でそのままコピペして使えるコード例とともに徹底解説します。


  1. 【早見表】Laravel ページネーション実装・カスタマイズ手法一覧
  2. Laravel ページネーションの基本構文と3つの取得メソッド
    1. 1. paginate():ページ番号付きの標準ページネーション
    2. 2. simplePaginate():前へ/次へのみの軽量ページネーション
    3. 3. cursorPaginate():大規模データ・無限スクロール向けの爆速ページネーション
  3. ページネーションのUIカスタマイズ(Tailwind CSS / Bootstrap)
    1. 1. アプリ全体でBootstrapを指定する(推奨)
    2. 2. 個別のBladeテンプレート内でビューを指定する
  4. 検索条件・クエリパラメータを引き継ぐ方法(withQueryString)
    1. 問題が発生する原因
    2. 解決策:withQueryString() を呼び出す(推奨)
      1. Bladeテンプレート側で指定する場合(最も手軽):
      2. コントローラー側で指定する場合:
    3. 個別パラメータの追加(appends)とアンカーリンク(fragment)
  5. Laravel ページネーションの日本語化手順(「前へ」「次へ」の変更)
    1. 方法1:lang/ja/pagination.php を作成する(推奨)
    2. 方法2:lang/ja.json に記述する
    3. 設定の反映(config/app.php のロケール設定)
  6. 完全オリジナルのカスタムページネーションビューを作成する
    1. 1. 標準のテンプレートファイルを公開(publish)する
    2. 2. ゼロからカスタムBladeファイルを作成する例
  7. Paginatorインスタンスで使える便利なメソッド一覧
    1. 「全◯件中 ◯〜◯件目を表示」の実装例
  8. API開発でのページネーション(JSON / API Resource対応)
    1. 1. API Resourceを使った推奨実装
    2. 2. 返却されるJSONのデータ構造
  9. 実務で頻発するページネーションの落とし穴・アンチパターン 4選
    1. 落とし穴1:get() や all() の後に paginate() を呼んでしまう
    2. 落とし穴2:groupBy() と paginate() の併用で総件数が狂う
    3. 落とし穴3:大量データ(100万件超)での OFFSET パフォーマンス劣化
    4. 落とし穴4:N+1問題の放置(with() のつけ忘れ)
  10. Laravel関連記事・内部リンク
  11. まとめ:ページネーション設計チェックリスト
    1. ✅ ページネーション実装チェックリスト
  12. 関連記事

【早見表】Laravel ページネーション実装・カスタマイズ手法一覧

まずは、実務で頻出するページネーションの実装・カスタマイズパターンを一覧でまとめました。

実現したいこと 使用するコード / メソッド 主な特徴・用途
標準ページネーション User::paginate(15); ページ番号リンク付き。全体の総件数(COUNTクエリ)を取得する。
シンプルページネーション User::simplePaginate(15); 「前へ」「次へ」のみ。総件数カウントを省略して高速化。
カーソルページネーション User::cursorPaginate(15); 主キー等のカーソル基準。OFFSETを使わないため100万件超でも爆速。
検索条件の引き継ぎ $users->withQueryString()->links() 現在のURLクエリパラメータを保持したまま次ページへ遷移。
Bootstrap 5 への切り替え Paginator::useBootstrapFive(); AppServiceProvider::boot() に記述して全体スタイルを変更。
日本語化(翻訳) lang/ja/pagination.php または lang/ja.json 「Previous / Next」を「前へ / 次へ」に自動変換。
独自Bladeビューの適用 $users->links('custom.pagination') 自作のHTML/CSSで完全オリジナルデザインを描画。

Laravel ページネーションの基本構文と3つの取得メソッド

LaravelのEloquentおよびクエリビルダでは、用途とデータ規模に合わせて3種類のページネーションメソッドが提供されています。

1. paginate():ページ番号付きの標準ページネーション

最も一般的に使用されるメソッドです。1ページあたりの件数を指定するだけで、現在のページ番号(?page=2 等)に応じたデータ取得と総件数のカウントを自動で行います。

// app/Http/Controllers/UserController.php
namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\View\View;

class UserController extends Controller
{
    public function index(): View
    {
        // 1ページあたり15件取得(最新順)
        $users = User::latest()->paginate(15);

        return view('users.index', compact('users'));
    }
}

Bladeテンプレート側では、渡されたPaginatorインスタンスの links() メソッドを呼び出すだけでページネーションナビゲーションがレンダリングされます。

<!-- resources/views/users/index.blade.php -->
<div class="container mx-auto px-4 py-8">
    <h1 class="text-2xl font-bold mb-6">ユーザー一覧</h1>

    <table class="min-w-full bg-white border border-gray-200 mb-6">
        <thead class="bg-gray-50">
            <tr>
                <th class="px-4 py-2 border-b text-left">ID</th>
                <th class="px-4 py-2 border-b text-left">名前</th>
                <th class="px-4 py-2 border-b text-left">メールアドレス</th>
                <th class="px-4 py-2 border-b text-left">登録日時</th>
            </tr>
        </thead>
        <tbody>
            @forelse ($users as $user)
                <tr class="hover:bg-gray-50">
                    <td class="px-4 py-2 border-b">{{ $user->id }}</td>
                    <td class="px-4 py-2 border-b">{{ $user->name }}</td>
                    <td class="px-4 py-2 border-b">{{ $user->email }}</td>
                    <td class="px-4 py-2 border-b">{{ $user->created_at->format('Y-m-d H:i') }}</td>
                </tr>
            @empty
                <tr>
                    <td colspan="4" class="px-4 py-4 text-center text-gray-500">ユーザーが見つかりません。</td>
                </tr>
            @endforelse
        </tbody>
    </table>

    <!-- ページネーションリンクを描画 -->
    <div class="mt-4">
        {{ $users->links() }}
    </div>
</div>

内部で発行されるSQL:
1. 該当ページのデータ取得:SELECT * FROM `users` ORDER BY `created_at` DESC LIMIT 15 OFFSET 0;
2. 総件数のカウント:SELECT COUNT(*) AS aggregate FROM `users`;

2. simplePaginate():前へ/次へのみの軽量ページネーション

「1, 2, 3…」といったページ番号リンクが不要で、「前へ」「次へ」ボタンだけで十分な場合は simplePaginate() を利用します。

// 総件数カウントを行わない軽量ページネーション
$users = User::latest()->simplePaginate(15);

メリット:
SELECT COUNT(*) クエリが発行されないため、数十万〜数百万件以上のテーブルでもDB負荷を大幅に抑えられます(指定件数 + 1件を取得して次のページが存在するかのみを判定します)。

3. cursorPaginate():大規模データ・無限スクロール向けの爆速ページネーション

Laravelで提供されている カーソルページネーション(Cursor Pagination) は、OFFSET を使用せず、前回の末尾レコードの主キーやソートキー(カーソル文字列)を条件に WHERE id > ? で次ページを取得します。

// カーソルベースのページネーション
$users = User::orderBy('id', 'desc')->cursorPaginate(15);

生成されるURLパラメータは ?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0 のようなエンコードされた文字列になります。SNSのタイムラインやモバイルアプリの無限スクロールAPI、超大規模データの一覧表示に最適です。


ページネーションのUIカスタマイズ(Tailwind CSS / Bootstrap)

Laravelのデフォルトページネーションビューは Tailwind CSS に最適化されています。しかし、プロジェクトで Bootstrap 5 や Bootstrap 4 を使用している場合、表示が大きく崩れてしまいます。

1. アプリ全体でBootstrapを指定する(推奨)

プロジェクト全体でUIフレームワークを統一する場合は、AppServiceProviderboot メソッド内で指定します。

// app/Providers/AppServiceProvider.php
namespace App\Providers;

use Illuminate\Pagination\Paginator;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        // Bootstrap 5 を使用する場合
        Paginator::useBootstrapFive();

        // Bootstrap 4 を使用する場合
        // Paginator::useBootstrapFour();

        // Tailwind CSS を明示的に指定する場合(デフォルト)
        // Paginator::useTailwind();
    }
}

💡 ポイント:
Paginator::useBootstrapFive() を設定しておけば、すべてのBladeファイルで単に {{ $items->links() }} と書くだけで、Bootstrap 5の .pagination, .page-item, .page-link クラスが適用された綺麗なHTMLが出力されます。

2. 個別のBladeテンプレート内でビューを指定する

特定の画面だけ別のデザインを適用したい場合は、links() の第1引数にLaravel組み込みのビュー名を直接指定できます。

<!-- Bootstrap 5 スタイルを個別指定 -->
{{ $users->links('pagination::bootstrap-5') }}

<!-- Bootstrap 4 スタイルを個別指定 -->
{{ $users->links('pagination::bootstrap-4') }}

<!-- Bootstrap 5 のシンプル(前へ/次へのみ)スタイル -->
{{ $users->links('pagination::simple-bootstrap-5') }}

<!-- Tailwind CSS のシンプルスタイル -->
{{ $users->links('pagination::simple-tailwind') }}

検索条件・クエリパラメータを引き継ぐ方法(withQueryString)

一覧画面に検索フォームやソート・絞り込みフィルターを実装した際、最も頻発するトラブルが「2ページ目をクリックすると検索条件が消えて全件一覧に戻ってしまう」という現象です。

問題が発生する原因

検索フォームから /users?keyword=tanaka&status=active でアクセスした場合でも、デフォルトの {{ $users->links() }}/users?page=2 というURLリンクを生成してしまうため、keywordstatus が欠落してしまいます。

解決策:withQueryString() を呼び出す(推奨)

この問題を一発で解決するのが withQueryString() メソッドです。現在のリクエストに含まれるすべてのクエリパラメータを自動的にページネーションリンクへ付加してくれます。

Bladeテンプレート側で指定する場合(最も手軽):

<!-- 現在の検索条件(?keyword=xxx&role=yyy 等)をすべて保持してリンク生成 -->
{{ $users->withQueryString()->links() }}

コントローラー側で指定する場合:

// app/Http/Controllers/UserController.php
public function index(Request $request): View
{
    $query = User::query();

    // キーワード検索(名前またはメールアドレス)
    if ($request->filled('keyword')) {
        $keyword = $request->input('keyword');
        $query->where(function ($q) use ($keyword) {
            $q->where('name', 'like', "%{$keyword}%")
              ->orWhere('email', 'like', "%{$keyword}%");
        });
    }

    // ステータス絞り込み
    if ($request->filled('status')) {
        $query->where('status', $request->input('status'));
    }

    // withQueryString() をチェーンしてクエリ文字列を保持
    $users = $query->latest()->paginate(15)->withQueryString();

    return view('users.index', compact('users'));
}

個別パラメータの追加(appends)とアンカーリンク(fragment)

すべてのクエリを引き継ぐのではなく、特定のパラメータだけを明示的に追加・制御したい場合は appends() を使用します。

// 特定のパラメータのみを追加する
$users = User::paginate(15)->appends([
    'sort' => 'popular',
    'category_id' => 3,
]);

また、ページ遷移後に特定のHTML要素(ページ内アンカー #user-list)へスクロールさせたい場合は fragment() を利用します。

// 生成されるURL: /users?page=2#user-list
$users = User::paginate(15)->withQueryString()->fragment('user-list');

Laravel ページネーションの日本語化手順(「前へ」「次へ」の変更)

Laravelの標準ページネーションビューでは、前後のリンクテキストとして pagination.previouspagination.next の翻訳キーが使われています。

日本語化を行うには、言語ファイル(PHP配列形式) または JSON翻訳ファイル を作成します。

方法1:lang/ja/pagination.php を作成する(推奨)

プロジェクトの lang/ja/ ディレクトリに pagination.php を配置します(ディレクトリが存在しない場合は作成してください)。

<?php
// lang/ja/pagination.php

return [
    /*
    |--------------------------------------------------------------------------
    | ページネーション言語行
    |--------------------------------------------------------------------------
    |
    | ページネーションリンクで使用されるメッセージ群です。
    | 必要に応じて自由にカスタマイズ可能です。
    |
    */

    'previous' => '&laquo; 前へ',
    'next'     => '次へ &raquo;',
];

方法2:lang/ja.json に記述する

多言語対応でJSON翻訳ファイルを使用している場合は、lang/ja.json 内にキーを追加することも可能です。

{
    "pagination.previous": "&laquo; 前へ",
    "pagination.next": "次へ &raquo;",
    "Previous": "前へ",
    "Next": "次へ",
    "Showing": "表示中",
    "to": "〜",
    "of": "全",
    "results": "件"
}

設定の反映(config/app.php のロケール設定)

アプリケーションのデフォルトロケールが日本語(ja)になっているか確認します。

// config/app.php または .env (APP_LOCALE=ja)
'locale' => 'ja',
'fallback_locale' => 'en',

設定ファイルを変更した後は、設定キャッシュをクリアしておきましょう。

php artisan config:clear
php artisan view:clear

完全オリジナルのカスタムページネーションビューを作成する

自社デザインやUIライブラリに合わせた完全オリジナルのページネーションを作成する方法を解説します。

1. 標準のテンプレートファイルを公開(publish)する

以下のArtisanコマンドを実行すると、Laravelが内蔵しているすべてのページネーション用Bladeファイルが resources/views/vendor/pagination/ にコピーされます。

php artisan vendor:publish --tag=laravel-pagination

公開されるファイル構成:

公開された tailwind.blade.phpbootstrap-5.blade.php を直接編集することで、{{ $items->links() }} の出力を全体的にカスタマイズできます。

2. ゼロからカスタムBladeファイルを作成する例

独自のクラス構成で作成したい場合は、新しいBladeファイル(例:resources/views/partials/my-pagination.blade.php)を作成します。

<!-- resources/views/partials/my-pagination.blade.php -->
@if ($paginator->hasPages())
    <nav role="navigation" aria-label="ページネーションナビゲーション" class="flex items-center justify-between my-4">
        <!-- モバイル用:前へ / 次へのみ -->
        <div class="flex justify-between flex-1 sm:hidden">
            @if ($paginator->onFirstPage())
                <span class="px-4 py-2 text-sm font-medium text-gray-400 bg-gray-100 border border-gray-300 rounded-md cursor-not-allowed">前へ</span>
            @else
                <a href="{{ $paginator->previousPageUrl() }}" class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50">前へ</a>
            @endif

            @if ($paginator->hasMorePages())
                <a href="{{ $paginator->nextPageUrl() }}" class="ml-3 px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50">次へ</a>
            @else
                <span class="ml-3 px-4 py-2 text-sm font-medium text-gray-400 bg-gray-100 border border-gray-300 rounded-md cursor-not-allowed">次へ</span>
            @endif
        </div>

        <!-- PC用:件数情報 + ページ番号一覧 -->
        <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
            <div>
                <p class="text-sm text-gray-700">
                    全 <span class="font-bold text-indigo-600">{{ $paginator->total() }}</span> 件中
                    <span class="font-bold text-gray-900">{{ $paginator->firstItem() }}</span> 〜
                    <span class="font-bold text-gray-900">{{ $paginator->lastItem() }}</span> 件を表示
                </p>
            </div>

            <div>
                <span class="relative z-0 inline-flex shadow-sm rounded-md">
                    {{-- 前へボタン --}}
                    @if ($paginator->onFirstPage())
                        <span aria-disabled="true" aria-label="前へ" class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-300 bg-white border border-gray-300 rounded-l-md cursor-not-allowed">
                            &lt;
                        </span>
                    @else
                        <a href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="前へ" class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-600 bg-white border border-gray-300 rounded-l-md hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500">
                            &lt;
                        </a>
                    @endif

                    {{-- ページ番号リンク --}}
                    @foreach ($elements as $element)
                        {{-- 「...」の区切り記号 --}}
                        @if (is_string($element))
                            <span aria-disabled="true" class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300">{{ $element }}</span>
                        @endif

                        {{-- ページ番号配列 --}}
                        @if (is_array($element))
                            @foreach ($element as $page => $url)
                                @if ($page == $paginator->currentPage())
                                    <span aria-current="page" class="z-10 relative inline-flex items-center px-4 py-2 text-sm font-bold text-white bg-indigo-600 border border-indigo-600">{{ $page }}</span>
                                @else
                                    <a href="{{ $url }}" class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:z-10 focus:outline-none">{{ $page }}</a>
                                @endif
                            @endforeach
                        @endif
                    @endforeach

                    {{-- 次へボタン --}}
                    @if ($paginator->hasMorePages())
                        <a href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="次へ" class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-600 bg-white border border-gray-300 rounded-r-md hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500">
                            &gt;
                        </a>
                    @else
                        <span aria-disabled="true" aria-label="次へ" class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-300 bg-white border border-gray-300 rounded-r-md cursor-not-allowed">
                            &gt;
                        </span>
                    @endif
                </span>
            </div>
        </div>
    </nav>
@endif

作成したカスタムビューを使用するには、Blade側でビュー名を指定します。

{{ $users->withQueryString()->links('partials.my-pagination') }}

Paginatorインスタンスで使える便利なメソッド一覧

ページネーションインスタンス($users 等)は、単なるデータ配列ではなく、ページ情報や総件数を取得するための豊富なヘルパーメソッドを備えています。

メソッド名 戻り値型 説明・使い道
$items->count() int 現在のページ に表示されているアイテム数
$items->total() int データベース内の全該当レコード総数(※paginateのみ)
$items->currentPage() int 現在表示しているページ番号(例:2
$items->lastPage() int 最後のページ番号(※paginateのみ)
$items->perPage() int 1ページあたりの最大取得件数(例:15
$items->firstItem() int|null 現在のページで最初に表示されるレコードの通番(例:2ページ目なら 16
$items->lastItem() int|null 現在のページで最後に表示されるレコードの通番(例:2ページ目なら 30
$items->hasPages() bool 複数ページにまたがるデータがあるか(総数がperPageを超えているか)
$items->hasMorePages() bool 次のページが存在するかどうか
$items->onFirstPage() bool 現在が最初のページ(1ページ目)かどうか

「全◯件中 ◯〜◯件目を表示」の実装例

一覧画面の上部や下部に件数サマリーを配置する実用コードです。

@if ($users->total() > 0)
    <p class="text-sm text-gray-600 mb-4">
        全 {{ number_format($users->total()) }} 件中
        {{ $users->firstItem() }} 〜 {{ $users->lastItem() }} 件目を表示しています
        ({{ $users->currentPage() }} / {{ $users->lastPage() }} ページ)
    </p>
@endif

API開発でのページネーション(JSON / API Resource対応)

SPA(Vue.js / React / Next.js)やモバイルアプリ向けのRESTful APIを構築する際、Paginatorインスタンスをコントローラーからそのまま返却、あるいは API Resource(Eloquent API Resources) を経由して返却します。

1. API Resourceを使った推奨実装

// app/Http/Controllers/Api/UserController.php
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Resources\UserResource;
use App\Models\User;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;

class UserController extends Controller
{
    public function index(): AnonymousResourceCollection
    {
        $users = User::latest()->paginate(15);

        // UserResourceコレクションとして返却
        return UserResource::collection($users);
    }
}

2. 返却されるJSONのデータ構造

API Resourceを介して返却されるJSONには、データ本体の data に加えて、ページネーション用の linksmeta が自動で付与されます。

{
    "data": [
        {
            "id": 1,
            "name": "山田 太郎",
            "email": "yamada@example.com"
        }
    ],
    "links": {
        "first": "https://example.com/api/users?page=1",
        "last": "https://example.com/api/users?page=7",
        "prev": null,
        "next": "https://example.com/api/users?page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 7,
        "links": [
            {
                "url": null,
                "label": "&laquo; Previous",
                "active": false
            },
            {
                "url": "https://example.com/api/users?page=1",
                "label": "1",
                "active": true
            },
            {
                "url": "https://example.com/api/users?page=2",
                "label": "2",
                "active": false
            },
            {
                "url": "https://example.com/api/users?page=2",
                "label": "Next &raquo;",
                "active": false
            }
        ],
        "path": "https://example.com/api/users",
        "per_page": 15,
        "to": 15,
        "total": 100
    }
}

フロントエンド(React/Vue)では、response.data.data でリストを描画し、response.data.meta を使ってページネーションコンポーネントを制御できます。


実務で頻発するページネーションの落とし穴・アンチパターン 4選

ページネーション実装で初心者がハマりやすいトラブルと回避策をまとめました。

落とし穴1:get() や all() の後に paginate() を呼んでしまう

// ❌ NG例:全件取得したCollectionに対してpaginateを呼ぼうとしてエラーまたは全件メモリ展開
$users = User::get()->paginate(15); // Method paginate does not exist on Collection

// ⭕ OK例:QueryBuilderに対してpaginateを呼ぶ(SQLでLIMIT/OFFSETが実行される)
$users = User::paginate(15);

解説: paginate() はデータベースクエリ(Illuminate\Database\Eloquent\Builder)に対して呼び出す必要があります。先に get()all() を呼ぶと全件がメモリにロードされてしまい、パフォーマンスの大幅悪化やエラーの原因になります。

落とし穴2:groupBy() と paginate() の併用で総件数が狂う

クエリ内で groupBy を使用している場合、Laravelの paginate() が内部で発行する COUNT(*) がグループ全体の件数を正しく計算できず、総件数やページ数がずれる問題が発生します。

解決策: クエリをサブクエリ化するか、DB::raw でカウントするか、simplePaginate() で件数カウント自体をスキップする設計を検討します。

落とし穴3:大量データ(100万件超)での OFFSET パフォーマンス劣化

標準の paginate() はSQLの OFFSET 1000000 LIMIT 15 を使用します。MySQLなどのRDBMSでは、OFFSET値が大きくなるほど「スキップするために100万件読み飛ばす」必要があり、深いページに進むほど極端に重くなります。

解決策: 大規模ログや履歴テーブルでは、cursorPaginate() を採用することで、100ページ目でも1ページ目と全く同じミリ秒単位の速度でクエリが実行されます。

落とし穴4:N+1問題の放置(with() のつけ忘れ)

一覧画面で関連モデルのデータ(投稿者の名前、カテゴリ名など)を表示する場合、ページネーションで15件取得しても、15回の追加SQLが発行される「N+1問題」が発生します。

// ❌ NG例:ループ内でリレーション先へ都度クエリ発行(1 + 15回SQL)
$posts = Post::latest()->paginate(15);

// ⭕ OK例:with() で事前にEager Loading(2回のSQLで完了)
$posts = Post::with(['user', 'category'])->latest()->paginate(15);

Laravelの実務開発で役立つクエリ最適化やEloquent設計の関連記事もあわせてご確認ください。


まとめ:ページネーション設計チェックリスト

Laravelのページネーションは、少ないコード量で高度なUIと機能を備えた一覧画面を構築できる非常に優れた機能です。本番運用へリリースする前に、以下のチェックリストを確認しておきましょう。

✅ ページネーション実装チェックリスト

  • [ ] 検索条件の引き継ぎ: withQueryString() が設定されており、ページ遷移しても検索条件が消えないか
  • [ ] UIフレームワークの統一: AppServiceProvider 等で Tailwind CSS / Bootstrap の指定が適切に行われているか
  • [ ] 日本語化対応: 「Previous / Next」が「前へ / 次へ」に正しくローカライズされているか
  • [ ] N+1問題対策: リレーション先のデータ取得に with() によるEager Loadingを行っているか
  • [ ] 件数・データ規模に応じたメソッド選択: 大規模テーブルでは simplePaginate()cursorPaginate() の採用を検討したか
  • [ ] レスポンシブ表示: モバイル画面でページ番号がはみ出さず、適切にレイアウトが調整されているか
  • Laravel 複数条件の絞り込み検索機能 実装ガイド|whenメソッド・動的WHERE句・ページネーション連携

適切なページネーション手法を選択し、ユーザーフレンドリーで表示速度の速い一覧画面を実装していきましょう。

レン (Wren)

こんにちは。レンです。

Laravelのコードの森に住んでいる、小さな案内役です。
ルーティングの枝やクラスの影を歩きながら、コードの流れや仕組みを眺めています。

このサイトでは、Laravelの基本から実装のコツまで、開発で役立つポイントを静かに整理しています。
難しいことを増やすのではなく、コードの見通しが少し良くなるヒントを届けるのが役目です。

「この処理はどこに書くのがいいのか」
「Laravelではどう考えると整理できるのか」

そんな疑問に、小さなメモを残すような気持ちで記事を書いています。

コードを書いている途中で迷ったとき、
このサイトが少し立ち止まって整理できる場所になればうれしいです。

レン (Wren)をフォローする

コメント