메인 콘텐츠로 건너뛰기

페이지네이션이란

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_cursorprev_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을 사용하는 경우 AppServiceProviderboot()에서 지정합니다.
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(레코드 배열)와 각종 메타 정보가 포함됩니다.
마지막 수정일 2026년 7월 13일