본문 바로가기

데이터베이스: 페이지네이션

업데이트됨

번역일: 2026년 10월 1일

이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.

원문 수정
2026년 10월 1일
번역 갱신
2026년 10월 1일

데이터베이스: 페이지네이션

소개

다른 프레임워크에서는 페이지네이션 기능을 구현하는 게 꽤 번거로운 작업일 수 있습니다. 라라벨의 페이지네이션 방식은 이런 번거로움 없이 훨씬 수월하게 느껴질 것입니다. 라라벨의 페이지네이터는 쿼리 빌더 및 Eloquent ORM과 긴밀하게 통합되어 있어서, 별다른 설정 없이도 데이터베이스 레코드를 편리하게 페이지 단위로 나눠 보여줄 수 있습니다.

페이지네이터가 생성하는 HTML은 기본적으로 Tailwind CSS 프레임워크와 호환되도록 되어 있지만, Bootstrap을 사용한 페이지네이션도 지원합니다.

Tailwind

Tailwind 4.x와 함께 라라벨의 기본 Tailwind 페이지네이션 뷰를 사용하고 있다면, 애플리케이션의 resources/css/app.css 파일에는 이미 라라벨 페이지네이션 뷰를 @source로 포함하도록 설정이 되어 있을 것입니다.

@import 'tailwindcss'; @source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';

기본 사용법

쿼리 빌더 결과 페이지네이션

항목을 페이지네이션하는 방법은 여러 가지가 있지만, 가장 간단한 방법은 쿼리 빌더나 Eloquent 쿼리에서 paginate 메서드를 사용하는 것입니다. paginate 메서드는 사용자가 현재 보고 있는 페이지를 기준으로 쿼리의 "limit"과 "offset"을 자동으로 설정해줍니다. 기본적으로 현재 페이지는 HTTP 요청의 page 쿼리 문자열 값을 통해 감지되며, 이 값은 라라벨이 자동으로 인식할 뿐만 아니라 페이지네이터가 생성하는 링크에도 자동으로 삽입됩니다.

아래 예제에서는 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에서 전체 페이지 수를 표시할 계획이 없다면 이 레코드 카운트 쿼리는 사실 불필요한 작업입니다.

따라서 애플리케이션 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);

마찬가지로 cursorPaginate 메서드를 사용하면 Eloquent 모델을 커서 기반으로 페이지네이션할 수 있습니다.

$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" 절을 구성하는 방식으로 동작합니다. 이 방식은 라라벨이 제공하는 페이지네이션 방법 중 데이터베이스 성능 측면에서 가장 효율적이며, 특히 대용량 데이터셋이나 "무한 스크롤" 형태의 UI에 적합합니다.

페이지 번호를 쿼리 문자열에 담아 URL을 생성하는 offset 기반 페이지네이션과 달리, 커서 기반 페이지네이션은 쿼리 문자열에 "커서" 문자열을 담습니다. 이 커서는 다음 페이지네이션 쿼리가 시작해야 할 위치와, 어느 방향으로 페이지를 이동해야 하는지에 대한 정보를 인코딩한 문자열입니다.

http://localhost/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0

쿼리 빌더가 제공하는 cursorPaginate 메서드를 통해 커서 기반 페이지네이터 인스턴스를 생성할 수 있습니다. 이 메서드는 Illuminate\Pagination\CursorPaginator 인스턴스를 반환합니다.

$users = DB::table('users')->orderBy('id')->cursorPaginate(15);

커서 페이지네이터 인스턴스를 가져온 후에는 paginate나 simplePaginate를 사용할 때와 마찬가지 방식으로 페이지네이션 결과를 화면에 표시할 수 있습니다. 커서 페이지네이터가 제공하는 인스턴스 메서드에 대해 더 자세히 알고 싶다면 커서 페이지네이터 인스턴스 메서드 문서를 참고하세요.

WARNING

커서 페이지네이션을 사용하려면 쿼리에 반드시 "order by" 절이 포함되어야 합니다. 또한 정렬 기준이 되는 컬럼은 페이지네이션하려는 테이블에 속한 컬럼이어야 합니다.

커서 페이지네이션 vs. Offset 페이지네이션

offset 페이지네이션과 커서 페이지네이션의 차이를 이해하기 위해 간단한 SQL 쿼리 예시를 살펴보겠습니다. 아래 두 쿼리는 모두 id 기준으로 정렬된 users 테이블의 "두 번째 페이지"를 보여줍니다.

# Offset 페이지네이션... select * from users order by id asc limit 15 offset 15; # 커서 페이지네이션... select * from users where id > 15 order by id asc limit 15;

커서 페이지네이션 쿼리는 offset 페이지네이션에 비해 다음과 같은 장점이 있습니다.

  • "order by" 기준 컬럼에 인덱스가 걸려 있다면, 대용량 데이터셋에서는 커서 페이지네이션이 더 나은 성능을 제공합니다. offset 절은 이전까지 일치한 데이터를 전부 스캔하고 지나가야 하기 때문입니다.
  • 데이터가 자주 쓰여지는(쓰기 작업이 빈번한) 데이터셋의 경우, 사용자가 현재 보고 있는 페이지에 최근 레코드가 추가되거나 삭제되면 offset 페이지네이션은 일부 레코드를 건너뛰거나 중복해서 보여줄 수 있습니다.

다만 커서 페이지네이션에는 다음과 같은 제약 사항도 있습니다.

  • simplePaginate와 마찬가지로 커서 페이지네이션은 "다음"과 "이전" 링크만 지원하며, 페이지 번호가 있는 링크는 생성할 수 없습니다.
  • 정렬 기준이 최소 하나의 유니크 컬럼, 또는 유니크한 조합을 이루는 컬럼들을 기반으로 해야 합니다. null 값을 포함하는 컬럼은 지원되지 않습니다.
  • "order by" 절에 쿼리 표현식(expression)을 사용하는 경우, 반드시 별칭(alias)을 지정하고 "select" 절에도 함께 추가해야 지원됩니다.
  • 파라미터를 포함하는 쿼리 표현식은 지원되지 않습니다.

페이지네이터 직접 생성하기

이미 메모리에 가지고 있는 배열 데이터를 이용해 페이지네이션 인스턴스를 직접 만들고 싶을 때도 있을 것입니다. 이런 경우에는 필요에 따라 Illuminate\Pagination\Paginator, Illuminate\Pagination\LengthAwarePaginator, Illuminate\Pagination\CursorPaginator 중 하나의 인스턴스를 생성하면 됩니다.

Paginator와 CursorPaginator 클래스는 결과 집합의 전체 항목 수를 알 필요가 없습니다. 다만 그 때문에 마지막 페이지의 인덱스를 조회하는 메서드는 제공하지 않습니다. LengthAwarePaginator는 Paginator와 거의 동일한 인자를 받지만, 결과 집합의 전체 항목 수를 반드시 알아야 합니다.

즉, Paginator는 쿼리 빌더의 simplePaginate 메서드에, CursorPaginator는 cursorPaginate 메서드에, LengthAwarePaginator는 paginate 메서드에 각각 대응됩니다.

WARNING

페이지네이터 인스턴스를 직접 생성할 때는 페이지네이터에 전달하는 결과 배열을 직접 "슬라이스(slice)" 처리해야 합니다. 방법이 헷갈린다면 PHP의 array_slice 함수를 참고하세요.

페이지네이션 URL 커스터마이징

페이지네이터가 생성하는 링크는 기본적으로 현재 요청의 URI와 일치합니다. 하지만 페이지네이터의 withPath 메서드를 사용하면 링크를 생성할 때 사용할 URI를 직접 지정할 수 있습니다. 예를 들어 페이지네이터가 http://example.com/admin/users?page=N 형태의 링크를 생성하도록 하려면, withPath 메서드에 /admin/users를 전달하면 됩니다.

use App\Models\User; Route::get('/users', function () { $users = User::paginate(15); $users->withPath('/admin/users'); // ... });

쿼리 문자열 값 추가하기

appends 메서드를 사용하면 페이지네이션 링크의 쿼리 문자열에 값을 추가할 수 있습니다. 예를 들어 각 페이지네이션 링크에 sort=votes를 추가하고 싶다면 다음과 같이 appends를 호출하면 됩니다.

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를 추가하려면 다음과 같이 fragment 메서드를 호출하면 됩니다.

$users = User::paginate(15)->fragment('users');

페이지네이션 결과 표시하기

paginate 메서드를 호출하면 Illuminate\Pagination\LengthAwarePaginator 인스턴스를 받게 되고, simplePaginate 메서드를 호출하면 Illuminate\Pagination\Paginator 인스턴스를 받게 됩니다. 마지막으로 cursorPaginate 메서드를 호출하면 Illuminate\Pagination\CursorPaginator 인스턴스를 받게 됩니다.

이 객체들은 결과 집합에 대한 다양한 정보를 제공하는 여러 메서드를 가지고 있습니다. 이런 헬퍼 메서드들과 더불어, 페이지네이터 인스턴스 자체도 이터레이터(iterator)이기 때문에 배열처럼 반복문을 돌릴 수 있습니다. 즉, 결과를 가져온 다음에는 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으로 변환하기

라라벨의 페이지네이터 클래스들은 Illuminate\Contracts\Support\Jsonable 인터페이스 계약(contract)을 구현하며 toJson 메서드를 제공하기 때문에, 페이지네이션 결과를 JSON으로 변환하는 작업이 매우 간단합니다. 라우트나 컨트롤러 액션에서 페이지네이터 인스턴스를 그대로 반환하는 방식으로도 JSON으로 변환할 수 있습니다.

use App\Models\User; Route::get('/users', function () { return User::paginate(); });

페이지네이터가 생성하는 JSON에는 total, current_page, last_page 등의 메타 정보가 함께 포함됩니다. 실제 결과 레코드는 JSON 배열의 data 키를 통해 접근할 수 있습니다. 아래는 라우트에서 페이지네이터 인스턴스를 반환했을 때 생성되는 JSON의 예시입니다.

{ "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 명령어를 사용해 뷰 파일을 애플리케이션의 resources/views/vendor 디렉터리로 내보내는 것입니다.

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 사용하기

라라벨은 Bootstrap CSS로 작성된 페이지네이션 뷰도 함께 제공합니다. 기본 Tailwind 뷰 대신 이 뷰를 사용하려면, App\Providers\AppServiceProvider 클래스의 boot 메서드 안에서 페이지네이터의 useBootstrapFour 또는 useBootstrapFive 메서드를 호출하면 됩니다.

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)콜백을 사용해 각 항목을 변환합니다.

Cursor 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을 가져옵니다.

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 10월 1일