Improving SEO and Perceived Performance with Inertia.js SSR
Enable Inertia.js server‑side rendering to serve fully rendered HTML, boost SEO, and cut First Contentful Paint. Walk through a Laravel + React setup, a concrete dashboard example, and the trade‑offs you’ll face.
19 Aug 2025, 22:35 UTC

Why SSR matters for Inertia apps
Inertia.js lets you write single‑page applications with server‑side routing, but the default client‑only mode sends an empty HTML shell and a JSON payload. Search crawlers and users on slow connections see a blank page until the JavaScript bundle finishes downloading and hydrates the components. Server‑side rendering (SSR) solves this by returning fully rendered markup on the first request, giving you better SEO indexing and a faster First Contentful Paint.
Setting up SSR with Laravel and React
The SSR pipeline requires the same component code to run in two environments: a Node process on the server and the browser on the client. The steps below assume a Laravel 11 project using Vite and React 18. Adjust versions as needed.
- Install the SSR adapter –
npm i -D @inertiajs/server(provides thecreateServerhelper). - Create an SSR entry point –
resources/js/ssr.jsx:import { createServer } from '@inertiajs/server'; import { createRoot } from 'react-dom/client'; import App from './Pages/App'; createServer((page) => { const root = createRoot(document.getElementById('app')); root.render( <App {...page.props} /> ); }); - Configure Vite for a separate SSR build –
vite.config.js:import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [ laravel({ input: ['resources/js/app.jsx', 'resources/js/ssr.jsx'], ssr: 'resources/js/ssr.jsx', refresh: true, }), react(), ], ssr: { noExternal: ['@inertiajs/server'], }, }); - Add build scripts to
package.json:"scripts": { "build": "vite build", "build:ssr": "vite build --ssrManifest", "ssr": "node bootstrap/ssr/ssr.js" } - Run the Node SSR server in production – after
npm run build:ssr, start the process (e.g., via systemd, Supervisor, or a PaaS):
The server listens on# from project root, as a non‑root user with Node ≥18 npm run ssrhttp://127.0.0.1:13714by default; Laravel’sInertia::ssr()helper will proxy requests to it.
A worked example: rendering a dashboard page
Assume a Dashboard page component at resources/js/Pages/Dashboard.jsx. The controller returns Inertia::render('Dashboard', ['stats' => $stats]). With SSR enabled, the initial HTTP response contains markup like:
<div id="app">
<div class="dashboard">
<h1>Dashboard</h1>
<ul>
<li>Users: 1,234</li>
<li>Revenue: $56,789</li>
</ul>
</div>
</div>
<script src="/build/assets/app-abc123.js"></script>
The browser loads the same app-abc123.js bundle, calls hydrateRoot on the existing DOM, and the page becomes interactive without a full re‑render.
Trade‑offs and limitations
- Experimental status – Inertia’s SSR support is still marked experimental in the latest stable release. Expect occasional API changes; lock your dependencies and test thoroughly before shipping.
- Identical bundles required – The server and client must share the exact same component definitions and props. A version mismatch (e.g., a hot‑module replacement only on the client) triggers hydration mismatches and console errors.
- No streaming or partial hydration – The entire page is rendered before the response is sent. Large pages increase Time‑to‑First‑Byte; consider caching rendered HTML or using incremental static regeneration for relatively static content.
- Added server CPU load – Each SSR request runs React’s rendering logic in Node. Profile the Node process under realistic traffic and scale horizontally or add a CDN cache layer.
Verifying the result
- Inspect the raw response – Run
curl -i https://example.com/dashboardfrom a terminal. The body should contain the rendered markup (as shown above) rather than a single<div id="app"></div>placeholder. - Check hydration in the browser console – Open DevTools → Console. You should see no “hydration mismatch” warnings. React 18 logs a single “Hydration completed” message when successful.
- Run a Lighthouse audit – Compare the SSR‑enabled page against a client‑only baseline. Expect a lower First Contentful Paint (often 30‑50 % improvement) and a higher SEO score because the HTML already contains semantic content.
Next steps
Deploy the SSR build to a staging environment, enable the Node process behind your load balancer, and monitor the three verification points above for at least one full traffic cycle. If metrics look good, promote to production and add a cache‑control header (e.g., Cache-Control: public, max-age=60, stale-while-revalidate=300) for pages that tolerate slightly stale data. Keep an eye on the Inertia changelog for SSR stability updates and be ready to adjust the build pipeline when the experimental flag is removed.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.