Inertia.js bridges the gap between server-side Laravel applications and modern JavaScript frontends. You get the productivity of Laravel routing, controllers, and middleware combined with the interactivity of Vue or React, without building a separate API. But deploying an Inertia application to production involves more than just pushing code. You need a build pipeline for your JavaScript assets, and if you want server-side rendering, you need a Node.js process running alongside your PHP application.
This guide covers the full deployment pipeline for Inertia.js applications on Deploynix, from configuring build scripts in deployment hooks through setting up SSR and troubleshooting the issues that commonly arise in production.
Understanding the Deployment Pipeline
An Inertia.js application has two build outputs:
- Client-side assets: JavaScript and CSS compiled by Vite, served to the browser. These go into your
public/builddirectory. - SSR bundle (optional): A Node.js-compatible JavaScript file that renders your pages on the server for the initial page load.
Both need to be built during deployment, and the SSR bundle needs a running Node.js process to serve requests.
Configuring Vite for Production
Laravel ships with Vite integration out of the box. Your vite.config.js should be configured for your framework of choice.
For Vue:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
laravel({
input: 'resources/js/app.js',
ssr: 'resources/js/ssr.js',
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
});
For React:
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',
ssr: 'resources/js/ssr.jsx',
refresh: true,
}),
react(),
],
});
The ssr key tells Vite where to find your SSR entry point. This file sets up the server-side rendering logic.
Setting Up Deployment Hooks
Deploynix supports deployment hooks that run custom commands at different stages of the deployment process. For Inertia applications, you need to build assets during deployment.
Build Script in Deployment Hooks
Configure a deployment hook that runs after your code is deployed but before the site goes live:
cd /home/deploynix/your-site
# Install Node.js dependencies
npm ci --production=false
# Build client-side assets
npm run build
# Build SSR bundle (if using SSR)
npm run build:ssr
Why **npm ci instead of npm install?**
npm ci installs dependencies from your package-lock.json exactly as specified, ensuring reproducible builds. It's faster than npm install in CI/CD environments because it skips the dependency resolution step. The --production=false flag ensures devDependencies (like Vite, Vue compiler, etc.) are installed, which are needed for the build step.
Important: Make sure your package-lock.json is committed to your repository. Without it, npm ci will fail.
Optimizing Build Times
Asset builds can take 30-60 seconds or longer for large applications. Here are strategies to minimize deployment downtime:
Cache node_modules: If your Node.js dependencies haven't changed, you can skip npm ci. Check if node_modules exists and if package-lock.json has changed since the last install:
# In your deployment hook
if [ ! -d "node_modules" ] || [ "package-lock.json" -nt "node_modules/.package-lock.json" ]; then
npm ci --production=false
fi
npm run build
Use Deploynix's zero-downtime deployments: Deploynix supports zero-downtime deployments, meaning the build happens in a new release directory while the previous version continues serving traffic. The switchover happens only after the build completes successfully.
Setting Up Server-Side Rendering
SSR improves your application's initial page load performance and SEO. Without SSR, the browser receives an empty HTML shell and must download, parse, and execute JavaScript before rendering any content. With SSR, the server sends fully rendered HTML, and JavaScript hydrates it for interactivity.
The SSR Entry Point
Vue SSR entry point (resources/js/ssr.js):
import { createSSRApp, h } from 'vue';
import { renderToString } from 'vue/server-renderer';
import { createInertiaApp } from '@inertiajs/vue3';
import createServer from '@inertiajs/vue3/server';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { route } from '../../vendor/tightenco/ziggy';
createServer((page) =>
createInertiaApp({
page,
render: renderToString,
title: (title) => `${title} - Your App`,
resolve: (name) =>
resolvePageComponent(
`./Pages/${name}.vue`,
import.meta.glob('./Pages/**/*.vue'),
),
setup({ App, props, plugin }) {
return createSSRApp({ render: () => h(App, props) })
.use(plugin)
.mixin({
methods: {
route: (name, params, absolute) =>
route(name, params, absolute, page.props.ziggy),
},
});
},
}),
);
React SSR entry point (resources/js/ssr.jsx):
import ReactDOMServer from 'react-dom/server';
import { createInertiaApp } from '@inertiajs/react';
import createServer from '@inertiajs/react/server';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { route } from '../../vendor/tightenco/ziggy';
createServer((page) =>
createInertiaApp({
page,
render: ReactDOMServer.renderToString,
title: (title) => `${title} - Your App`,
resolve: (name) =>
resolvePageComponent(
`./Pages/${name}.jsx`,
import.meta.glob('./Pages/**/*.jsx'),
),
setup: ({ App, props }) => {
global.route = (name, params, absolute) =>
route(name, params, absolute, props.initialPage.props.ziggy);
return ;
},
}),
);
Building the SSR Bundle
Add an SSR build script to your package.json:
{
"scripts": {
"build": "vite build",
"build:ssr": "vite build --ssr"
}
}
The SSR build creates a file at bootstrap/ssr/ssr.js (or wherever your Vite config outputs it). This is a Node.js-compatible bundle that runs on the server.
Running the SSR Server
The SSR server needs to run as a persistent process. On Deploynix, configure it as a daemon:
- Command:
node bootstrap/ssr/ssr.js - Directory: Your site's root directory
- User:
deploynix
Deploynix manages this daemon with Supervisor, ensuring it restarts if it crashes.
The SSR server listens on port 13714 by default. Laravel communicates with it internally to render pages on the server before sending the HTML to the client.
Restarting the SSR Server During Deployments
Like Horizon, the SSR server loads your JavaScript code into memory. After deploying new code, you need to restart the SSR process to pick up changes.
Add a restart step to your deployment hook:
# Build steps
npm ci --production=false
npm run build
npm run build:ssr
# Restart SSR (Deploynix's Supervisor will handle the restart)
php artisan inertia:stop-ssr
After the SSR process is stopped, Supervisor will automatically start a new instance with the freshly built bundle.
Asset Versioning
Vite automatically adds content hashes to your asset filenames (e.g., app-BxH7a3C4.js). This ensures browsers always load the latest version of your assets. The Vite manifest file (public/build/manifest.json) maps the original filenames to their hashed versions.
How it works in production:
- Vite builds your assets with content hashes.
- The manifest file records the mapping.
- Laravel's
@viteBlade directive reads the manifest and generates the correctandtags. - Browsers cache assets aggressively because the hash changes when the content changes.
Common issues with versioning:
Stale assets after deployment: If users have your old page cached, their HTML references old asset URLs. When those assets are deleted (because only the new hashed versions exist), users see broken pages. Solution: Keep the previous deployment's assets available during the transition. Deploynix's zero-downtime deployments handle this by maintaining the previous release until the new one is fully active.
CDN cache issues: If you use a CDN, purge the cache after deployment or configure your CDN to respect the cache headers set by Nginx. Since asset filenames include content hashes, CDN caching works perfectly as long as you don't strip the hash from the filename.
Troubleshooting Common Issues
"Unable to locate file in Vite manifest"
Symptom: Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest
Cause: The Vite manifest file doesn't exist because assets haven't been built.
Solution: Run npm run build on the server. If you're seeing this after deployment, verify your deployment hook is running the build step successfully. Check the deployment logs in Deploynix for errors.
SSR Process Crashes on Startup
Symptom: The SSR daemon keeps restarting and the Supervisor logs show JavaScript errors.
Common causes:
- Missing Node.js modules. Ensure
npm ciruns before the SSR build. - Incompatible Node.js version. Verify the server's Node.js version matches your development environment.
- Browser-only APIs used in SSR context. Code that references
window,document, orlocalStoragewill crash in the Node.js SSR environment.
Solution for browser-only APIs:
Guard browser-specific code:
// In Vue components
import { onMounted } from 'vue';
onMounted(() => {
// This only runs in the browser, not during SSR
window.addEventListener('scroll', handleScroll);
});
// In React components
import { useEffect } from 'react';
useEffect(() => {
// This only runs in the browser, not during SSR
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
Hydration Mismatches
Symptom: Console warnings about hydration mismatches. The server-rendered HTML doesn't match what the client renders.
Common causes:
- Date/time formatting that differs between server and client.
- Randomized content (random IDs, shuffled lists).
- Browser-only data (screen size, user agent) used in the initial render.
Solution: Ensure your components produce the same output regardless of whether they're running on the server or client during the initial render. Move browser-dependent rendering into lifecycle hooks that only run on the client.
Slow SSR Rendering
Symptom: Initial page loads are slower with SSR enabled than without.
Possible causes:
- The SSR process is under-resourced (not enough memory or CPU).
- Complex components with heavy computation during render.
- External API calls during SSR.
Solution: Profile your SSR rendering to identify bottlenecks. Increase the daemon's available memory. Avoid external API calls during the server render; use Inertia's deferred props or lazy data loading instead.
Performance Optimization Tips
Code splitting: Inertia automatically code-splits by page component. Each page loads only the JavaScript it needs, reducing initial bundle size.
Preloading: Use Inertia's link component with prefetching to load the next page's data before the user clicks:
Users
Asset optimization: Configure Vite to optimize your production build:
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', '@inertiajs/vue3'],
},
},
},
},
});
This separates vendor libraries into their own chunk, which changes less frequently and can be cached longer by browsers.
Conclusion
Deploying an Inertia.js application on Deploynix requires attention to three areas: the asset build pipeline, SSR process management, and asset versioning. Deploynix's deployment hooks provide the right integration point for your build commands. Daemon management keeps your SSR process running reliably. And zero-downtime deployments ensure your users never see a broken page during the transition.
Start without SSR to keep things simple. Add SSR when SEO or initial load performance becomes important. Configure your deployment hooks once, and every subsequent deployment automatically handles the build process. The result is a modern, interactive Laravel application with the performance characteristics of a server-rendered site.
Top comments (0)