Single-page applications usually render in the browser. The server sends a small HTML shell, JavaScript loads, and Vue builds the page.
That model works well for authenticated dashboards and internal tools. Public pages often need more.
Server-side rendering (SSR) lets Laravel and Vue produce the initial HTML before it reaches the browser. Inertia then hydrates that HTML and turns it into an interactive SPA.
You keep Laravel routing and controllers. You keep Vue components and client-side navigation. SSR closes the gap between a classic server-rendered application and a JavaScript-heavy frontend.
This article explains how SSR works with Laravel, Vue 3, and Inertia 3.x. It also covers the cost. SSR improves the first response, but it adds a rendering process and more deployment work.
SSR matters: The first response sets the tone
A client-rendered SPA often returns an empty application shell:
<div id="app"></div>
<script type="module" src="/build/assets/app.js"></script>
The browser must download JavaScript, parse it, resolve the page component, fetch data, and render the result.
On a fast connection, this can feel instant. On a slower device or mobile network, users see a blank page or loading state first.
SSR changes the first response:
<div id="app" data-page="...">
<main>
<h1>Upcoming events</h1>
<p>Browse events near you.</p>
</main>
</div>
The browser can paint meaningful content before the Vue bundle finishes loading. Vue later hydrates the existing markup and attaches event handlers.
This improves three related experiences.
First paint: Show useful content earlier
SSR can reduce the time between navigation and visible content. The browser receives HTML that already contains the page structure and data.
That does not guarantee a faster server response. Laravel still needs to resolve the request, and the SSR process must render the Vue tree.
It does mean the browser has something useful to display while the rest of the application loads.
SEO: Give crawlers actual page content
Search engines can process JavaScript, but server-rendered HTML remains a simpler path for indexing public content.
SSR is useful for landing pages, documentation, marketing pages, product listings, and editorial content. Your title, headings, links, and page copy arrive in the initial document.
Inertia’s server-side rendering documentation describes this as pre-rendering JavaScript pages on the server. It also notes that Node.js must be available for SSR.
SSR does not replace good SEO. You still need useful content, stable URLs, metadata, canonical links, and sensible internal linking.
Perceived performance: Make the wait feel shorter
Users judge performance by what they can see and use. A page that paints its heading and main content early often feels faster than one that shows a spinner.
Inertia keeps subsequent navigation client-side. After hydration, visits can update the page without a full browser reload.
That combination works well:
- Laravel and Vue render the first visit on the server.
- The browser displays the returned HTML.
- Vue hydrates the page.
- Inertia handles later navigation in the client.

Inertia 3.x enables SSR: Use the built-in flow
Inertia sits between Laravel and your frontend. Laravel remains responsible for routes, authorization, controllers, and data. Vue remains responsible for rendering the interface.
With SSR enabled, Laravel sends the page object to the SSR server. The SSR server renders the matching Vue component and returns HTML. Laravel includes that HTML in the response.
Inertia 3.x simplifies this setup through the @inertiajs/vite plugin. The plugin detects the SSR entry point and handles development mode SSR through Vite.
Install the relevant packages:
composer require inertiajs/inertia-laravel
npm install @inertiajs/vue3 @inertiajs/server @inertiajs/vite
npm install vue
Inertia SSR requires Node.js 22 or higher according to the Inertia 3 documentation. Check your production runtime before enabling the feature.
The recommended Vite configuration looks like this:
// vite.config.js
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import inertia from '@inertiajs/vite'
export default defineConfig({
plugins: [
laravel({
input: ['resources/js/app.js'],
refresh: true,
}),
inertia(),
],
})
The plugin can reuse your existing app.js entry point. You can also provide a dedicated SSR entry when you need custom setup logic.
The SSR URL: Know what Laravel calls
The built-in SSR server listens on port 13714 by default. In a manual configuration, its render endpoint is:
http://127.0.0.1:13714/render
The @inertiajs/server package exposes this endpoint. Laravel sends SSR requests there when the adapter renders an initial Inertia response.
With Inertia 3.x and the Vite plugin, you usually do not need to manage this URL directly. The plugin coordinates the development endpoint and production SSR process.
You may still need the URL for a custom setup, a containerized deployment, or an existing process manager. Keep the Laravel configuration and SSR server address consistent.
Publish the Inertia configuration if your application does not have it:
php artisan vendor:publish \
--provider="Inertia\\ServiceProvider"
Then configure SSR in config/inertia.php:
return [
'ssr' => [
'enabled' => (bool) env('INERTIA_SSR_ENABLED', true),
'url' => env(
'INERTIA_SSR_URL',
'http://127.0.0.1:13714/render'
),
'runtime' => env('INERTIA_SSR_RUNTIME', 'node'),
],
];
Your .env file can make the setting explicit:
INERTIA_SSR_ENABLED=true
INERTIA_SSR_URL=http://127.0.0.1:13714/render
INERTIA_SSR_RUNTIME=node
Use 127.0.0.1 when Laravel and the SSR process share a host. Use the internal service hostname when they run in separate containers.
Bundle Vue for SSR: Build two applications
SSR needs a server bundle. The browser needs a client bundle.
The client bundle runs in the browser. The server bundle runs in Node and imports the same Vue pages in a server-safe environment.
For custom behavior, create resources/js/ssr.js:
// resources/js/ssr.js
import { createSSRApp, h } from 'vue'
import { renderToString } from 'vue/server-renderer'
import { createInertiaApp } from '@inertiajs/vue3'
import createServer from '@inertiajs/server'
createServer((page) =>
createInertiaApp({
page,
render: renderToString,
resolve: (name) => {
const pages = import.meta.glob('./Pages/**/*.vue', {
eager: true,
})
return pages[`./Pages/${name}.vue`]
},
setup({ App, props, plugin }) {
return createSSRApp({
render: () => h(App, props),
}).use(plugin)
},
})
)
The browser entry must hydrate the server-rendered markup:
// resources/js/app.js
import { createSSRApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
createInertiaApp({
resolve: (name) => {
const pages = import.meta.glob('./Pages/**/*.vue')
return pages[`./Pages/${name}.vue`]()
},
setup({ el, App, props, plugin }) {
createSSRApp({
render: () => h(App, props),
})
.use(plugin)
.mount(el)
},
})
The important difference is createSSRApp. A normal createApp call mounts a fresh client tree. createSSRApp tells Vue to reuse the HTML produced by the server.
Update Vite if you use the dedicated entry:
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import inertia from '@inertiajs/vite'
export default defineConfig({
plugins: [
laravel({
input: ['resources/js/app.js'],
ssr: 'resources/js/ssr.js',
refresh: true,
}),
inertia({
ssr: false,
}),
],
})
The ssr: false option tells the Inertia plugin that you are managing the SSR entry manually. The Laravel Vite plugin still receives the SSR entry.
Build both bundles:
{
"scripts": {
"dev": "vite",
"build": "vite build && vite build --ssr"
}
}

Run SSR in development and production: Keep the workflow clear
In development, the Inertia Vite plugin handles SSR automatically:
npm run dev
You do not need to build the SSR bundle or start a separate Node process during normal development.
For production, build the application and start the SSR server:
npm run build
php artisan inertia:start-ssr
The SSR process should run under a process monitor. Laravel Forge includes an Inertia SSR option for managing this process. Laravel Cloud also provides native support for Inertia SSR.
You can check the process after deployment:
php artisan inertia:check-ssr
Stop it before replacing a bundle:
php artisan inertia:stop-ssr
A deployment should build the client and server bundles together. Then restart the SSR process so it loads the new bundle.
SSR tradeoffs: Measure before you commit
SSR improves the initial document, but it adds work to every first visit.
TTFB: Rendering happens before the response completes
Laravel must call the SSR process and wait for rendered HTML. That can increase time to first byte (TTFB), especially when the Vue tree is large or the SSR process is under pressure.
Keep public SSR pages focused. Avoid loading unnecessary data into the initial response. Cache expensive queries where appropriate.
Measure both TTFB and time to first content. A faster paint does not excuse a slow origin response.
Server load: Add Node capacity to your stack
SSR adds a long-running Node process. It consumes memory and CPU while rendering pages.
A traffic spike can affect both Laravel and the SSR service. Clustering can start multiple SSR workers and distribute requests across them:
inertia({
ssr: {
cluster: true,
},
})
Your deployment platform must still provide enough resources. Monitoring helps identify rendering errors, memory growth, and slow requests. Laravel Nightwatch can be part of that operational picture.
Hydration: The server and browser must agree
Hydration fails when the server and browser produce different markup. Common causes include random values, timestamps, browser-only APIs, and environment-specific logic.
This code is unsafe during SSR:
const width = window.innerWidth
Move browser-only work into a lifecycle hook:
import { onMounted, ref } from 'vue'
const width = ref(null)
onMounted(() => {
width.value = window.innerWidth
})
Avoid using window or document during the initial render. Make data deterministic. Test pages with JavaScript disabled, then test hydration with JavaScript enabled.
Inertia falls back to client-side rendering when SSR fails. That keeps the page available, but it can hide problems. Listen for SsrRenderFailed and log failures during testing.
When SSR fits: Choose pages with a public first visit
SSR is a strong fit for pages where first-load content matters:
- Public marketing pages
- Product and documentation pages
- Search results and listings
- Editorial content
- Shareable campaign pages
- Authenticated screens where first paint is still important
It may add little value to a private admin panel. Inertia lets you exclude routes from SSR, including dashboard and admin paths.
Laravel already gives you a productive PHP web framework, routing layer, validation, and view integration. Its ecosystem also includes PHP developer tools for deployment, monitoring, testing, queues, and administration.
That means you can keep SSR focused. Use it where rendered HTML improves the user experience. Keep client-only rendering for application areas that do not need search visibility or fast public content.
If you need a separate service alongside your Inertia pages, Laravel remains a practical choice to build REST API with PHP. SSR and API endpoints can live in the same application without forcing your entire frontend architecture to change.
The practical balance: Keep Laravel, add SSR where it pays
Vue and Inertia already remove much of the friction from building an SPA with Laravel. SSR extends that model to the first request.
You get server-rendered HTML, better crawlability, and a faster-feeling entry point. You still use Inertia navigation after hydration. You still write Vue components and Laravel controllers.
The cost is operational. You must run Node, build two bundles, monitor rendering, and keep server and client output consistent.
Start with the pages that benefit most. Measure TTFB, first content, hydration errors, and server resource use. Then expand SSR when the numbers support it.
That is the useful promise of Inertia 3.x SSR: not a different application model, but less friction between a Laravel backend and a polished Vue SPA.