페이지네이션이란
Laravel의 페이지네이션은 쿼리 빌더 및 Eloquent ORM과 통합되어 있어, 설정 없이 사용할 수 있습니다. 현재 페이지는 HTTP 요청의 page 쿼리 파라미터에서 자동으로 취득되며, 생성되는 링크에도 자동으로 부가됩니다.
기본 HTML은 Tailwind CSS에 대응하며, Bootstrap CSS도 선택할 수 있습니다.
3종류의 페이지네이션
메서드 반환 값 특징 paginate()LengthAwarePaginator총 건수 취득. 페이지 번호 링크 생성 simplePaginate()Paginator총 건수 취득 안 함. “이전”·“다음”만 cursorPaginate()CursorPaginator커서 기반. 대량 데이터에 최적
기본 사용법
쿼리 빌더의 페이지네이션
use Illuminate\Support\Facades\ DB ;
// 1페이지당 15건씩 가져오기
$users = DB :: table ( 'users' ) -> paginate ( 15 );
Eloquent의 페이지네이션
use App\Models\ User ;
$users = User :: paginate ( 15 );
// 조건 포함
$users = User :: where ( 'votes' , '>' , 100 ) -> paginate ( 15 );
simplePaginate
총 건수 카운트 쿼리가 불필요한 경우(“이전”·“다음” 링크만 표시)에는 simplePaginate()를 사용하면 효율적입니다.
$users = DB :: table ( 'users' ) -> simplePaginate ( 15 );
$users = User :: where ( 'active' , true ) -> simplePaginate ( 15 );
“전체 N건 중 M건째”라는 표시가 불필요하다면 simplePaginate()를 선택합시다. paginate()는 COUNT(*) 쿼리를 추가로 실행하므로 simplePaginate() 쪽이 빠릅니다.
cursorPaginate(커서 페이지네이션)
커서 페이지네이션은 OFFSET 절 대신 WHERE 절을 사용하기 때문에 대량 데이터에 대해 높은 성능을 발휘합니다. 무한 스크롤 UI에 특히 적합합니다.
// orderBy가 필수
$users = DB :: table ( 'users' ) -> orderBy ( 'id' ) -> cursorPaginate ( 15 );
$users = User :: where ( 'active' , true ) -> cursorPaginate ( 15 );
생성되는 URL에는 페이지 번호가 아닌 커서 문자열이 들어갑니다.
http://example.com/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0
커서 페이지네이션을 사용하려면 orderBy가 필수입니다. 또한 정렬 순서의 컬럼은 페이지네이션 대상 테이블에 속해야 합니다.
OFFSET과 커서의 비교
-- paginate() / simplePaginate() — OFFSET 사용
SELECT * FROM users ORDER BY id ASC LIMIT 15 OFFSET 15 ;
-- cursorPaginate() — WHERE 절 사용(인덱스가 효과적)
SELECT * FROM users WHERE id > 15 ORDER BY id ASC LIMIT 15 ;
커서 페이지네이션은 인덱스가 효과적으로 활용되며, 데이터가 자주 추가·삭제되는 경우에도 레코드의 중복·누락이 잘 발생하지 않는다는 이점이 있습니다. 다만 페이지 번호 링크의 생성은 불가하며 “이전”·“다음”만 가능합니다.
컨트롤러에서의 구현
<? php
namespace App\Http\Controllers ;
use App\Models\ User ;
use Illuminate\View\ View ;
class UserController extends Controller
{
public function index () : View
{
$users = User :: orderBy ( 'name' ) -> paginate ( 20 );
return view ( 'users.index' , compact ( 'users' ));
}
}
Blade에서의 페이지네이션 링크 표시
< div class = "container" >
@foreach ( $users as $user )
< p > {{ $user -> name }} </ p >
@endforeach
</ div >
{{-- 페이지네이션 링크를 출력(Tailwind CSS 대응) --}}
{{ $users -> links () }}
links() 메서드가 자동으로 페이지 링크의 HTML을 생성합니다. 현재 페이지 앞뒤 3페이지 분량의 링크가 표시됩니다.
표시할 링크 수 조정
onEachSide()로 현재 페이지 앞뒤에 표시할 링크 수를 변경할 수 있습니다.
{{-- 현재 페이지 앞뒤 5페이지 분량을 표시 --}}
{{ $users -> onEachSide ( 5 ) -> links () }}
1페이지당 건수를 요청에서 받기
public function index ( Request $request ) : View
{
$perPage = $request -> integer ( 'per_page' , 15 );
$perPage = min ( max ( $perPage , 1 ), 100 ); // 1~100 범위로 제한
$users = User :: paginate ( $perPage );
return view ( 'users.index' , compact ( 'users' ));
}
1페이지에 여러 페이지네이터 표시
동일 화면에 두 개의 페이지네이터를 표시하는 경우, 양쪽이 page 파라미터를 사용하면 충돌합니다. 세 번째 인수로 파라미터 이름을 변경합니다.
$users = User :: paginate (
perPage : 15 ,
columns : [ '*' ],
pageName : 'users'
);
$posts = Post :: paginate (
perPage : 10 ,
columns : [ '*' ],
pageName : 'posts'
);
URL 커스터마이징
베이스 URL 변경
$users = User :: paginate ( 15 );
// /admin/users?page=N 형식의 URL을 생성
$users -> withPath ( '/admin/users' );
쿼리 파라미터 추가
// sort=votes를 각 페이지 링크에 추가
$users -> appends ([ 'sort' => 'votes' ]);
// 현재 요청의 모든 쿼리 파라미터를 이어받기
$users -> withQueryString ();
해시 프래그먼트 추가
// URL 끝에 #users를 추가
$users -> fragment ( 'users' );
API 응답(JSON 출력)
페이지네이터를 라우트나 컨트롤러에서 그대로 반환하면 자동으로 JSON으로 변환됩니다.
use App\Models\ User ;
Route :: get ( '/api/users' , function () {
return User :: paginate ( 15 );
});
응답의 JSON 형식:
{
"total" : 50 ,
"per_page" : 15 ,
"current_page" : 1 ,
"last_page" : 4 ,
"first_page_url" : "http://example.com/api/users?page=1" ,
"last_page_url" : "http://example.com/api/users?page=4" ,
"next_page_url" : "http://example.com/api/users?page=2" ,
"prev_page_url" : null ,
"path" : "http://example.com/api/users" ,
"from" : 1 ,
"to" : 15 ,
"data" : [
{ "id" : 1 , "name" : "홍길동" },
{ "id" : 2 , "name" : "김하나" }
]
}
API 리소스와의 조합
paginate()의 결과를 API 리소스 컬렉션으로 감싸는 경우 UserResource::collection()에 전달합니다.
use App\Http\Resources\ UserResource ;
use App\Models\ User ;
Route :: get ( '/api/users' , function () {
$users = User :: paginate ( 15 );
return UserResource :: collection ( $users );
});
UserResource::collection()에 페이지네이터를 전달하면 페이지네이션 정보가 메타데이터로 자동으로 부가됩니다.
cursorPaginate()의 JSON에는 페이지 번호가 아닌 next_cursor와 prev_cursor가 포함됩니다. API 클라이언트는 이 값들을 다음 요청의 cursor 파라미터로 사용합니다.
커스텀 페이지네이션 뷰
뷰 파일에 직접 지정
{{-- 커스텀 뷰로 링크를 출력 --}}
{{ $paginator -> links ( 'vendor.pagination.custom' ) }}
{{-- 데이터를 추가로 전달 --}}
{{ $paginator -> links ( 'vendor.pagination.custom' , [ 'theme' => 'dark' ]) }}
기본 뷰를 커스텀 파일로 변경
먼저 공식 뷰를 게시한 후 커스터마이징합니다.
php artisan vendor:publish --tag=laravel-pagination
resources/views/vendor/pagination/에 다음 파일이 생성됩니다.
tailwind.blade.php — 기본값(Tailwind CSS용)
bootstrap-5.blade.php — Bootstrap 5용
simple-tailwind.blade.php — simplePaginate용
…
tailwind.blade.php를 직접 편집하거나 새 뷰를 만들어서 AppServiceProvider에서 지정합니다.
use Illuminate\Pagination\ Paginator ;
public function boot () : void
{
Paginator :: defaultView ( 'vendor.pagination.custom' );
Paginator :: defaultSimpleView ( 'vendor.pagination.simple-custom' );
}
Bootstrap CSS 사용
Tailwind가 아닌 Bootstrap을 사용하는 경우 AppServiceProvider의 boot()에서 지정합니다.
use Illuminate\Pagination\ Paginator ;
public function boot () : void
{
Paginator :: useBootstrapFive (); // Bootstrap 5
// Paginator::useBootstrapFour(); // Bootstrap 4
}
수동으로 페이지네이터 생성
배열 등 기존 데이터에 페이지네이션을 적용하고 싶은 경우, 페이지네이터 클래스를 직접 인스턴스화합니다.
use Illuminate\Pagination\ LengthAwarePaginator ;
$items = collect ( range ( 1 , 200 )) -> map ( fn ( $i ) => [ 'id' => $i , 'name' => "아이템{ $i }" ]);
$perPage = 15 ;
$currentPage = request () -> integer ( 'page' , 1 );
$paginator = new LengthAwarePaginator (
items : $items -> forPage ( $currentPage , $perPage ),
total : $items -> count (),
perPage : $perPage ,
currentPage : $currentPage ,
options : [ 'path' => request () -> url ()]
);
자주 사용하는 인스턴스 메서드
$paginator = User :: paginate ( 15 );
$paginator -> currentPage (); // 현재 페이지 번호
$paginator -> lastPage (); // 마지막 페이지 번호(simplePaginate에서는 사용 불가)
$paginator -> total (); // 총 건수(simplePaginate에서는 사용 불가)
$paginator -> perPage (); // 1페이지당 건수
$paginator -> count (); // 현재 페이지의 건수
$paginator -> firstItem (); // 현재 페이지의 첫 아이템 번호
$paginator -> lastItem (); // 현재 페이지의 마지막 아이템 번호
$paginator -> hasPages (); // 여러 페이지가 존재하는지
$paginator -> hasMorePages (); // 다음 페이지가 존재하는지
$paginator -> onFirstPage (); // 첫 페이지인지
$paginator -> onLastPage (); // 마지막 페이지인지
$paginator -> nextPageUrl (); // 다음 페이지 URL
$paginator -> previousPageUrl (); // 이전 페이지 URL
$paginator -> url ( 3 ); // 페이지 3의 URL
$paginator -> items (); // 현재 페이지의 아이템 배열
paginate() — 총 건수와 페이지 번호 링크가 필요한 경우(일반적인 리스트 화면)
simplePaginate() — “이전”·“다음” 링크만으로 충분한 경우(고속)
cursorPaginate() — 대량 데이터·무한 스크롤·잦은 쓰기가 있는 경우(최고 성능)
@foreach ( $users as $user )
< p > {{ $user -> name }} </ p >
@endforeach
{{ $users -> links () }}
paginate()의 결과를 뷰에 전달하고 links()로 페이지 링크를 출력하기만 하면 됩니다.
현재 페이지는 page 쿼리 파라미터에서 자동으로 검출됩니다.
페이지네이터를 라우트에서 직접 반환하면 자동으로 JSON으로 변환됩니다.
API 리소스와 조합하려면 UserResource::collection($paginator)를 반환합니다.
응답에는 data(레코드 배열)와 각종 메타 정보가 포함됩니다.