> ## 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 保留服务端的路由和控制器,只让你把前端换成用 React、Vue、Svelte 来写,它就是这两者之间的“粘合剂”。它不是一个框架,而是作为一层适配器,把现有的 Laravel 和 JavaScript 框架连接起来。

<Info>
  当前最新版本是 Inertia v3(2026 年 3 月 26 日发布)。Laravel 13 的启动套件(React、Vue、Svelte)已内置 Inertia 支持。
</Info>

### 与传统 SPA / MPA 的差异

| 架构                | 特点                 | 挑战                   |
| ----------------- | ------------------ | -------------------- |
| MPA(传统 Blade 应用)  | 简单,与 Laravel 集成容易  | 每次页面跳转都要整页刷新         |
| SPA(API + 前端分离)   | 交互性强               | API 设计、认证、类型定义都要双重维护 |
| **Inertia(现代单体)** | SPA 的用户体验 + 服务端的简洁 | 有一定的学习成本             |

使用 Inertia 后,可以直接从 Laravel 的控制器把数据传给 Vue 组件或 React 组件。无需定义 REST API,页面跳转也不是浏览器的整页刷新,而是通过 XHR 完成,从而实现 SPA 般的流畅体验。

***

## 与 Laravel 启动套件的关系

使用 `laravel new` 创建项目时,如果选择了 React、Vue、Svelte,Inertia 会被自动配置好。

```shell theme={null}
laravel new my-app
# 通过交互式提示选择 React / Vue / Svelte 即得到 Inertia 结构
```

启动套件中会自动准备好如下内容:

* `inertiajs/inertia-laravel`(服务端适配器)
* `@inertiajs/react` / `@inertiajs/vue3` / `@inertiajs/svelte`(客户端适配器)
* `HandleInertiaRequests` 中间件
* 登录 / 注册 / 密码重置等认证页面(已经使用 Inertia 组件实现)

在已有项目中手动引入 Inertia 时,需要分别安装服务端和客户端。详细步骤请参考官方文档中的安装说明。

***

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

在 Laravel 控制器中,通过 `Inertia::render()` 返回 Inertia 响应。第一个参数是 JavaScript 组件名,第二个参数是要作为 props 传给组件的数据。

```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::render()` 外,也可以使用 `inertia()` 辅助函数。团队内统一其中一种风格即可。
</Tip>

组件名 `'Posts/Index'` 对应文件路径。在 React 项目中对应 `resources/js/Pages/Posts/Index.jsx`,在 Vue 项目中对应 `resources/js/Pages/Posts/Index.vue`。

***

## 页面组件的结构

Inertia 的页面组件就是普通的 Vue/React 组件。从控制器传来的数据会作为 props 被接收。

```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` 中间件

所有页面都需要的公共数据(登录用户信息、闪存消息等),可以在 `HandleInertiaRequests` 中间件的 `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'),
            ],
        ]);
    }
}
```

共享数据会自动被合并到所有页面的 props 中。

```jsx React theme={null}
// 在布局组件中访问的示例
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()` 辅助器,可以以简洁的方式实现表单状态管理、提交、错误展示。

### 控制器侧

```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 客户端

v3 移除了 Axios,替换为更轻量的内置 XHR 客户端。大部分应用无需改代码。Axios 拦截器可以直接迁移到内置拦截器。如果还想继续使用 Axios,可以通过 Axios 适配器接入。

### `@inertiajs/vite` 插件简化 SSR

引入新的 Vite 插件后,页面组件的自动解析和 SSR 配置都被大幅简化。开发时的 SSR 只需 `npm run dev` 就能工作,不再需要单独启动 Node 服务器。

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

### `useHttp` hook —— 不引发页面跳转的 HTTP 请求

`useHttp` hook 让你可以向服务器发送 HTTP 请求,同时不触发页面跳转(Inertia visit)。在模态框保存或后台处理等需要保持当前页面的同时进行通信的场景中很有用。

### 乐观 UI 更新(Optimistic Updates)

在 `useForm` 及 router 层面提供了乐观更新支持。无需等待服务器响应就立即更新 UI,失败时会自动回滚。

### 布局 Props(Layout Props)

使用 `useLayoutProps` hook 后,可以把数据从页面组件传给持久化布局。以前只能通过共享数据或页面 props 来实现的“特定页面控制布局状态”的场景现在能轻松写出来了。

### 需求变更

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 升级到 v3 的详细步骤请参考 <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,就能直接从控制器把数据传给组件。

Laravel 13 的启动套件已支持 Inertia,这也说明 Inertia 在 Laravel 生态中的地位已经确立。如果说 Livewire 是用纯 PHP 构建动态 UI 的方式,那么 Inertia 就是让你在不牺牲服务端简洁性的前提下用上 JavaScript 框架能力的方式。

具体选哪个,取决于团队技能栈和项目需求。但当你想“保留 Laravel 的控制器,同时用 React 写画面”时,Inertia 会是最自然的选择。

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


## Related topics

- [Vue.js 入门 —— 与 Inertia × Laravel 搭配使用的基础](/zh/blog/vue-introduction.md)
- [启动套件](/zh/starter-kits.md)
- [前端](/zh/frontend.md)
- [React 入门 —— 与 Inertia × Laravel 搭配使用的基础](/zh/blog/react-introduction.md)
- [认证入门](/zh/authentication.md)
