페이지네이션
업데이트됨번역일: 2026년 6월 21일
이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.
- 원문 수정
- 2026년 6월 20일
- 번역 갱신
- 2026년 6월 21일
페이지네이션
소개
일부 프레임워크에서 페이지네이션을 구현하는 것은 꽤 번거로운 작업입니다. Laravel은 쿼리 빌더와 Eloquent ORM에 페이지네이션이 완전히 통합되어 있어, 별도의 설정 없이 편리하게 데이터베이스 레코드를 페이지 단위로 나눌 수 있습니다.
기본적으로 페이지네이터가 생성하는 HTML은 Tailwind CSS와 호환되며, Bootstrap도 지원합니다.
Tailwind
Tailwind 4.x와 Laravel 기본 페이지네이션 뷰를 함께 사용하는 경우, resources/css/app.css 파일에 아래와 같이 Laravel의 페이지네이션 뷰 경로를 @source로 지정해야 합니다. 새 프로젝트라면 이미 설정되어 있을 수 있습니다.
@import 'tailwindcss';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';기본 사용법
쿼리 빌더 결과 페이지네이션
가장 간단한 방법은 쿼리 빌더 또는 Eloquent 쿼리에서 paginate 메서드를 호출하는 것입니다. paginate 메서드는 현재 사용자가 보고 있는 페이지를 기준으로 limit과 offset을 자동으로 처리합니다. 현재 페이지는 HTTP 요청의 page 쿼리 스트링 값으로 감지되며, Laravel이 자동으로 인식하고 페이지네이터가 생성하는 링크에도 자동으로 포함됩니다.
아래 예제에서는 paginate에 페이지당 표시할 항목 수만 전달하면 됩니다. 여기서는 페이지당 15개를 표시하도록 설정합니다.
<?php
namespace App\Http\Controllers;
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 메서드는 레코드를 가져오기 전에 전체 레코드 수를 세는 쿼리를 추가로 실행합니다. 이를 통해 총 페이지 수를 계산합니다. 하지만 UI에 총 페이지 수를 표시하지 않는다면, 이 카운트 쿼리는 불필요합니다.
"이전" / "다음" 링크만 있으면 충분한 경우에는 simplePaginate 메서드를 사용하세요. 단 하나의 효율적인 쿼리만 실행됩니다.
$users = DB::table('users')->simplePaginate(15);Eloquent 결과 페이지네이션
Eloquent 쿼리에서도 동일하게 페이지네이션을 사용할 수 있습니다. 아래 예제는 App\Models\User 모델을 페이지당 15개씩 나누는 예입니다. 쿼리 빌더와 문법이 거의 동일합니다.
use App\Models\User;
$users = User::paginate(15);물론 where 등의 조건을 추가한 뒤 paginate를 호출할 수도 있습니다.
$users = User::where('votes', '>', 100)->paginate(15);Eloquent 모델에서도 simplePaginate를 사용할 수 있습니다.
$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에 페이지 번호(?page=2)를 포함하는 반면, 커서 페이지네이션은 아래와 같이 인코딩된 커서 문자열을 URL에 포함합니다. 커서에는 다음 쿼리의 시작 위치와 방향 정보가 담겨 있습니다.
http://localhost/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0cursorPaginate 메서드를 사용하면 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;커서 페이지네이션의 장점:
- 대용량 데이터셋에서
order by컬럼에 인덱스가 있다면 성능이 훨씬 뛰어납니다.offset절은 이전 데이터를 모두 스캔하지만, 커서 방식은 그렇지 않습니다. - 데이터 삽입/삭제가 빈번한 환경에서도 레코드 누락이나 중복 표시 문제가 발생하지 않습니다.
커서 페이지네이션의 제한사항:
simplePaginate처럼 "이전" / "다음" 링크만 지원하며, 페이지 번호 링크는 생성할 수 없습니다.- 정렬 기준이 최소 하나의 고유 컬럼이거나 고유한 컬럼 조합이어야 합니다.
null값을 가진 컬럼은 지원하지 않습니다. order by에 쿼리 표현식을 사용하려면 해당 표현식에 별칭을 부여하고select절에도 포함해야 합니다.- 파라미터가 있는 쿼리 표현식은 지원하지 않습니다.
페이지네이터 직접 생성
이미 메모리에 배열로 데이터를 가지고 있고, 이를 페이지네이션 처리하고 싶은 경우 Illuminate\Pagination\Paginator, Illuminate\Pagination\LengthAwarePaginator, Illuminate\Pagination\CursorPaginator 중 필요에 맞는 클래스를 직접 인스턴스화할 수 있습니다.
Paginator와 CursorPaginator는 전체 항목 수를 알 필요가 없지만, 그 대신 마지막 페이지를 조회하는 메서드를 제공하지 않습니다. LengthAwarePaginator는 Paginator와 거의 동일하지만, 전체 항목 수를 필수로 받습니다.
정리하면, Paginator는 simplePaginate, CursorPaginator는 cursorPaginate, LengthAwarePaginator는 paginate에 대응합니다.
WARNING
페이지네이터를 직접 생성할 때는 전달할 배열을 직접 현재 페이지에 해당하는 부분만 잘라서 전달해야 합니다. 방법이 헷갈린다면 PHP의 array_slice 함수를 참고하세요.
페이지네이션 URL 커스터마이징
기본적으로 페이지네이터가 생성하는 링크는 현재 요청 URI를 기반으로 합니다. withPath 메서드를 사용하면 링크 생성에 사용할 URI를 직접 지정할 수 있습니다. 예를 들어 http://example.com/admin/users?page=N 형태의 링크를 생성하려면 다음과 같이 /admin/users를 전달합니다.
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\LengthAwarePaginator 인스턴스를, simplePaginate는 Illuminate\Pagination\Paginator 인스턴스를, cursorPaginate는 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,
"current_page_url": "http://laravel.app?page=1",
"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->onLastPage() | 현재 페이지가 마지막 페이지인지 확인합니다. |
$paginator->perPage() | 페이지당 표시할 항목 수를 반환합니다. |
$paginator->previousPageUrl() | 이전 페이지의 URL을 반환합니다. |
$paginator->total() | 전체 일치 항목 수를 반환합니다. (simplePaginate에서는 사용 불가) |
$paginator->url($page) | 지정한 페이지 번호의 URL을 반환합니다. |
$paginator->getPageName() | 페이지를 저장하는 쿼리 스트링 변수명을 반환합니다. |
$paginator->setPageName($name) | 페이지를 저장하는 쿼리 스트링 변수명을 설정합니다. |
$paginator->through($callback) | 콜백을 사용해 각 항목을 변환합니다. |
커서 페이지네이터 인스턴스 메서드
각 커서 페이지네이터 인스턴스는 다음 메서드를 통해 페이지네이션 정보를 제공합니다.
| 메서드 | 설명 |
|---|---|
$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을 반환합니다. |