Skip to main content

What is Inertia.js?

You want to build the frontend in React or Vue, but you’d rather not design, implement, and maintain a separate API. Inertia.js solves exactly that dilemma. Inertia is the “glue” that lets you keep your server-side routing and controllers as they are while writing your frontend in React, Vue, or Svelte. It isn’t a framework—it acts as an adapter layer connecting an existing Laravel app to a JavaScript framework.
The current latest version is Inertia v3 (released March 26, 2026). Laravel 13’s starter kits (React, Vue, Svelte) already support Inertia.

How it differs from traditional SPAs and MPAs

ArchitectureCharacteristicsChallenges
MPA (traditional Blade app)Simple, integrates easily with LaravelFull reloads on every page change
SPA (API + separate frontend)High interactivityDuplicated management of API design, auth, and types
Inertia (modern monolith)SPA UX + server-side simplicityHas its own learning curve
With Inertia, you can pass data directly from Laravel controllers to Vue or React components. There’s no need to define a REST API, and page transitions happen via XHR rather than a full browser reload, giving you SPA-like smooth interactions.

Relationship with Laravel starter kits

If you choose React, Vue, or Svelte when running laravel new, Inertia is set up automatically.
laravel new my-app
# Selecting React / Vue / Svelte in the interactive prompt yields an Inertia setup
The starter kits configure everything for you:
  • inertiajs/inertia-laravel (server-side adapter)
  • @inertiajs/react / @inertiajs/vue3 / @inertiajs/svelte (client adapters)
  • The HandleInertiaRequests middleware
  • Auth screens like login, registration, and password reset (already implemented as Inertia components)
To manually add Inertia to an existing project, install the server-side and client-side pieces separately. See the official installation docs for details.

The basics of Inertia::render()

Use Inertia::render() to return an Inertia response from a Laravel controller. The first argument is the JavaScript component name, and the second is the data to pass as props.
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'),
        ]);
    }
}
You can also use the inertia() helper function instead of Inertia::render(). Pick one and standardize on it across your team.
The component name 'Posts/Index' maps to a file path. For React it’s resources/js/Pages/Posts/Index.jsx; for Vue it’s resources/js/Pages/Posts/Index.vue.

Structure of a page component

Inertia page components are ordinary Vue or React components. Data passed from the controller arrives as props.
React
// resources/js/Pages/Posts/Index.jsx
import { Link } from '@inertiajs/react'

export default function PostsIndex({ posts }) {
    return (
        <div>
            <h1>Posts</h1>
            {posts.data.map(post => (
                <article key={post.id}>
                    <h2>
                        <Link href={`/posts/${post.id}`}>{post.title}</Link>
                    </h2>
                </article>
            ))}
        </div>
    )
}
Vue
<!-- resources/js/Pages/Posts/Index.vue -->
<script setup>
import { Link } from '@inertiajs/vue3'

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

<template>
    <div>
        <h1>Posts</h1>
        <article v-for="post in posts.data" :key="post.id">
            <h2>
                <Link :href="`/posts/${post.id}`">{{ post.title }}</Link>
            </h2>
        </article>
    </div>
</template>
Using the <Link> component routes page transitions via XHR, avoiding a full page reload. It’s written just like a normal <a> tag, but under the hood Inertia only swaps the page component.

Shared data — the HandleInertiaRequests middleware

Define data needed on every page (the logged-in user, flash messages, etc.) in the share() method of the HandleInertiaRequests middleware.
// 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'),
            ],
        ]);
    }
}
Shared data is automatically merged into every page’s props.
React
// Example accessing shared data from a layout component
import { usePage } from '@inertiajs/react'

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

    return (
        <main>
            <header>
                {auth.user ? `Signed in as: ${auth.user.name}` : 'Guest'}
            </header>
            {flash.success && <div className="alert-success">{flash.success}</div>}
            <article>{children}</article>
        </main>
    )
}
Because shared data is included on every request, keep it to the minimum you need. Wrapping values in fn() for lazy evaluation ensures they’re only evaluated when actually required.

Form submission and validation error handling

Form handling in Inertia integrates naturally with Laravel’s validation. The useForm() helper makes form state management, submission, and error display straightforward.

Controller side

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', 'Post created.');
    }
}
When validation fails, Laravel automatically redirects back to the form page and stores errors in the session. Inertia detects this automatically and passes them to the page as an errors prop.

Frontend side

React
// 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>Title</label>
                <input
                    value={data.title}
                    onChange={e => setData('title', e.target.value)}
                />
                {errors.title && <p className="error">{errors.title}</p>}
            </div>

            <div>
                <label>Content</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 ? 'Submitting...' : 'Post'}
            </button>
        </form>
    )
}
Vue
<!-- 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>Title</label>
            <input v-model="form.title" />
            <p v-if="form.errors.title" class="error">{{ form.errors.title }}</p>
        </div>

        <div>
            <label>Content</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 ? 'Submitting...' : 'Post' }}
        </button>
    </form>
</template>
When validation errors are returned, useForm() displays them while retaining the input values. Users don’t have to re-enter data, improving the experience.

Good fits and poor fits

When Inertia is a good fit

  • A team that knows Laravel well wants to build an SPA-like UI
  • You want to centrally manage auth, authorization, and validation on the Laravel side
  • Admin panels, internal tools, and other apps where SEO isn’t a top priority
  • Projects that want to avoid the cost of designing and maintaining a separate API

When it isn’t a good fit

  • Multiple external clients (like a mobile app) need to share the same API
  • Content sites where SEO is very important (SSR can help, but adds complexity)
  • Micro-frontend setups where the frontend is developed by a completely independent team
Inertia also supports server-side rendering (SSR). If you have pages that need SEO, consider the SSR option. Laravel’s starter kits ship with SSR configuration too.

Highlights of Inertia v3

Inertia v3 was released on March 26, 2026. Here are the main changes from v2.

Axios removed — replaced with a lightweight built-in XHR client

v3 removes Axios in favor of a lighter, built-in XHR client. Most applications require no code changes. Axios interceptors can be migrated directly to the built-in interceptors. If you’d rather keep using Axios, it’s still available via the Axios adapter.

Simpler SSR with the @inertiajs/vite plugin

Introducing the new Vite plugin dramatically simplifies auto-resolution of page components and SSR configuration. SSR now runs during development simply by running npm run dev, with no separate Node server required.
npm install @inertiajs/vite@^3.0

useHttp hook — HTTP requests without page transitions

The useHttp hook lets you send HTTP requests to the server without causing an Inertia visit. Useful when you want to communicate while staying on the current page—for saving modals or handling background work, for instance.

Optimistic UI updates

Optimistic updates are now supported at both the useForm and router levels. The UI is updated immediately without waiting for the server response, and rolls back automatically if the request fails.

Layout props

The useLayoutProps hook lets you pass data from page components up to a persistent layout. It cleanly handles “controlling layout state for a specific page,” which previously required shared data or page props.

Requirements changes

v3 raises several minimum versions:
Itemv2v3
PHP8.1+8.2+
Laravel10+11+
React18+19+
Svelte4+5+

Removal of Inertia::lazy()

Inertia::lazy(), which was deprecated in v2, has been fully removed in v3. Use Inertia::optional() instead.
// v2 (deprecated)
'users' => Inertia::lazy(fn () => User::all()),

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

Event name changes

v2v3
invalidhttpException
exceptionnetworkError

Removal of the future option

The future configuration block, offered as experimental in v2, has been removed. All of those options are now enabled by default. Remove the future block from your createInertiaApp configuration.
For details on upgrading from v2, see the official upgrade guide. v2 receives bug fixes through September 26, 2026 and security fixes through March 26, 2027.

Summary

Inertia.js addresses the very real need to “keep Laravel as is, but write the frontend in React/Vue.” Its greatest strength is the simplicity of passing data directly from controllers to components without designing an API. The fact that Laravel 13’s starter kits support Inertia shows how established Inertia’s position in the Laravel ecosystem has become. Where Livewire builds dynamic UIs using only PHP, Inertia leverages the power of a JavaScript framework without sacrificing server-side simplicity. Which one to choose depends on your team’s skills and project requirements, but for the case “I want to keep my Laravel controllers as-is and build the UI in React,” Inertia is the most natural choice.

Inertia.js official docs

See the official documentation for all Inertia v3 features.
Last modified on July 13, 2026