Inertia js is a glue layer that lets you build a modern single-page application (SPA) with Vue or React on top of your Laravel app without writing a separate REST or GraphQL API. In the classic approach the backend is a standalone API, the frontend consumes it with fetch, you juggle tokens for authentication, and you end up writing the same validation logic twice. Inertia removes that duplication: your controllers return a component name plus data (props) instead of HTML, and the browser renders that component without a full page reload.
What exactly is Inertia.js?
Inertia is not a framework; it is often called "the framework-less framework." On the server you keep using your Laravel routes and controllers exactly as before. The difference is that when you return a page you call Inertia::render('Users/Index', [...]) instead of a Blade view. On the first request the server sends a full HTML document; for every subsequent navigation Inertia returns only JSON over XHR in the background and the client-side router swaps the component. The user gets a SPA experience while you keep writing a server-routed app.
There are three core pieces:
- A server adapter — the official
inertiajs/inertia-laravelpackage for Laravel. - A client adapter —
@inertiajs/vue3/@inertiajs/reactfor Vue 3, React or Svelte. - A build tool — Vite, which is Laravel's default.
Installing in a new Laravel project
The fastest route is to use Laravel's official starter kits, which ship Inertia + Vue or React preconfigured. But adding it manually to an existing project is straightforward too. First install the server package:
composer require inertiajs/inertia-laravel
php artisan inertia:middleware
The inertia:middleware command generates the HandleInertiaRequests middleware; register it in your web middleware group in bootstrap/app.php. Then prepare the root Blade template — this is the app's single HTML skeleton. In resources/views/app.blade.php:
<!DOCTYPE html>
<html>
<head>
@vite(['resources/js/app.js'])
@inertiaHead
</head>
<body>
@inertia
</body>
</html>
Wiring up the client side
Let's continue with a Vue example. Install the NPM packages and create the entry file:
npm install @inertiajs/vue3 vue
npm install --save-dev @vitejs/plugin-vue
In resources/js/app.js, boot the Inertia app:
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
createInertiaApp({
resolve: name =>
import(`./Pages/${name}.vue`),
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
},
})
If you use React the logic is the same; you simply pull in the @inertiajs/react package and a setup that mounts with createRoot. The resolve function maps the component name coming from the controller (e.g. Users/Index) to the real file inside the Pages folder.
Returning a page from a controller
This is the good part: there is no JSON resource, serializer or separate API route anymore. A controller returns a component and its props directly.
use Inertia\Inertia;
class UserController extends Controller
{
public function index()
{
return Inertia::render('Users/Index', [
'users' => User::query()
->select('id', 'name', 'email')
->get(),
]);
}
}
On the other side the resources/js/Pages/Users/Index.vue component receives that prop:
<script setup>
import { Link } from '@inertiajs/vue3'
defineProps({ users: Array })
</script>
<template>
<ul>
<li v-for="user in users" :key="user.id">
<Link :href="`/users/${user.id}`">{{ user.name }}</Link>
</li>
</ul>
</template>
The <Link> component renders an ordinary <a> but intercepts the click and performs an Inertia visit instead of a full page refresh. To submit forms there is the useForm helper; it automatically pulls validation errors from Laravel's session error bag and exposes them in form.errors.
Shared data, SSR and other strengths
The share() method in the HandleInertiaRequests middleware lets you define global props that flow automatically to every page — ideal for the authenticated user, flash messages or permission data. If SEO or first-load performance is critical, you can enable Inertia's server-side rendering (SSR); php artisan inertia:start-ssr runs a Node process that pre-renders the page on the server. With lazily evaluated props (Inertia::lazy()) and partial reloads (the only option) you cut network traffic by shipping only the data you actually need.
Inertia's fundamental trade-off is this: you do not get a truly decoupled API. If you ever need to expose the same backend to a mobile app or third-party integrations, you will still have to write an API layer. But for apps whose only consumer is their own web frontend, Inertia eliminates a substantial amount of duplicate code.
Frequently Asked Questions
Does Inertia.js replace a separate API?
For your web frontend, yes — because controllers render components directly, you do not need a separate REST/GraphQL layer. But if you need to serve the same data to other clients such as a mobile app, you should still write a conventional API.
Should I choose Vue or React?
Both are officially supported and the server side of Inertia is identical. The choice comes down to your team's experience; the logic and API stay almost one-to-one, only the component syntax changes.
Is it good for SEO?
The default client-side rendering may be enough for search engines, but if you want a guarantee, enable Inertia's SSR mode; the page's first HTML is then produced on the server and bots see the full content.
Want to build a modern SPA or migrate an existing Laravel project to Inertia? I can help end to end, from backend architecture to the Vue/React interface. Get in touch and let's talk about your project.