페이지네이션
번역일: 2026년 6월 25일
페이지네이션
- 소개
- 기본 사용법
- 페이지네이션 결과 표시
- 페이지네이션 뷰 커스터마이징
- Paginator / LengthAwarePaginator 인스턴스 메서드
- 커서 Paginator 인스턴스 메서드
소개
다른 프레임워크에서는 페이지네이션 구현이 번거로운 경우가 많습니다. Laravel은 쿼리 빌더와 Eloquent ORM에 페이지네이션이 완전히 통합되어 있어, 별도 설정 없이도 편리하게 데이터베이스 레코드를 페이지 단위로 나눌 수 있습니다.
기본적으로 페이지네이터가 생성하는 HTML은 Tailwind CSS와 호환되며, Bootstrap 스타일도 지원합니다.
Tailwind JIT 사용 시 주의사항
Laravel 기본 Tailwind 페이지네이션 뷰를 사용하면서 Tailwind JIT 엔진을 함께 쓰는 경우, tailwind.config.js의 content 항목에 Laravel 페이지네이션 뷰 경로를 추가해야 합니다. 그렇지 않으면 JIT가 해당 Tailwind 클래스를 미사용으로 판단하여 제거합니다.
content: [
'./resources/**/*.blade.php',
'./resources/**/*.js',
'./resources/**/*.vue',
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
],기본 사용법
쿼리 빌더 결과 페이지네이션
가장 간단한 방법은 쿼리 빌더 또는 Eloquent 쿼리에서 paginate 메서드를 호출하는 것입니다. paginate는 현재 사용자가 보고 있는 페이지를 기준으로 SQL의 LIMIT와 OFFSET을 자동으로 계산합니다. 현재 페이지는 HTTP 요청의 page 쿼리 스트링 값으로 감지되며, Laravel이 이를 자동으로 처리하고 페이지네이터가 생성하는 링크에도 자동으로 포함시킵니다.
아래 예시에서는 paginate에 페이지당 표시할 항목 수(15개)만 전달합니다.
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* 모든 사용자 목록을 표시합니다.
*/
public function index(): View
{
return view('user.index', [
'users' => DB::table('users')->paginate(15)
]);
}
}단순 페이지네이션
paginate 메서드는 레코드를 가져오기 전에 전체 레코드 수를 계산하는 COUNT 쿼리를 추가로 실행합니다. 이는 전체 페이지 수를 표시하기 위한 것인데, UI에서 전체 페이지 수가 필요 없다면 이 COUNT 쿼리는 불필요한 오버헤드가 됩니다.
"다음"과 "이전" 링크만 있으면 충분한 경우에는 simplePaginate를 사용하세요. 단 하나의 효율적인 쿼리만 실행합니다.
$users = DB::table('users')->simplePaginate(15);
Eloquent 결과 페이지네이션
Eloquent 쿼리에도 동일하게 페이지네이션을 적용할 수 있습니다. 문법은 쿼리 빌더와 거의 동일합니다.
use App\Models\User;
$users = User::paginate(15);물론 where 등의 조건을 추가한 뒤에도 paginate를 호출할 수 있습니다.
$users = User::where('votes', '>', 100)->paginate(15);
simplePaginate와 cursorPaginate도 Eloquent에서 동일하게 사용할 수 있습니다.
$users = User::where('votes', '>', 100)->simplePaginate(15);
$users = User::where('votes', '>', 100)->cursorPaginate(15);한 페이지에 여러 페이지네이터 사용
한 화면에 페이지네이터를 두 개 이상 렌더링해야 할 때, 둘 다 page 쿼리 스트링 파라미터를 사용하면 충돌이 발생합니다. 이를 방지하려면 paginate, simplePaginate, cursorPaginate의 세 번째 인수로 각 페이지네이터가 사용할 쿼리 스트링 파라미터 이름을 지정하세요.
use App\Models\User;
$users = User::where('votes', '>', 100)->paginate(
$perPage = 15, $columns = ['*'], $pageName = 'users'
);커서 페이지네이션
paginate와 simplePaginate가 SQL의 OFFSET 절을 사용하는 반면, 커서 페이지네이션은 정렬 기준 컬럼 값을 비교하는 WHERE 절을 사용합니다. 이 방식은 Laravel이 제공하는 페이지네이션 방법 중 데이터베이스 성능 면에서 가장 효율적이며, 대용량 데이터셋이나 무한 스크롤 UI에 특히 적합합니다.
오프셋 방식이 URL에 페이지 번호를 포함시키는 것과 달리, 커서 페이지네이션은 "커서"라는 인코딩된 문자열을 URL에 포함시킵니다. 이 커서에는 다음 쿼리를 시작할 위치와 방향 정보가 담겨 있습니다.
http://localhost/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0쿼리 빌더의 cursorPaginate 메서드를 사용하면 Illuminate\Pagination\CursorPaginator 인스턴스를 반환합니다.
$users = DB::table('users')->orderBy('id')->cursorPaginate(15);
커서 페이지네이터 인스턴스를 얻은 뒤에는 paginate, simplePaginate와 동일하게 결과를 표시할 수 있습니다. 커서 페이지네이터의 인스턴스 메서드는 아래 문서를 참고하세요.
WARNING
커서 페이지네이션을 사용하려면 쿼리에 반드시 ORDER BY 절이 포함되어야 합니다. 또한 정렬 기준 컬럼은 페이지네이션을 적용하는 테이블에 속한 컬럼이어야 합니다.
커서 페이지네이션 vs. 오프셋 페이지네이션
두 방식의 차이를 SQL로 비교해 보겠습니다. 아래 두 쿼리는 모두 users 테이블을 id 기준으로 정렬했을 때 두 번째 페이지를 조회합니다.
-- 오프셋 페이지네이션
select * from users order by id asc limit 15 offset 15;
-- 커서 페이지네이션
select * from users where id > 15 order by id asc limit 15;커서 페이지네이션의 장점:
- 정렬 기준 컬럼에 인덱스가 있을 경우, 대용량 데이터셋에서 성능이 훨씬 뛰어납니다.
OFFSET은 앞의 모든 행을 스캔해야 하지만, 커서는 특정 지점부터 바로 읽기 시작합니다. - 데이터가 빈번하게 추가·삭제되는 환경에서 오프셋 방식은 레코드가 누락되거나 중복 노출될 수 있지만, 커서 방식은 이런 문제가 발생하지 않습니다.
커서 페이지네이션의 제한 사항:
simplePaginate와 마찬가지로 "다음"/"이전" 링크만 생성할 수 있고, 페이지 번호 링크는 지원하지 않습니다.- 정렬 기준이 하나 이상의 고유(unique) 컬럼 또는 고유 컬럼 조합이어야 합니다.
NULL값을 허용하는 컬럼은 지원하지 않습니다. ORDER BY절의 쿼리 표현식은 별칭(alias)을 지정하고SELECT절에도 포함된 경우에만 지원합니다.- 파라미터가 포함된 쿼리 표현식은 지원하지 않습니다.
페이지네이터 직접 생성
이미 메모리에 있는 배열 데이터를 페이지네이션해야 할 때는 페이지네이터 인스턴스를 직접 생성할 수 있습니다. 필요에 따라 Illuminate\Pagination\Paginator, Illuminate\Pagination\LengthAwarePaginator, Illuminate\Pagination\CursorPaginator 중 하나를 선택하세요.
Paginator→simplePaginate에 대응. 전체 레코드 수 불필요.CursorPaginator→cursorPaginate에 대응. 전체 레코드 수 불필요.LengthAwarePaginator→paginate에 대응. 전체 레코드 수 필요.
Paginator와 CursorPaginator는 전체 항목 수를 알 필요가 없지만, 그 대신 마지막 페이지 인덱스를 조회하는 메서드를 제공하지 않습니다.
WARNING
페이지네이터를 직접 생성할 때는 페이지네이터에 전달할 배열을 직접 슬라이싱해야 합니다. 방법을 모르신다면 PHP의 array_slice 함수를 참고하세요.
페이지네이션 URL 커스터마이징
기본적으로 페이지네이터가 생성하는 링크는 현재 요청 URI를 기반으로 합니다. withPath 메서드를 사용하면 링크 생성에 사용할 URI를 직접 지정할 수 있습니다. 예를 들어 http://example.com/admin/users?page=N 형태의 링크를 생성하려면 다음과 같이 사용하세요.
use App\Models\User;
Route::get('/users', function () {
$users = User::paginate(15);
$users->withPath('/admin/users');
// ...
});쿼리 스트링 값 추가
appends 메서드를 사용하면 페이지네이션 링크의 쿼리 스트링에 추가 파라미터를 붙일 수 있습니다. 예를 들어 모든 링크에 sort=votes를 추가하려면 다음과 같이 하세요.
use App\Models\User;
Route::get('/users', function () {
$users = User::paginate(15);
$users->appends(['sort' => 'votes']);
// ...
});현재 요청의 모든 쿼리 스트링 값을 페이지네이션 링크에 그대로 유지하고 싶다면 withQueryString 메서드를 사용하세요.
$users = User::paginate(15)->withQueryString();
해시 프래그먼트 추가
페이지네이터가 생성하는 URL 끝에 해시 프래그먼트를 추가하려면 fragment 메서드를 사용하세요. 예를 들어 모든 링크 끝에 #users를 추가하려면 다음과 같이 호출합니다.
$users = User::paginate(15)->fragment('users');
페이지네이션 결과 표시
각 메서드가 반환하는 인스턴스 타입은 다음과 같습니다.
paginate→Illuminate\Pagination\LengthAwarePaginatorsimplePaginate→Illuminate\Pagination\PaginatorcursorPaginate→Illuminate\Pagination\CursorPaginator
이 인스턴스들은 결과셋을 설명하는 다양한 메서드를 제공하며, 배열처럼 반복(iterate)할 수도 있습니다. 결과를 조회했다면 Blade에서 아래와 같이 결과를 출력하고 페이지 링크를 렌더링할 수 있습니다.
<div class="container">
@foreach ($users as $user)
{{ $user->name }}
@endforeach
</div>
{{ $users->links() }}links 메서드는 나머지 페이지로 이동하는 링크를 렌더링합니다. 각 링크에는 page 쿼리 스트링이 자동으로 포함됩니다. links 메서드가 생성하는 HTML은 Tailwind CSS와 호환됩니다.
페이지 링크 범위 조정
페이지네이터는 현재 페이지 번호를 중심으로 앞뒤 각 3개의 페이지 링크를 함께 표시합니다. onEachSide 메서드를 사용하면 현재 페이지 양쪽에 표시할 추가 링크 수를 조절할 수 있습니다.
{{ $users->onEachSide(5)->links() }}결과를 JSON으로 변환
Laravel 페이지네이터 클래스는 Illuminate\Contracts\Support\Jsonable 인터페이스를 구현하고 toJson 메서드를 제공하므로, 페이지네이션 결과를 JSON으로 변환하기가 매우 쉽습니다. 라우트나 컨트롤러 액션에서 페이지네이터 인스턴스를 바로 반환해도 JSON으로 자동 변환됩니다.
use App\Models\User;
Route::get('/users', function () {
return User::paginate();
});변환된 JSON에는 total, current_page, last_page 등의 메타 정보가 포함되며, 실제 레코드는 data 키 아래에 배열로 담깁니다.
{
"total": 50,
"per_page": 15,
"current_page": 1,
"last_page": 4,
"first_page_url": "http://laravel.app?page=1",
"last_page_url": "http://laravel.app?page=4",
"next_page_url": "http://laravel.app?page=2",
"prev_page_url": null,
"path": "http://laravel.app",
"from": 1,
"to": 15,
"data":[
{
// 레코드...
},
{
// 레코드...
}
]
}페이지네이션 뷰 커스터마이징
기본적으로 페이지네이션 링크는 Tailwind CSS 기반의 뷰로 렌더링됩니다. Tailwind를 사용하지 않는 경우, links 메서드에 뷰 이름을 첫 번째 인수로 전달하여 직접 만든 뷰를 사용할 수 있습니다.
{{ $paginator->links('view.name') }}
<!-- 뷰에 추가 데이터 전달 -->
{{ $paginator->links('view.name', ['foo' => 'bar']) }}가장 간단한 커스터마이징 방법은 vendor:publish 명령어로 기본 뷰 파일을 프로젝트에 복사하는 것입니다.
php artisan vendor:publish --tag=laravel-pagination이 명령어를 실행하면 뷰 파일이 resources/views/vendor/pagination 디렉터리에 생성됩니다. 해당 디렉터리의 tailwind.blade.php 파일이 기본 페이지네이션 뷰이며, 이 파일을 수정하여 HTML을 자유롭게 변경할 수 있습니다.
기본 페이지네이션 뷰로 다른 파일을 지정하려면 App\Providers\AppServiceProvider의 boot 메서드에서 defaultView와 defaultSimpleView를 호출하세요.
<?php
namespace App\Providers;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Paginator::defaultView('view-name');
Paginator::defaultSimpleView('view-name');
}
}Bootstrap 사용
Laravel에는 Bootstrap CSS 기반의 페이지네이션 뷰도 기본으로 포함되어 있습니다. 기본 Tailwind 뷰 대신 Bootstrap 뷰를 사용하려면 App\Providers\AppServiceProvider의 boot 메서드에서 useBootstrapFive 또는 useBootstrapFour를 호출하세요.
use Illuminate\Pagination\Paginator;
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Paginator::useBootstrapFive();
// 또는
Paginator::useBootstrapFour();
}Paginator / LengthAwarePaginator 인스턴스 메서드
각 페이지네이터 인스턴스는 다음 메서드를 통해 페이지네이션 관련 정보를 제공합니다.
| 메서드 | 설명 |
|---|---|
$paginator->count() | 현재 페이지의 항목 수를 반환합니다. |
$paginator->currentPage() | 현재 페이지 번호를 반환합니다. |
$paginator->firstItem() | 현재 페이지 첫 번째 항목의 결과 번호를 반환합니다. |
$paginator->getOptions() | 페이지네이터 옵션을 반환합니다. |
$paginator->getUrlRange($start, $end) | 지정한 범위의 페이지네이션 URL 배열을 생성합니다. |
$paginator->hasPages() | 여러 페이지로 나눌 수 있을 만큼 항목이 충분한지 확인합니다. |
$paginator->hasMorePages() | 데이터 저장소에 더 많은 항목이 있는지 확인합니다. |
$paginator->items() | 현재 페이지의 항목들을 반환합니다. |
$paginator->lastItem() | 현재 페이지 마지막 항목의 결과 번호를 반환합니다. |
$paginator->lastPage() | 마지막 페이지 번호를 반환합니다. (simplePaginate 사용 시 불가) |
$paginator->nextPageUrl() | 다음 페이지 URL을 반환합니다. |
$paginator->onFirstPage() | 현재 페이지가 첫 번째 페이지인지 확인합니다. |
$paginator->perPage() | 페이지당 표시할 항목 수를 반환합니다. |
$paginator->previousPageUrl() | 이전 페이지 URL을 반환합니다. |
$paginator->total() | 데이터 저장소의 전체 일치 항목 수를 반환합니다. (simplePaginate 사용 시 불가) |
$paginator->url($page) | 주어진 페이지 번호의 URL을 반환합니다. |
$paginator->getPageName() | 현재 페이지를 저장하는 쿼리 스트링 변수 이름을 반환합니다. |
$paginator->setPageName($name) | 현재 페이지를 저장하는 쿼리 스트링 변수 이름을 설정합니다. |
$paginator->through($callback) | 콜백을 사용하여 각 항목을 변환합니다. |
커서 Paginator 인스턴스 메서드
각 커서 페이지네이터 인스턴스는 다음 메서드를 통해 페이지네이션 관련 정보를 제공합니다.
| 메서드 | 설명 |
|---|---|
$paginator->count() | 현재 페이지의 항목 수를 반환합니다. |
$paginator->cursor() | 현재 커서 인스턴스를 반환합니다. |
$paginator->getOptions() | 페이지네이터 옵션을 반환합니다. |
$paginator->hasPages() | 여러 페이지로 나눌 수 있을 만큼 항목이 충분한지 확인합니다. |
$paginator->hasMorePages() | 데이터 저장소에 더 많은 항목이 있는지 확인합니다. |
$paginator->getCursorName() | 커서를 저장하는 쿼리 스트링 변수 이름을 반환합니다. |
$paginator->items() | 현재 페이지의 항목들을 반환합니다. |
$paginator->nextCursor() | 다음 항목 집합의 커서 인스턴스를 반환합니다. |
$paginator->nextPageUrl() | 다음 페이지 URL을 반환합니다. |
$paginator->onFirstPage() | 현재 페이지가 첫 번째 페이지인지 확인합니다. |
$paginator->onLastPage() | 현재 페이지가 마지막 페이지인지 확인합니다. |
$paginator->perPage() | 페이지당 표시할 항목 수를 반환합니다. |
$paginator->previousCursor() | 이전 항목 집합의 커서 인스턴스를 반환합니다. |
$paginator->previousPageUrl() | 이전 페이지 URL을 반환합니다. |
$paginator->setCursorName() | 커서를 저장하는 쿼리 스트링 변수 이름을 설정합니다. |
$paginator->url($cursor) | 주어진 커서 인스턴스의 URL을 반환합니다. |