> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 分頁

> 說明 Laravel 的分頁功能。涵蓋 paginate()、simplePaginate()、cursorPaginate() 的差異、Blade 顯示、API 回應以及 URL 自訂。

## 什麼是分頁

Laravel 的分頁與查詢建構器和 Eloquent ORM 整合，無須任何設定即可使用。目前頁碼會自動從 HTTP 請求的 `page` 查詢參數取得，並自動附加到產生的連結上。

預設的 HTML 支援 Tailwind CSS，也可選用 Bootstrap CSS。

## 三種分頁方式

| 方法                 | 回傳值                    | 特徵                  |
| ------------------ | ---------------------- | ------------------- |
| `paginate()`       | `LengthAwarePaginator` | 會取得總筆數。可產生頁碼連結      |
| `simplePaginate()` | `Paginator`            | 不取得總筆數。僅有「上一頁」「下一頁」 |
| `cursorPaginate()` | `CursorPaginator`      | 以游標為基礎。適合大量資料       |

## 基本用法

### 查詢建構器的分頁

```php theme={null}
use Illuminate\Support\Facades\DB;

// 每頁 15 筆
$users = DB::table('users')->paginate(15);
```

### Eloquent 的分頁

```php theme={null}
use App\Models\User;

$users = User::paginate(15);

// 附加條件
$users = User::where('votes', '>', 100)->paginate(15);
```

### simplePaginate

若不需要總筆數的計數查詢（只顯示「上一頁」「下一頁」連結），使用 `simplePaginate()` 會更有效率。

```php theme={null}
$users = DB::table('users')->simplePaginate(15);
$users = User::where('active', true)->simplePaginate(15);
```

<Tip>
  若不需要顯示「共 X 筆中的第 Y 筆」，請選 `simplePaginate()`。`paginate()` 會額外執行 `COUNT(*)` 查詢，因此 `simplePaginate()` 較快。
</Tip>

### cursorPaginate（游標分頁）

游標分頁使用 WHERE 子句而非 OFFSET，因此在大量資料時能發揮較高的效能。特別適合無限捲動的 UI。

```php theme={null}
// 必須使用 orderBy
$users = DB::table('users')->orderBy('id')->cursorPaginate(15);
$users = User::where('active', true)->cursorPaginate(15);
```

產生的 URL 內含的是游標字串，而非頁碼。

```
http://example.com/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0
```

<Warning>
  使用游標分頁時 `orderBy` 為必要。此外，排序欄位必須屬於進行分頁的資料表。
</Warning>

### OFFSET 與游標的比較

```sql theme={null}
-- 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 theme={null}
<?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 顯示分頁連結

```blade theme={null}
<div class="container">
    @foreach ($users as $user)
        <p>{{ $user->name }}</p>
    @endforeach

    {{-- 輸出分頁連結（支援 Tailwind CSS） --}}
{{ $users->links() }}
</div>
```

`links()` 方法會自動產生分頁連結的 HTML，並顯示目前頁面前後各 3 頁的連結。

### 調整顯示的連結數量

可使用 `onEachSide()` 變更目前頁面前後顯示的連結數量。

```blade theme={null}
{{-- 顯示目前頁面前後 5 頁 --}}
{{ $users->onEachSide(5)->links() }}
```

## 從請求接收每頁筆數

```php theme={null}
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'));
}
```

## 在同一頁顯示多個分頁器

若在同一畫面顯示 2 個分頁器，兩者都使用 `page` 參數會衝突。可用第 3 個引數變更參數名稱。

```php theme={null}
$users = User::paginate(
    perPage: 15,
    columns: ['*'],
    pageName: 'users'
);

$posts = Post::paginate(
    perPage: 10,
    columns: ['*'],
    pageName: 'posts'
);
```

## URL 自訂

### 變更基礎 URL

```php theme={null}
$users = User::paginate(15);

// 產生 /admin/users?page=N 形式的 URL
$users->withPath('/admin/users');
```

### 附加查詢參數

```php theme={null}
// 在每個頁面連結加入 sort=votes
$users->appends(['sort' => 'votes']);

// 繼承目前請求的所有查詢參數
$users->withQueryString();
```

### 加入 hash fragment

```php theme={null}
// 在 URL 尾端附加 #users
$users->fragment('users');
```

## API 回應（JSON 輸出）

若直接從路由或控制器回傳分頁器，會自動轉為 JSON。

```php theme={null}
use App\Models\User;

Route::get('/api/users', function () {
    return User::paginate(15);
});
```

回應的 JSON 格式：

```json theme={null}
{
    "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 資源結合

若要以 API 資源集合包裝 `paginate()` 的結果，將它傳給 `UserResource::collection()`：

```php theme={null}
use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/api/users', function () {
    $users = User::paginate(15);

    return UserResource::collection($users);
});
```

當你將分頁器傳給 `UserResource::collection()`，分頁資訊會自動作為中繼資料附加。

<Info>
  `cursorPaginate()` 的 JSON 中不會有頁碼，而是 `next_cursor` 與 `prev_cursor`。API 客戶端將這些值作為下一次請求的 `cursor` 參數使用。
</Info>

## 自訂分頁 view

### 直接在 view 中指定

```blade theme={null}
{{-- 使用自訂 view 輸出連結 --}}
{{ $paginator->links('vendor.pagination.custom') }}

{{-- 額外傳入資料 --}}
{{ $paginator->links('vendor.pagination.custom', ['theme' => 'dark']) }}
```

### 將預設 view 改為自訂檔案

首先發布官方 view 再進行自訂。

```shell theme={null}
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`，或建立新的 view 並在 `AppServiceProvider` 指定。

```php theme={null}
use Illuminate\Pagination\Paginator;

public function boot(): void
{
    Paginator::defaultView('vendor.pagination.custom');
    Paginator::defaultSimpleView('vendor.pagination.simple-custom');
}
```

### 使用 Bootstrap CSS

若使用 Bootstrap 而非 Tailwind，可在 `AppServiceProvider` 的 `boot()` 中指定：

```php theme={null}
use Illuminate\Pagination\Paginator;

public function boot(): void
{
    Paginator::useBootstrapFive(); // Bootstrap 5
    // Paginator::useBootstrapFour(); // Bootstrap 4
}
```

## 手動建立分頁器

若想對陣列等既有資料套用分頁，可以直接實例化分頁器類別。

```php theme={null}
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()]
);
```

## 常用的實例方法

```php theme={null}
$paginator = User::paginate(15);

$paginator->currentPage();     // 目前頁碼
$paginator->lastPage();        // 最後頁碼（simplePaginate 無法使用）
$paginator->total();           // 總筆數（simplePaginate 無法使用）
$paginator->perPage();         // 每頁筆數
$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();           // 目前頁面的項目陣列
```

## 總結

<AccordionGroup>
  <Accordion title="分頁方式的選擇">
    * **`paginate()`** — 需要總筆數與頁碼連結時（一般的列表畫面）
    * **`simplePaginate()`** — 僅需「上一頁」「下一頁」連結即可時（較快）
    * **`cursorPaginate()`** — 資料量大、無限捲動、寫入頻繁時（最佳效能）
  </Accordion>

  <Accordion title="Blade 顯示的基本模式">
    ```blade theme={null}
    @foreach ($users as $user)
        <p>{{ $user->name }}</p>
    @endforeach

    {{ $users->links() }}
    ```

    只要將 `paginate()` 的結果傳到 view，用 `links()` 輸出分頁連結即可。
    目前頁面會自動由 `page` 查詢參數偵測。
  </Accordion>

  <Accordion title="API 中的分頁">
    直接從路由回傳分頁器會自動轉為 JSON。
    若要與 API 資源結合，回傳 `UserResource::collection($paginator)`。
    回應中包含 `data`（記錄陣列）以及各項中繼資訊。
  </Accordion>
</AccordionGroup>


## Related topics

- [Laravel Scout](/zh-TW/scout.md)
- [Eloquent API 資源](/zh-TW/eloquent-resources.md)
- [從 Laravel 12 升級到 13 指南](/zh-TW/blog/upgrade-12-to-13.md)
- [在 WSL 中安裝 PHP、Composer 與 Node.js（Windows）](/zh-TW/blog/windows-setup.md)
- [Laravel Telescope](/zh-TW/telescope.md)
