A modern single-page application does not need a separate frontend and backend team.
With Laravel, Vue, and Inertia, you can keep server-side routing, controllers, validation, and authentication while delivering a fast Vue interface. You get the flow of an SPA without building and maintaining a separate API for every screen.
Laravel remains the productive PHP web framework. Vue handles the interface. Inertia connects both layers through page visits and server-provided props.
This guide covers the architecture, Inertia 3 features, SSR, state management, authentication, and practical project patterns.
Laravel, Vue, and Inertia: A productive boundary
Inertia is not a JavaScript router. It is a client adapter that lets Laravel return page data to Vue components.
A typical request looks like this:
- Vue links to a Laravel route.
- Laravel runs middleware, authorization, and controller logic.
- The controller returns an Inertia page and its props.
- Inertia updates the Vue component without a full browser reload.
use Inertia\Inertia;
use Illuminate\Support\Facades\Route;
Route::get('/projects', function () {
return Inertia::render('Projects/Index', [
'projects' => Project::latest()->paginate(20),
]);
})->middleware('auth');
The Vue page receives the projects prop and renders it like any other component.
This approach keeps application logic close to Laravel. You can use policies, form requests, Eloquent resources, queues, events, and validation without reproducing them in a standalone API layer.
You can still build a REST API with PHP when mobile clients, third-party integrations, or separate frontends need one. Inertia simply avoids forcing every internal screen through that boundary.
Start with the official Vue starter kit
The Laravel Vue starter kit provides a strong foundation. It uses Vue 3 Composition API, TypeScript, Tailwind, shadcn-vue, and Inertia 3.
Create an application with the Laravel installer, select Vue during setup, then install the frontend dependencies:
laravel new operations-hub
cd operations-hub
npm install
npm run build
composer run dev
The starter kit keeps its frontend code inside resources/js. Pages, layouts, components, composables, and types remain part of the Laravel application.
That structure works well for teams. Backend developers can follow familiar controllers and routes. Vue developers can work inside a clear component system. Everyone shares one deployment pipeline.
Design pages around server-owned data
Inertia works best when the server owns data access and the client owns presentation.
Keep database queries in controllers, query objects, or dedicated actions. Return only the fields required by the page.
return Inertia::render('Orders/Index', [
'orders' => OrderResource::collection(
Order::query()
->with('customer:id,name')
->latest()
->paginate(25)
),
'filters' => $request->only(['status', 'search']),
]);
This design prevents large payloads and keeps authorization in one place. It also gives you Laravel’s validation and policies without creating duplicate frontend rules.
Use partial reloads when only part of a page needs fresh data:
import { router } from '@inertiajs/vue3'
router.reload({
only: ['orders'],
})
For repeated page updates, Inertia 3 provides usePoll.

Inertia 3: Polling without request clutter
usePoll periodically reloads the current page. It automatically stops when the component unmounts.
<script setup>
import { usePoll } from '@inertiajs/vue3'
const { polling, start, stop } = usePoll(
5000,
{ only: ['stats'] },
)
</script>
<template>
<button v-if="polling" @click="stop">
Pause updates
</button>
<button v-else @click="start">
Resume updates
</button>
</template>
Use only to keep polling narrow. A dashboard should refresh its active-user count, not reload every chart and navigation prop.
Inertia 3 also provides polling modes:
-
overlapis the default. Every tick starts a request, even if the previous request remains active. -
cancelaborts the current request before starting the next one. -
restwaits for the previous request to finish, then starts the interval.
usePoll(
5000,
{ only: ['stats'] },
{ mode: 'rest' },
)
Choose overlap for small, fast read-only endpoints. Choose cancel when only the newest response matters. Choose rest for slow or rate-limited operations.
Background tabs throttle polling by 90 percent by default. Set keepAlive: true only when the update must continue while the tab is hidden.
useHttp: Keep auxiliary requests out of navigation
Not every request should change the current page.
Inertia 3’s useHttp hook handles standalone HTTP requests. It provides reactive state similar to useForm, but it does not trigger an Inertia visit or change browser history.
This fits search suggestions, autosave, file uploads, and auxiliary JSON endpoints.
<script setup>
import { useHttp } from '@inertiajs/vue3'
const search = useHttp({
query: '',
})
function findResults() {
search.get('/api/search')
}
</script>
<template>
<input
v-model="search.query"
@input="findResults"
placeholder="Search"
>
<span v-if="search.processing">
Searching...
</span>
</template>
The hook includes get, post, put, patch, delete, and submit methods. It also exposes errors, processing, progress, wasSuccessful, and isDirty.
Use useHttp when the response should remain local to a component. Use an Inertia visit or router.reload when the server response should update page props.
For validation-heavy forms, Laravel Precognition can add real-time validation:
const form = useHttp({
name: '',
email: '',
}).withPrecognition('post', '/api/users')
Deferred props: Load the page before the heavy data
Deferred props keep slow data out of the initial response.
return Inertia::render('Dashboard', [
'summary' => $summary,
'activity' => Inertia::defer(
fn () => Activity::latest()->limit(100)->get()
),
]);
The dashboard can render its essential summary first. Inertia then requests activity in the background.
On the Vue side, use Deferred to control the loading state:
<script setup>
import { Deferred } from '@inertiajs/vue3'
</script>
<template>
<Deferred data="activity">
<template #fallback>
<div class="skeleton">Loading activity...</div>
</template>
<ActivityList :items="activity" />
</Deferred>
</template>
Inertia 3 adds a reloading slot value. It lets you keep existing content visible while a partial reload runs.
<Deferred data="activity" #default="{ reloading }">
<div :class="{ 'opacity-60': reloading }">
<ActivityList :items="activity" />
</div>
<template #fallback>
<div>Loading activity...</div>
</template>
</Deferred>
For optional data, use rescue behavior on the server:
'activity' => Inertia::defer(
fn () => Activity::latest()->limit(100)->get(),
rescue: true
),
If the deferred request fails, the rest of the page can continue rendering. The rescue slot can offer a clear message and a retry action.
<Deferred data="activity">
<template #rescue="{ reloading }">
<div>
<p>Activity is temporarily unavailable.</p>
<button
:disabled="reloading"
@click="$inertia.reload({ only: ['activity'] })"
>
{{ reloading ? 'Retrying...' : 'Try again' }}
</button>
</div>
</template>
<ActivityList :items="activity" />
</Deferred>
This pattern is useful for analytics, third-party services, recommendations, and other data that should not block the first render.
Vue SSR: Render the first view on the server
Server-side rendering pre-renders Vue pages before sending them to the browser. It improves the initial contentful paint and makes public pages easier for search engines to index.
Laravel’s Vue starter kit supports Inertia SSR. In development, the Inertia Vite plugin handles the SSR process. For production, build both bundles:
npm run build:ssr
Inertia 3 requires Node.js 22 or higher for its SSR server. You can also use the standard production commands documented in the Inertia SSR guide:
npm run build
php artisan inertia:start-ssr
SSR is a strong fit for marketing pages, public directories, ecommerce storefronts, and authenticated dashboards that benefit from a fast first shell.
Keep browser-only code inside Vue lifecycle hooks. References to window, document, or local storage will fail during server rendering if they run at module scope.

State management: Prefer the smallest useful layer
Inertia already manages page state and server props. Start there.
Use:
- Page props for server-owned data.
- Local Vue state for inputs, dialogs, tabs, and temporary UI behavior.
- Shared props for small global values, such as the authenticated user or flash messages.
- A store such as Pinia for client-owned state shared across unrelated pages.
Avoid copying every page prop into a global store. That creates two sources of truth and makes reloads harder to reason about.
For persistent client preferences, store only what belongs to the browser. Theme choice, dismissed notices, and table density are good examples. Permissions, account status, and billing data should come from Laravel.
Authentication patterns that scale
For a Laravel application with an Inertia frontend, session authentication is usually the simplest choice. The official starter kits use Laravel Fortify and include registration, login, password resets, email verification, and two-factor authentication.
Protect routes on the server:
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dashboard', DashboardController::class)
->name('dashboard');
});
Share the authenticated user through your Inertia middleware. The frontend can then render navigation and account controls without making a separate user request.
For a separate SPA, mobile client, or public API, use Laravel Sanctum. Sanctum supports cookie-based SPA authentication and API tokens. Use Laravel Passport when your application needs full OAuth2 capabilities.
The rule is straightforward:
- Inertia monolith: Laravel sessions and starter kit authentication.
- Separate first-party SPA: Sanctum.
- Third-party OAuth2 integrations: Passport.
- Headless authentication flows: Fortify with your own frontend.
Real-world project shapes

Operations dashboard: Poll only live metrics
A logistics dashboard can render shipments through normal Inertia props. It can defer a large activity feed and poll only delivery counts every few seconds.
Use mode: 'rest' if the metrics query is expensive. Add caching on the Laravel side before increasing the polling frequency.
Project workspace: Keep interactions local
A project board can use Inertia visits for moving cards between lists. It can use useHttp for presence indicators, comment autocomplete, and background saves that should not navigate.
Vue manages drag state. Laravel remains responsible for authorization and persistence.
Ecommerce account area: SSR the public pages
A storefront can use SSR for product and category pages. The authenticated account area can use session authentication and deferred order history.
If the catalog becomes a separate mobile or partner API, expose dedicated Laravel resources without changing the internal Inertia pages.
Agency admin platform: Start with the ecosystem
Agencies often need authentication, search, payments, queues, monitoring, and deployment before they build their differentiating features. Laravel’s PHP developer tools cover these concerns through the framework and its ecosystem.
Use Cloud or Forge for infrastructure. Add Nightwatch for application monitoring. Use Horizon for queues and Pulse for application insights.
A practical default architecture
For most new Laravel and Vue SPAs, begin with this structure:
- Laravel routes and controllers own page data.
- Vue pages own presentation and interaction.
- Inertia visits handle navigation and server prop updates.
-
useHttphandles local JSON requests. -
usePollrefreshes selected live props. - Deferred props protect the initial render.
- SSR serves public pages and important first views.
- Sessions handle authentication inside the monolith.
- Sanctum protects separate SPAs and API clients.
- Cloud, Forge, and Nightwatch support deployment and operations.
This stack gives you a fast interface without discarding the strengths of Laravel. You can start with one elegant codebase, then introduce APIs, SSR, background jobs, or dedicated services when the product requires them.
The best SPA architecture is not the one with the most moving parts. It is the one that keeps each responsibility close to the layer built to handle it.