Laravelのページネーション実装方法|一覧を1ページずつ表示する
記事一覧やユーザー一覧を作ったとき、データが増えると1つの画面に何百件も並んでしまいます。表示は遅くなり、読む側も探しづらくなります。
そこで使うのがページネーション(ページ分割)です。「1ページ10件ずつ表示して、下に[1][2][3]…のリンクを出す」あの仕組みです。
Laravel には最初から用意されていて、驚くほど短いコードで実装できます。
完成形(先に全体像)
やることは2つだけです。
- コントローラで
all()の代わりにpaginate()を使う - Blade で
{{ $posts->links() }}を書く
これだけでページ送りのリンクまで自動で作られます。順に見ていきます。
1. コントローラ側
まず、データを取得している部分を書き換えます。
// 変更前: 全件取得
$posts = Post::all();
// 変更後: 10件ずつに分割
$posts = Post::paginate(10);
paginate(10) の 10 が「1ページあたりの件数」です。コントローラのファイル全体では次のようになります。
<?php
// app/Http/Controllers/PostController.php
namespace App\Http\Controllers;
use App\Models\Post;
class PostController extends Controller
{
public function index()
{
$posts = Post::latest()->paginate(10);
return view('posts.index', ['posts' => $posts]);
}
}
use App\Models\Post;を忘れずに。 この1行がないと、PHP は同じフォルダにあるApp\Http\Controllers\Postを探しにいってしまい、「Class “App\Http\Controllers\Post” not found」というエラーになります。初心者がとてもよく詰まるポイントです。
latest() は「新しい順に並べる」という指定です。並び順を決めてから paginate() を呼ぶ、と覚えてください。
ポイント:
paginate()は「いま何ページ目を見ているか」を自分で判断します。URL の?page=2を Laravel が自動で読み取ってくれるので、こちらで書く必要はありません。
2. Blade 側(画面)
取得したデータは、これまでどおり @foreach で回せます。そこに1行足すだけです。
{{-- resources/views/posts/index.blade.php --}}
<ul>
@foreach ($posts as $post)
<li>{{ $post->title }}</li>
@endforeach
</ul>
{{-- ページ送りのリンク --}}
{{ $posts->links() }}
{{ $posts->links() }} の部分に、[前へ][1][2][3][次へ]のようなリンクが自動で出力されます。実際には <nav> タグを含む HTML が生成されます。
見た目について: 出力される HTML には、標準で Tailwind CSS 用のクラスが付いています。そのため Tailwind を使っている構成(Laravel Breeze など)ならきれいに整いますが、CSS を何も読み込んでいない状態では、リンクが素の状態で並ぶだけです。「リンクは出たがデザインが崩れている」という場合は、この後の「見た目を Bootstrap に変える」を参照してください。
件数や状態を取り出す
「全何件中、何件目を表示中」といった情報も取り出せます。
<p>
全 {{ $posts->total() }} 件中
{{ $posts->firstItem() }}〜{{ $posts->lastItem() }} 件目を表示
({{ $posts->currentPage() }} / {{ $posts->lastPage() }} ページ)
</p>
よく使うものをまとめます。
| メソッド | 返すもの |
|---|---|
total() | 全体の件数 |
count() | いまのページの件数 |
perPage() | 1ページあたりの件数 |
currentPage() | いま何ページ目か |
lastPage() | 最後のページ番号 |
hasPages() | ページ分割が必要か(1ページに収まるなら false) |
paginate() と simplePaginate() の違い
似たメソッドに simplePaginate() があります。
$posts = Post::simplePaginate(10);
違いは次のとおりです。
paginate() | simplePaginate() | |
|---|---|---|
| リンクの見た目 | [1][2][3]…と番号が出る | [前へ][次へ]だけ |
| 全体の件数 | 分かる(total() が使える) | 分からない |
| データベースへの負荷 | やや重い(全件を数えるため) | 軽い |
paginate() は「全部で何件あるか」を知るために、件数を数える処理を追加で実行します。データが非常に多いときはこれが負担になります。
使い分けの目安
- 件数を見せたい、ページ番号を出したい →
paginate() - とにかく速くしたい、件数は不要 →
simplePaginate()
迷ったら paginate() で構いません。
つまずきポイント: 検索条件が消える
実務でほぼ必ず遭遇する問題です。
検索フォームを付けて ?keyword=laravel のような URL で絞り込んだあと、2ページ目のリンクを押すと検索条件が消えてしまうことがあります。リンクの URL が ?page=2 だけになってしまうためです。
これは withQueryString() を付けると解決します。コントローラの全体像はこうなります。
<?php
// app/Http/Controllers/PostController.php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index(Request $request)
{
// URLの ?keyword=... を受け取る(無ければ null)
$keyword = $request->input('keyword');
$posts = Post::when($keyword, function ($query, $keyword) {
$query->where('title', 'like', "%{$keyword}%");
})
->latest()
->paginate(10)
->withQueryString();
return view('posts.index', ['posts' => $posts]);
}
}
when() は「第1引数が空でなければ、中の処理を実行する」という便利なメソッドです。これで、検索語が入力されたときだけ絞り込みが効きます。
withQueryString() を付けたことで、ページ送りのリンクが ?keyword=laravel&page=2 のように、いまの検索条件を引き継いだ URL になります。
補足:
like検索では、%と_が特別な意味を持つ記号(ワイルドカード)として扱われます。ユーザーが検索語に100%のような文字を入れると、意図と違う結果になることがあります。厳密に扱いたい場合は、これらの記号をエスケープする処理が別途必要です。
特定のパラメータだけを付けたい場合は appends() を使います。
$posts = Post::paginate(10)->appends(['sort' => 'new']);
見た目を Bootstrap に変える
標準では Tailwind CSS 向けの HTML が出力されます。Bootstrap を使っているサイトでは、AppServiceProvider で切り替えられます。
// app/Providers/AppServiceProvider.php
use Illuminate\Pagination\Paginator;
public function boot(): void
{
Paginator::useBootstrapFive();
}
デザインを自分で作り込みたい場合は、ページネーション用のビューを自分のプロジェクトに取り出して編集できます。
php artisan vendor:publish --tag=laravel-pagination
実行すると resources/views/vendor/pagination/ にテンプレートがコピーされ、自由に書き換えられるようになります。
表示するリンクの数を調整する
ページ数が多いと、リンクが横に長く伸びてしまいます。現在ページの左右に表示する数は onEachSide() で調整できます。
$posts = Post::paginate(10)->onEachSide(2);
まとめ
- コントローラで
paginate(件数)、Blade で{{ $posts->links() }}。この2つだけで実装できる。 ?page=2は Laravel が自動で読み取るので、自分で処理する必要はない。- 件数を出したいときは
total()やcurrentPage()を使う。 - 番号リンクが要らない・速さ優先なら
simplePaginate()(ただしtotal()は使えない)。 - 検索条件を保ちたいときは
withQueryString()。これを忘れるとページ送りで条件が消える。 - 見た目は
useBootstrapFive()やvendor:publishでカスタマイズできる。
一覧・登録・編集・削除をひととおり作る流れは、カリキュラムの 一覧・登録・編集・削除を実装する で解説しています。