Introduction
Inertia lets you build modern single-page applications with Laravel's server-side routing and Vue or React page components. No REST API, no client-side router — just controllers returning page responses.
Key Concepts
Inertia::render: The controller helper that returns a page component and its props instead of a Blade view.- Page component: A Vue or React component in
resources/js/Pages/corresponding to a route. <Link>: Inertia's client-side navigation component that swaps pages via XHR without a full reload.- Props: Data passed from controller to page component via the second argument of
Inertia::render. - Shared data: Values merged into every page's props via the
HandleInertiaRequestsmiddleware (authenticated user, flash messages, feature flags).
Real World Context
When you want SPA smoothness (instant navigation, no full reloads, scroll preservation) but don't want to build and maintain a separate JSON API, Inertia is the pragmatic choice. Controllers stay classic Laravel; pages become Vue/React components.
Deep Dive
Inertia.js lets you build modern single-page applications using server-side routing and controllers. No need for an API—connect your Laravel backend directly to Vue or React.
What is Inertia?
Inertia is the glue between your Laravel backend and JavaScript frontend:
┌─────────────────────────────────────────────────────────────────┐
│ Traditional SPA │
│ │
│ Frontend (React/Vue) ←──── API ────→ Backend (Laravel) │
│ (routing, (controllers, │
│ state, business logic, │
│ components) database) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Inertia.js │
│ │
│ Frontend (React/Vue) ←── Inertia ──→ Backend (Laravel) │
│ (components (controllers, │
│ only!) routing, │
│ business logic, │
│ database) │
└─────────────────────────────────────────────────────────────────┘
With Inertia:
- No API to build
- Server-side routing (like classic Laravel)
- Full SPA experience
- Use Vue or React components
- SEO friendly (server-side rendering available)
Installation with a Starter Kit
Laravel 13 replaced Breeze with first-party starter kits that scaffold the frontend, routing, and authentication flows in one step. Pick one when you create the project:
bashlaravel new my-app # Choose one of: # - React (Inertia 3 + React) # - Vue (Inertia 3 + Vue) # - Svelte (Inertia 3 + Svelte) # - Livewire (full-stack PHP with Livewire 4)
The three Inertia kits ship with Inertia 3, Tailwind v4, and authentication scaffolding preconfigured; the Livewire kit is the alternative for teams who want to stay in Blade.
Manual Installation
bashcomposer require inertiajs/inertia-laravel npm install @inertiajs/vue3 # or @inertiajs/react
How It Works
Server Side (Laravel)
php// routes/web.php Route::get('/users', [UserController::class, 'index']); Route::get('/users/{user}', [UserController::class, 'show']); Route::post('/users', [UserController::class, 'store']);
php// app/Http/Controllers/UserController.php use Inertia\Inertia; class UserController extends Controller { public function index() { return Inertia::render('Users/Index', [ 'users' => User::all(), ]); } public function show(User $user) { return Inertia::render('Users/Show', [ 'user' => $user, ]); } public function store(Request $request) { $validated = $request->validate([ 'name' => 'required', 'email' => 'required|email|unique:users', ]); User::create($validated); return redirect()->route('users.index'); } }
Client Side (Vue)
vue<!-- resources/js/Pages/Users/Index.vue --> <script setup> import { Link } from '@inertiajs/vue3' defineProps({ users: Array }) </script> <template> <h1>Users</h1> <Link href="/users/create">Create User</Link> <ul> <li v-for="user in users" :key="user.id"> <Link :href="`/users/${user.id}`">{{ user.name }}</Link> </li> </ul> </template>
vue<!-- resources/js/Pages/Users/Show.vue --> <script setup> defineProps({ user: Object }) </script> <template> <h1>{{ user.name }}</h1> <p>{{ user.email }}</p> </template>
The Link Component
Inertia's <Link> replaces anchor tags for SPA navigation:
vue<script setup> import { Link } from '@inertiajs/vue3' </script> <template> <!-- Basic link --> <Link href="/users">Users</Link> <!-- With method --> <Link href="/logout" method="post" as="button">Logout</Link> <!-- Preserve scroll position --> <Link href="/users" preserve-scroll>Users</Link> <!-- With data --> <Link href="/users" :data="{ page: 2 }">Page 2</Link> </template>
Forms
Use Inertia's form helper:
vue<script setup> import { useForm } from '@inertiajs/vue3' const form = useForm({ name: '', email: '', }) const submit = () => { form.post('/users') } </script> <template> <form @submit.prevent="submit"> <div> <input v-model="form.name" placeholder="Name"> <span v-if="form.errors.name">{{ form.errors.name }}</span> </div> <div> <input v-model="form.email" type="email" placeholder="Email"> <span v-if="form.errors.email">{{ form.errors.email }}</span> </div> <button type="submit" :disabled="form.processing"> {{ form.processing ? 'Saving...' : 'Create User' }} </button> </form> </template>
Shared Data
Share data with all pages via middleware:
php// app/Http/Middleware/HandleInertiaRequests.php public function share(Request $request): array { return array_merge(parent::share($request), [ 'auth' => [ 'user' => $request->user(), ], 'flash' => [ 'success' => $request->session()->get('success'), 'error' => $request->session()->get('error'), ], ]); }
Access in components:
vue<script setup> import { usePage } from '@inertiajs/vue3' const page = usePage() const user = page.props.auth.user const flash = page.props.flash </script> <template> <div v-if="flash.success" class="alert-success"> {{ flash.success }} </div> <p v-if="user">Welcome, {{ user.name }}</p> </template>
Common Pitfalls
- Using
<a>tags for internal navigation — Full page reloads, no preserved state. Always use<Link>. - Returning
view()instead ofInertia::render()— The controller returns a Blade template instead of an Inertia response, breaking client-side navigation. - Mismatching page component paths —
Inertia::render('Users/Index')looks forresources/js/Pages/Users/Index.vue(or.tsx). Capitalization and forward slashes matter.
Best Practices
- Use
<Link>for all internal navigation — It preserves scroll, supports prefetching, and exposesmethod/dataprops for non-GET links. - Organize
Pages/*by feature — Mirror the route structure sousers.indexmaps toPages/Users/Index. - Centralize cross-page values with shared data — Auth, flash messages, feature flags, CSRF token all belong in
HandleInertiaRequests::share().
Summary
- Inertia bridges Laravel's server-side routing with Vue/React page components.
- Controllers return
Inertia::render('Pages/Name', $props)instead of views. <Link>performs client-side navigation; regular<a>tags trigger full reloads.- Forms use
useForm()for reactive state, server-side validation, and loading flags. - Laravel 13 starter kits ship with Inertia 3 and the React/Vue page structure pre-configured.