Server-Side Rendering (SSR)
Server-side rendering pre-renders your JavaScript pages on the server, allowing your visitors to receive fully rendered HTML when they visit your application. Since fully rendered HTML is served by your application, it’s also easier for search engines to index your site.
Server-side rendering uses Node.js to render your pages in a background process; therefore, Node must be available on your server for server-side rendering to function properly. Inertia’s SSR server requires Node.js 22 or higher.
InertiaCore Scaffolding
If you scaffolded your app with create-inertiacore, SSR support is already wired up — you can enable it by setting options.SsrEnabled = true in AddInertia(...) and running the SSR build script:
npm run build:ssrbuilder.Services.AddInertia(options =>{ options.SsrEnabled = true; options.SsrUrl = "http://127.0.0.1:13714/render";});Vite Plugin Setup
The recommended way to configure SSR is with the @inertiajs/vite plugin. This approach handles SSR configuration automatically, including development mode SSR without a separate Node.js server.
Install the Vite plugin
Terminal window npm install @inertiajs/viteConfigure Vite
Add the Inertia plugin to your
vite.config.jsfile. The plugin will automatically detect your SSR entry point.import inertia from '@inertiajs/vite'import laravel from 'laravel-vite-plugin'import { defineConfig } from 'vite'export default defineConfig({plugins: [laravel({input: ['resources/js/app.js'],refresh: true,}),inertia(),],})You may also configure SSR options explicitly.
inertia({ssr: {entry: 'resources/js/ssr.js',port: 13714,cluster: true,},})You may pass
falseto opt out of the plugin’s automatic SSR handling, for example if you prefer to configure SSR manually or disable SSR entirely.inertia({ssr: false,})Update your build script
Update the
buildscript in yourpackage.jsonto build both bundles."scripts": {"dev": "vite","build": "vite build" // [!code --]"build": "vite build && vite build --ssr" // [!code ++]},
Development Mode
The Vite plugin handles SSR automatically during development. There is no need to build your SSR bundle or start a separate Node.js server. Simply run your Vite dev server as usual:
npm run devThe Vite plugin exposes a server endpoint that Laravel uses for rendering, complete with HMR support.
Production
For production, build both bundles and start the SSR server.
npm run buildphp artisan inertia:start-ssrClustering
By default, the SSR server runs on a single thread. You may enable clustering to start multiple Node servers on the same port, with requests handled by each thread in a round-robin fashion.
inertia({ ssr: { cluster: true, },})Manual Setup
The Vite plugin reuses your app.js entry point for SSR by default, so no separate file is needed. Most customizations may be handled using the withApp callback.
For more control, such as providing a manual setup callback, you may create a separate resources/js/ssr.js entry point and update your app.js to use client-side hydration.
SSR Entry Point
import { createInertiaApp } from '@inertiajs/vue3'import createServer from '@inertiajs/vue3/server'import { createSSRApp, h } from 'vue'import { renderToString } from 'vue/server-renderer'
createServer(page => createInertiaApp({ page, render: renderToString, resolve: name => { const pages = import.meta.glob('./Pages/**/*.vue') return pages[`./Pages/${name}.vue`]() }, setup({ App, props, plugin }) { return createSSRApp({ render: () => h(App, props), }).use(plugin) }, }),)import { createInertiaApp } from '@inertiajs/react'import createServer from '@inertiajs/react/server'import ReactDOMServer from 'react-dom/server'
createServer(page => createInertiaApp({ page, render: ReactDOMServer.renderToString, resolve: name => { const pages = import.meta.glob('./Pages/**/*.jsx') return pages[`./Pages/${name}.jsx`]() }, setup: ({ App, props }) => <App {...props} />, }),)import { createInertiaApp } from '@inertiajs/svelte'import createServer from '@inertiajs/svelte/server'import { render } from 'svelte/server'
createServer(page => createInertiaApp({ page, resolve: name => { const pages = import.meta.glob('./Pages/**/*.svelte') return pages[`./Pages/${name}.svelte`]() }, setup({ App, props }) { return render(App, { props }) }, }),)Be sure to add anything that’s missing from your app.js file that makes sense to run in SSR mode, such as plugins or custom mixins.
Client-Side Hydration
You should also update your app.js to use hydration instead of normal rendering. This allows
import { createApp, h } from 'vue' // [!code --]import { createSSRApp, h } from 'vue' // [!code ++]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 }) { createApp({ render: () => h(App, props) }) // [!code --] createSSRApp({ render: () => h(App, props) }) // [!code ++] .use(plugin) .mount(el) },})import { createInertiaApp } from '@inertiajs/react'import { createRoot } from 'react-dom/client' // [!code --]import { hydrateRoot } from 'react-dom/client' // [!code ++]
createInertiaApp({ resolve: name => { const pages = import.meta.glob('./Pages/**/*.jsx') return pages[`./Pages/${name}.jsx`]() }, setup({ el, App, props }) { createRoot(el).render(<App {...props} />) // [!code --] hydrateRoot(el, <App {...props} />) // [!code ++] },})import { createInertiaApp } from '@inertiajs/svelte'import { mount } from 'svelte' // [!code --]import { hydrate, mount } from 'svelte' // [!code ++]
createInertiaApp({ resolve: name => { const pages = import.meta.glob('./Pages/**/*.svelte') return pages[`./Pages/${name}.svelte`]() }, setup({ el, App, props }) { mount(App, { target: el, props }) // [!code --] if (el.dataset.serverRendered === 'true') { // [!code ++:5] hydrate(App, { target: el, props }) } else { mount(App, { target: el, props }) } },})Opting Out of the Vite Plugin
You may pass ssr: false to the Inertia plugin to disable its automatic SSR handling and manage the SSR build yourself. You should also add the ssr property to the Laravel Vite plugin configuration so it knows about your entry point.
export default defineConfig({ plugins: [ laravel({ input: ['resources/js/app.js'], ssr: 'resources/js/ssr.js', // [!code ++] refresh: true, }), inertia({ ssr: false, // [!code ++] }), ],})export default defineConfig({ plugins: [ laravel({ input: ['resources/js/app.jsx'], ssr: 'resources/js/ssr.jsx', // [!code ++] refresh: true, }), inertia({ ssr: false, // [!code ++] }), ],})export default defineConfig({ plugins: [ laravel({ input: ['resources/js/app.js'], ssr: 'resources/js/ssr.js', // [!code ++] refresh: true, }), inertia({ ssr: false, // [!code ++] }), ],})Clustering
You may pass the cluster option to createServer to start multiple Node servers on the same port, with requests handled by each thread in a round-robin fashion.
createServer(page => createInertiaApp({ // ... }), { cluster: true },)createServer(page => createInertiaApp({ // ... }), { cluster: true },)createServer(page => createInertiaApp({ // ... }), { cluster: true },)Running the SSR Server
Once you have built both your client-side and server-side bundles, you may start the SSR server using the following Artisan command.
php artisan inertia:start-ssrBy default, the SSR server uses node as its runtime. You may change this by setting the runtime option in your config/inertia.php file. An absolute path to the runtime binary is also supported.
// InertiaCore does not start the SSR server for you — you run the// Node-based bundle yourself (e.g. `node ssr.js` or `bun ssr.js`).// Point InertiaCore at it via the SsrUrl option in Program.cs.builder.Services.AddInertia(options =>{ options.SsrEnabled = true; options.SsrUrl = builder.Configuration["Inertia:SsrUrl"] ?? "http://127.0.0.1:13714/render";});'ssr' => [ 'runtime' => env('INERTIA_SSR_RUNTIME', 'node'),],The --runtime flag on the Artisan command overrides the configured value for a single invocation.
php artisan inertia:start-ssr --runtime=bunYou may also enable the ensure_runtime_exists option to verify the runtime binary exists before attempting to start the SSR server. The command will exit with an error if the binary cannot be found.
// InertiaCore has no equivalent option — because you start the Node// SSR server yourself, verify the runtime binary exists in your own// startup or deploy script before launching it. For an absolute path,// File.Exists is enough; for a bare command name, resolve it via PATH.var runtime = builder.Configuration["Inertia:SsrRuntime"] ?? "node";
if (Path.IsPathRooted(runtime) && !File.Exists(runtime)){ throw new InvalidOperationException( $"SSR runtime '{runtime}' was not found.");}'ssr' => [ 'ensure_runtime_exists' => (bool) env('INERTIA_SSR_ENSURE_RUNTIME_EXISTS', false),],With the server running, you should be able to access your app within the browser with server-side rendering enabled. In fact, you should be able to disable JavaScript entirely and still navigate around your application.
Error Handling
When SSR rendering fails, Inertia gracefully falls back to client-side rendering. The Vite plugin logs detailed error information to the console, including the component name, request URL, source location, and a tailored hint to help you resolve the issue.
Common SSR errors are automatically classified. Browser API errors (such as referencing window or document in server-rendered code) include guidance on moving the code to a lifecycle hook. Component resolution errors suggest checking file paths and casing.
Inertia also dispatches an SsrRenderFailed event on the server. You may listen for this event to log failures or send them to an error tracking service.
// InertiaCore does not dispatch a dedicated SSR-failure event. If you// want to detect failed renders, enable SsrThrowOnError so the gateway// raises an exception instead of silently falling back, then catch it// in your global ASP.NET Core exception handler and log it there.builder.Services.AddInertia(options =>{ options.SsrEnabled = true; options.SsrUrl = "http://127.0.0.1:13714/render"; options.SsrThrowOnError = true;});use Illuminate\Support\Facades\Log;use Inertia\Ssr\SsrRenderFailed;
Event::listen(SsrRenderFailed::class, function (SsrRenderFailed $event) { Log::warning('SSR failed', $event->toArray());});Throwing on Error
Since Inertia gracefully falls back to client-side rendering, SSR failures may go unnoticed. Your tests pass because the client-side render succeeds, but your users never receive server-rendered HTML. This is especially common in E2E tests with tools like Laravel Dusk or Pest Browser Testing.
You may set the throw_on_error option in your config/inertia.php file to throw an exception instead of falling back silently, allowing you to catch SSR issues early.
builder.Services.AddInertia(options =>{ options.SsrEnabled = true; options.SsrUrl = "http://127.0.0.1:13714/render"; options.SsrThrowOnError = builder.Configuration .GetValue<bool>("Inertia:SsrThrowOnError");});'ssr' => [ 'throw_on_error' => (bool) env('INERTIA_SSR_THROW_ON_ERROR', false),],You may set the environment variable in your phpunit.xml to enable this only during testing.
<env name="INERTIA_SSR_THROW_ON_ERROR" value="true"/>Disabling SSR
SSR has two layers: the Vite plugin serves SSR during development and builds the SSR bundle for production, while the Laravel adapter dispatches rendering requests to the SSR server. To fully disable SSR, you should disable both.
inertia({ ssr: false,})'ssr' => [ 'enabled' => false,],builder.Services.AddInertia(options =>{ options.SsrEnabled = false;});You may also prevent the Laravel adapter from dispatching SSR requests programmatically using the Inertia::disableSsr() method. This is useful when you want to keep SSR in your build but disable it during tests or in specific environments.
using InertiaCore;
Inertia.DisableSsr();use Inertia\Inertia;
Inertia::disableSsr();A boolean or closure may be provided to disable SSR conditionally.
Inertia.DisableSsr(builder.Environment.IsEnvironment("Testing"));
Inertia.DisableSsr(() => builder.Environment.IsEnvironment("Testing"));Inertia::disableSsr(app()->runningUnitTests());
Inertia::disableSsr(fn () => app()->runningUnitTests());Excluding Routes from SSR
Sometimes you may wish to skip server-side rendering for certain routes while keeping SSR enabled for the rest of your application.
Via Middleware
You may use the $withoutSsr property on your Inertia middleware to disable SSR for specific route patterns.
// InertiaCore does not ship a publishable middleware class. Configure// the path exclusions once at startup using Inertia.WithoutSsr().using InertiaCore;
var app = builder.Build();
Inertia.WithoutSsr("admin/*", "dashboard");
app.UseInertia();use Inertia\Middleware;
class HandleInertiaRequests extends Middleware{ /** * Defines the routes that should not use SSR. * * @var array<int, string> */ protected $withoutSsr = [ 'admin/*', 'dashboard', ];}Via Facade
You may also exclude specific routes using the Inertia::withoutSsr() method, typically called from a service provider.
using InertiaCore;
Inertia.WithoutSsr(new[] { "admin/*", "dashboard" });use Inertia\Inertia;
Inertia::withoutSsr(['admin/*', 'dashboard']);Per-Request
You may disable SSR for the current request by setting the inertia.ssr.enabled configuration value to false.
// Resolve the current request from IHttpContextAccessor and disable// SSR conditionally using the Func<bool> overload.var httpContextAccessor = app.Services.GetRequiredService<IHttpContextAccessor>();
Inertia.DisableSsr(() =>{ var path = httpContextAccessor.HttpContext?.Request.Path.Value ?? string.Empty; return path.StartsWith("/admin", StringComparison.OrdinalIgnoreCase);});if (request()->is('admin/*')) { config(['inertia.ssr.enabled' => false]);}Deployment
When deploying your SSR enabled app to production, you’ll need to build both the client-side (app.js) and server-side bundles (ssr.js), and then run the SSR server as a background process, typically using a process monitoring tool such as Supervisor.
php artisan inertia:start-ssrTo stop the SSR server, for instance when you deploy a new version of your website, you may utilize the inertia:stop-ssr Artisan command. Your process monitor (such as Supervisor) should be responsible for automatically restarting the SSR server after it has stopped.
php artisan inertia:stop-ssrYou may use the inertia:check-ssr Artisan command to verify that the SSR server is running. This can be helpful after deployment and works well as a Docker health check to ensure the server is responding as expected.
php artisan inertia:check-ssrBy default, a check is performed to ensure the server-side bundle exists before dispatching a request to the SSR server. In some cases, such as when your app runs on multiple servers or is containerized, the web server may not have access to the SSR bundle. To disable this check, you may set the inertia.ssr.ensure_bundle_exists configuration value to false.
Laravel Cloud
To run the SSR server on Laravel Cloud, you may use Cloud’s native support for Inertia SSR.
Laravel Forge
To run the SSR server on Forge, you may enable it via the Inertia SSR toggle in your site’s application panel. Forge will create the required daemon and, optionally, update your deploy script to restart the SSR server on each deployment.