Your public post list shows a blank page to link previews and slow phones until JavaScript loads. Inertia SSR sends real HTML on the first response. Here is how to build it, run it, and keep your Vue code from crashing it.
TL;DR
- Ship
npm run build:ssr(not justnpm run build), then keepphp artisan inertia:start-ssrrunning in production. - In development,
npm run devalready server-renders through the Inertia Vite plugin. - Read
windowanddocumentonly inonMounted. An app entirely behind login can skip SSR.
When you need SSR#
Inertia SSR runs your Vue page in Node and sends HTML as the first response. Use it when a public page has to be readable without waiting for JavaScript. A tool that is entirely behind login does not need that HTML. The browser loads the client bundle after the redirect to login either way.
The host process that keeps SSR running is the deploy guide. This page is the build and the Vue code that crashes it. The choice of Inertia versus Nuxt for a public site is the stack guide. Nuxt is the other way to get HTML. You do not run both for the same pages.
The commands#
Production SSR is a second bundle. npm run build writes only the browser files in public/build. It does not refresh bootstrap/ssr. Ship this instead:
npm run build:ssrLocally, npm run dev and composer run dev already server-render through the Inertia Vite plugin. The current Vue starter kit has no dev:ssr script, and you do not run inertia:start-ssr during development.
In production the process has to stay up:
php artisan inertia:start-ssr
php artisan inertia:check-ssr
php artisan inertia:stop-ssr| Command | What it does |
|---|---|
npm run build:ssr | Builds the browser bundle and the server bundle in bootstrap/ssr |
php artisan inertia:start-ssr | Starts the SSR server (keep it supervised) |
php artisan inertia:check-ssr | Confirms the SSR server answers |
php artisan inertia:stop-ssr | Stops it, for example after a deploy |
inertia:check-ssr is how you confirm the process answered before you blame the page. inertia:start-ssr accepts --runtime=bun when you are actually running Bun. Do not add the flag on a Node host.
Gotcha: SSR doesn't make public/hot safe
public/hot still must not be on the server. SSR does not make a dev server safe to deploy.
window is not there#
The first render has no document and no window. A line in <script setup> that reads window.innerWidth throws during that render. Inertia then falls back to the client-only page. Read the browser inside onMounted, or guard it.
Crashes on the server, then Runs in the browser only:
import { ref } from "vue";
const width = ref(window.innerWidth);import { onMounted, ref } from "vue";
const width = ref(0);
onMounted(() => {
width.value = window.innerWidth;
});Heads up: Pinia and SSR
Pinia has the same constraint. Create the store inside the existing withApp callback, not at module scope, so two SSR requests do not share one store. That callback is the Pinia guide. A manual setup callback turns off the Vite plugin’s SSR wiring.
Check the HTML, not the screen
curl -s https://your-app.test/posts | grep -o "<h1[^>]*>[^<]*"With SSR working, the heading is in the first response. Without it, you only get the empty app container.
What to render#
SSR the public post list if search engines or link previews need the titles in the HTML. Leave the settings screen as a normal Inertia visit. You do not have to server-render every component because one marketing page needed it. The SSR server renders the first visit of each page you built the bundle for. It is a process you operate, next to the queue worker.
Deferred props still arrive after the HTML. They are not in the first document. Partial reloads explain that request. Do not defer the title you wanted Google to see.
Where this stops#
The supervise step on a VPS, php artisan optimize, and the queue are the deploy guide. This page does not pick a host.
Recap#
- SSR is for public pages that need HTML on the first response.
- Build with
npm run build:ssr, runinertia:start-ssrunder a supervisor, verify withinertia:check-ssr. - Touch
window/documentonly inonMounted; create Pinia insidewithApp. - Deferred props are never in the first HTML.
FAQ#
Do I need to run inertia:start-ssr in development?
No. With the Inertia Vite plugin, npm run dev and composer run dev already server-render.
Why does my page render client-side only even with SSR on?
Something threw during the server render, often code that reads window or document in <script setup>. Inertia falls back to the client-only page. Move that code into onMounted.
Does npm run build include the SSR bundle?
No. npm run build only writes public/build. Use npm run build:ssr to refresh bootstrap/ssr too.
Do apps behind a login need SSR?
Usually not. Search engines and link previews never see those pages, and users load the client bundle after login anyway.
Next steps#
- Deploy a Laravel + Vue app: supervise the SSR process next to the queue.
- Pinia with Laravel and Inertia: SSR-safe stores.
- Set the page title in Inertia and Vue: titles that land in the SSR HTML.
- Inertia docs: server-side rendering



