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

# 用 Inertia.js 建構 SPA

> 介紹連結 Laravel 後端與 Vue／React／Svelte 前端的 Inertia.js v3。從無需 API 就能實現 SPA 的機制，到頁面元件、共用資料與表單處理，皆有實務性的解說。

## 什麼是 Inertia.js

想用 React 或 Vue 寫前端，但又不想額外設計、實作、維護 API——解決這個兩難的正是 **Inertia.js**。

Inertia 是一種「膠水」，讓伺服器端的路由與 Controller 維持不變，只用 React、Vue、Svelte 撰寫前端。它不是框架，而是連接既有 Laravel 與 JavaScript 框架的介接層。

<Info>
  目前最新版本為 Inertia v3（2026 年 3 月 26 日發佈）。Laravel 13 的啟動套件（React、Vue、Svelte）皆已支援 Inertia。
</Info>

### 與傳統 SPA、MPA 的差異

| 架構                           | 特徵                 | 課題                   |
| ---------------------------- | ------------------ | -------------------- |
| MPA（傳統 Blade 應用）             | 簡單，容易與 Laravel 整合  | 每次頁面切換都要 full reload |
| SPA（API + 前端分離）              | 互動性高               | API 設計、認證、型別定義的雙重維護  |
| **Inertia（Modern Monolith）** | SPA 的 UX + 伺服器端的簡潔 | 有其獨自的學習成本            |

使用 Inertia 可以從 Laravel Controller 直接把資料傳給 Vue 或 React 元件。不需要定義 REST API，頁面切換也不是全頁重新載入，而是透過 XHR 進行，因此能實現 SPA 般的流暢操作體驗。

***

## 與 Laravel 啟動套件的關係

使用 `laravel new` 建立專案時選擇 React、Vue、Svelte，會自動設置 Inertia。

```shell theme={null}
laravel new my-app
# 於互動式 prompt 選 React / Vue / Svelte 時就會是 Inertia 架構
```

啟動套件會自動準備以下結構：

* `inertiajs/inertia-laravel`（伺服器端 adapter）
* `@inertiajs/react` / `@inertiajs/vue3` / `@inertiajs/svelte`（用戶端 adapter）
* `HandleInertiaRequests` middleware
* 登入、註冊、密碼重設等認證畫面（已用 Inertia 元件實作）

若要手動在既有專案導入 Inertia，需分別安裝伺服器端與用戶端。詳細請參考官方文件的安裝步驟。

***

## `Inertia::render()` 基礎

要從 Laravel Controller 回傳 Inertia response，使用 `Inertia::render()`。第一參數是 JavaScript 元件名稱，第二參數是要當作 prop 傳入的資料。

```php theme={null}
use Inertia\Inertia;

class PostController extends Controller
{
    public function index()
    {
        return Inertia::render('Posts/Index', [
            'posts' => Post::latest()->paginate(10),
        ]);
    }

    public function show(Post $post)
    {
        return Inertia::render('Posts/Show', [
            'post' => $post->only('id', 'title', 'content', 'created_at'),
            'author' => $post->user->only('id', 'name'),
        ]);
    }
}
```

<Tip>
  也可以使用 `inertia()` helper 函式代替 `Inertia::render()`。要用哪一種請團隊統一即可。
</Tip>

元件名稱 `'Posts/Index'` 對應到檔案路徑。React 則對應到 `resources/js/Pages/Posts/Index.jsx`；Vue 則對應到 `resources/js/Pages/Posts/Index.vue`。

***

## 頁面元件的結構

Inertia 的頁面元件就是一般的 Vue/React 元件。從 Controller 傳來的資料以 prop 接收。

```jsx React theme={null}
// resources/js/Pages/Posts/Index.jsx
import { Link } from '@inertiajs/react'

export default function PostsIndex({ posts }) {
    return (
        <div>
            <h1>貼文列表</h1>
            {posts.data.map(post => (
                <article key={post.id}>
                    <h2>
                        <Link href={`/posts/${post.id}`}>{post.title}</Link>
                    </h2>
                </article>
            ))}
        </div>
    )
}
```

```vue Vue theme={null}
<!-- resources/js/Pages/Posts/Index.vue -->
<script setup>
import { Link } from '@inertiajs/vue3'

defineProps({
    posts: Object,
})
</script>

<template>
    <div>
        <h1>貼文列表</h1>
        <article v-for="post in posts.data" :key="post.id">
            <h2>
                <Link :href="`/posts/${post.id}`">{{ post.title }}</Link>
            </h2>
        </article>
    </div>
</template>
```

使用 `<Link>` 元件時，頁面切換會以 XHR 進行，可避免全頁重新載入。寫法與一般 `<a>` 標籤完全相同，但幕後由 Inertia 只替換頁面元件。

***

## 共用資料 — `HandleInertiaRequests` middleware

所有頁面都需要的共通資料（登入使用者資訊、flash 訊息等）可在 `HandleInertiaRequests` middleware 的 `share()` 方法中定義。

```php theme={null}
// app/Http/Middleware/HandleInertiaRequests.php
use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'appName' => config('app.name'),

            'auth' => [
                'user' => $request->user()
                    ? $request->user()->only('id', 'name', 'email')
                    : null,
            ],

            'flash' => [
                'success' => $request->session()->get('success'),
                'error' => $request->session()->get('error'),
            ],
        ]);
    }
}
```

共用資料會自動合併到所有頁面的 prop。

```jsx React theme={null}
// 從 layout 元件存取的例子
import { usePage } from '@inertiajs/react'

export default function Layout({ children }) {
    const { auth, flash } = usePage().props

    return (
        <main>
            <header>
                {auth.user ? `登入中：${auth.user.name}` : '訪客'}
            </header>
            {flash.success && <div className="alert-success">{flash.success}</div>}
            <article>{children}</article>
        </main>
    )
}
```

<Info>
  共用資料會包含在每次請求裡，建議只放最必要的資料。使用 `fn()` 進行延遲求值，則只會在實際需要的請求中被求值。
</Info>

***

## 表單送出與驗證錯誤處理

Inertia 的表單處理與 Laravel 驗證自然整合。使用 `useForm()` helper 可以簡潔地處理表單狀態、送出與錯誤顯示。

### Controller 端

```php theme={null}
class PostController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'title'   => ['required', 'string', 'max:255'],
            'content' => ['required', 'string'],
        ]);

        Post::create($validated + ['user_id' => auth()->id()]);

        return redirect()->route('posts.index')
            ->with('success', '貼文已建立。');
    }
}
```

發生驗證錯誤時，Laravel 會自動重新導向回表單頁面，並將錯誤存到 session。Inertia 會自動偵測並以 `errors` prop 傳給頁面。

### 前端

```jsx React theme={null}
// resources/js/Pages/Posts/Create.jsx
import { useForm } from '@inertiajs/react'

export default function PostsCreate() {
    const { data, setData, post, processing, errors } = useForm({
        title: '',
        content: '',
    })

    function handleSubmit(e) {
        e.preventDefault()
        post('/posts')
    }

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label>標題</label>
                <input
                    value={data.title}
                    onChange={e => setData('title', e.target.value)}
                />
                {errors.title && <p className="error">{errors.title}</p>}
            </div>

            <div>
                <label>內容</label>
                <textarea
                    value={data.content}
                    onChange={e => setData('content', e.target.value)}
                />
                {errors.content && <p className="error">{errors.content}</p>}
            </div>

            <button type="submit" disabled={processing}>
                {processing ? '送出中...' : '發佈'}
            </button>
        </form>
    )
}
```

```vue Vue theme={null}
<!-- resources/js/Pages/Posts/Create.vue -->
<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm({
    title: '',
    content: '',
})

function submit() {
    form.post('/posts')
}
</script>

<template>
    <form @submit.prevent="submit">
        <div>
            <label>標題</label>
            <input v-model="form.title" />
            <p v-if="form.errors.title" class="error">{{ form.errors.title }}</p>
        </div>

        <div>
            <label>內容</label>
            <textarea v-model="form.content"></textarea>
            <p v-if="form.errors.content" class="error">{{ form.errors.content }}</p>
        </div>

        <button type="submit" :disabled="form.processing">
            {{ form.processing ? '送出中...' : '發佈' }}
        </button>
    </form>
</template>
```

當驗證錯誤回來時，`useForm()` 會保留輸入內容並顯示錯誤，使用者不必重打，提升體驗。

***

## 適合與不適合的情境

### Inertia 適合的情境

* 熟悉 Laravel 的團隊想做 SPA 般 UI
* 想在 Laravel 端集中處理認證、授權、驗證
* 管理介面、內部工具等 SEO 需求較低的應用
* 想避開額外設計、維護 API 成本的專案

### 不適合的情境

* 有多個外部用戶端（例如行動 App）共用同一 API
* SEO 極為重要的內容型網站（雖可 SSR 但會變複雜）
* 微前端結構，前端由完全獨立的團隊開發時

<Tip>
  Inertia 也支援伺服器端渲染（SSR）。若有 SEO 需求的頁面可考慮 SSR 選項。Laravel 啟動套件也內建了 SSR 設定。
</Tip>

***

## Inertia v3 的主要變更

Inertia v3 於 2026 年 3 月 26 日發佈。以下介紹自 v2 的主要變更。

### 移除 Axios — 改為輕量內建 XHR client

v3 移除 Axios，改用更輕量的內建 XHR client。大多數應用不必修改程式碼。Axios interceptor 可直接改用內建 interceptor。若仍要使用 Axios，也可以透過 Axios adapter。

### 用 `@inertiajs/vite` plugin 簡化 SSR

導入新的 Vite plugin 後，頁面元件的自動解析與 SSR 設定大幅簡化。開發時 SSR 僅執行 `npm run dev` 即可運作，不再需要另開 Node server。

```bash theme={null}
npm install @inertiajs/vite@^3.0
```

### `useHttp` hook — 不帶頁面切換的 HTTP 請求

使用 `useHttp` hook 可以在不觸發頁面切換（Inertia visit）的情況下向伺服器送出 HTTP 請求。適合對話框儲存、背景處理等，希望維持目前頁面同時通訊的場景。

### 樂觀 UI 更新（Optimistic Updates）

`useForm` 與 router 層級皆支援樂觀更新。在未等伺服器回應下立即更新 UI，失敗時自動回滾。

### Layout Props

使用 `useLayoutProps` hook 可以從頁面元件把資料傳給常駐 layout。原本只能靠共用資料或頁面 prop 處理的「特定頁面 layout 狀態控制」變得簡潔。

### 需求變更

v3 提升了以下最低版本要求。

| 項目      | v2   | v3   |
| ------- | ---- | ---- |
| PHP     | 8.1+ | 8.2+ |
| Laravel | 10+  | 11+  |
| React   | 18+  | 19+  |
| Svelte  | 4+   | 5+   |

### 移除 `Inertia::lazy()`

v2 已棄用的 `Inertia::lazy()` 於 v3 完全移除。改用 `Inertia::optional()`。

```php theme={null}
// v2（已棄用）
'users' => Inertia::lazy(fn () => User::all()),

// v3
'users' => Inertia::optional(fn () => User::all()),
```

### 事件名稱變更

| v2          | v3              |
| ----------- | --------------- |
| `invalid`   | `httpException` |
| `exception` | `networkError`  |

### 移除 `future` 選項

v2 中作為實驗性選項提供的 `future` 設定區塊已被移除。這些選項皆已預設啟用。請從 `createInertiaApp` 設定中移除 `future` 區塊。

<Info>
  自 v2 升級的詳細步驟請參考 <a href="https://inertiajs.com/docs/v3/getting-started/upgrade-guide">官方升級指南</a>。v2 的 bug 修正將提供至 2026 年 9 月 26 日，安全修正提供至 2027 年 3 月 26 日。
</Info>

***

## 總結

Inertia.js 回應了「維持 Laravel 原貌但用 React/Vue 寫前端」這個現實需求的工具。最大優勢是不需設計 API，就能從 Controller 直接把資料傳給元件的簡潔性。

從 Laravel 13 啟動套件對應 Inertia 也可看出，Inertia 在 Laravel 生態系中的定位已相當穩固。若說 Livewire 是「只用 PHP 就能做動態 UI」的路線，那 Inertia 則是「不損失伺服器端簡潔性，卻能發揮 JavaScript 框架威力」的路線。

究竟該選哪一個取決於團隊技能與專案需求，但「維持 Laravel Controller 現狀，用 React 做畫面」的情境下，Inertia 是最自然的選擇。

<Card title="Inertia.js 官方文件" icon="book-open" href="https://inertiajs.com">
  Inertia v3 的完整功能請參考官方文件。
</Card>


## Related topics

- [部落格](/zh-TW/blog/index.md)
- [Vue.js 入門 — 搭配 Inertia × Laravel 使用的基礎知識](/zh-TW/blog/vue-introduction.md)
- [起始套件（Starter Kit）](/zh-TW/starter-kits.md)
- [使用 Homebrew 安裝 PHP、Composer、Node.js（macOS）](/zh-TW/blog/mac-setup.md)
- [前端](/zh-TW/frontend.md)
