Inertia.js v3 has graduated from beta to stable. If you read our earlier beta breakdown, you already know the headline changes: Axios removal, native Fetch, optimistic updates, and reworked SSR. This post picks up where that one left off.
Now that v3 is the default install, the question is no longer “should I try it?” but “how do I ship it safely?” Below are seven concrete steps to move from v2 (or the v3 beta) to the stable release without surprises.
Table of Contents
- Audit What Changed Since Beta
- Handle the Breaking and Behavioral Edge Cases
- Update Your SSR and Vite Deployment Pipeline
- Revise Your Test Suite
- Plan Your Rollback Strategy
- Run the Production Rollout Checklist
- Update Team Documentation and Habits
1. Audit What Changed Since Beta
The stable release is not identical to the last beta tag. Several things shifted between the final beta and the v3.0.0 tag that you should be aware of:
router.reload()default behavior. During beta,reload()preserved all current component props by default. The stable release changed this so that only explicitly listed props are preserved. If your components depend on the old behavior, pass{ preserveState: true }explicitly.- Fetch request headers. The beta sent a minimal set of headers. Stable now includes
X-Inertia-Versionon every request by default, which means your server-side version checking middleware works consistently again. If you disabled version checking during beta testing, re-enable it. - Event naming. The
before,start,progress,finish,cancel,error, andsuccessevent names are unchanged, but the payload shape forerrorevents now includes astatusproperty alongside the existingerrorsobject. - Vue adapter version. The
@inertiajs/vue3adapter must bev3.0+to pair with the corev3.0. Mixing a beta adapter with the stable core (or vice versa) will silently break shared state handling.
Action: Pin exact versions in your package.json — do not rely on ^3.0.0 ranges until you have verified compatibility.
{
"@inertiajs/vue3": "3.0.0",
"@inertiajs/core": "3.0.0"
}2. Handle the Breaking and Behavioral Edge Cases
Beyond the headline changes, several behavioral differences bite teams mid-migration:
File uploads with progress tracking
Axios provided upload progress via its onUploadProgress callback. The native Fetch API does not support upload progress in most browsers. Inertia v3 works around this with a ReadableStream-based polyfill, but it behaves differently:
- Progress events fire less frequently (chunk-based rather than byte-based).
- The
percentagevalue may jump in larger increments. - If you display a smooth progress bar, consider debouncing the UI update or switching to an indeterminate indicator for small files.
CSRF token handling
With Axios gone, Inertia v3 reads the CSRF token from the XSRF-TOKEN cookie and attaches it as an X-XSRF-TOKEN header on each Fetch request. This works automatically with Laravel’s default cookie settings, but breaks if:
- You have changed
SESSION_DOMAINorSANCTUM_STATEFUL_DOMAINSand the cookie domain does not match. - You use
SameSite=Strict— Fetch requests triggered by Inertia link navigations from external referrers will not include the cookie. - You are behind a CDN or reverse proxy that strips cookie headers.
Action: Test CSRF flow end-to-end in staging before upgrading production. Pay special attention to cross-subdomain setups.
External redirects
Inertia::location() for full-page external redirects now returns a 409 Conflict response with a specific header, rather than a standard 302. If you have middleware, monitoring, or WAF rules that alert on 409 responses, add an exception for Inertia external redirects.
3. Update Your SSR and Vite Deployment Pipeline
SSR in Inertia v3 has been rewritten to integrate more tightly with Vite’s SSR build mode. Here is what to change in your build and deploy process:
Vite config
// vite.config.js
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,
},
},
}),
],
})SSR entry point
The v3 SSR entry point uses createInertiaApp from @inertiajs/vue3/server instead of the older @inertiajs/server package. If you are still importing from @inertiajs/server, that package is deprecated and will not work with v3.
// resources/js/ssr.js
import { createSSRApp, h } from 'vue'
import { renderToString } from '@vue/server-renderer'
import { createInertiaApp } from '@inertiajs/vue3'
export default createInertiaApp({
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)
},
})Build and run
# Build both client and SSR bundles
npx vite build
npx vite build --ssr
# Run the SSR server
node bootstrap/ssr/ssr.jsDeployment notes
- The SSR Node process now uses ESM by default. If your deployment tooling expects CommonJS (PM2 with older config, some Docker setups), add
"type": "module"to yourpackage.jsonor adjust your process manager config. - Memory usage for the SSR process has increased slightly in v3. Monitor RSS after deployment and adjust your container memory limits if you are running close to the ceiling.
- If you use Laravel Forge or Envoyer, update the deploy script to rebuild the SSR bundle and restart the Node process after each deployment.
4. Revise Your Test Suite
Backend (PHPUnit / Pest)
Inertia’s assertInertia() test helper works the same way in v3, but the underlying response structure has a new clearHistory property. If you have snapshot tests that assert the full Inertia JSON response, they will fail. Update your snapshots or switch to targeted assertions:
$response->assertInertia(fn ($page) => $page
->component('Users/Index')
->has('users', 10)
->where('filters.search', 'John')
);Frontend (Vitest / Jest)
- Mock
router.visitandrouter.reloadcarefully — the function signatures have changed.router.visitnow returns aPromiseinstead ofvoid, so tests that do notawaitit may behave differently. - If you were mocking Axios request/response interceptors, remove those mocks entirely. They are dead code now.
- For components that use optimistic updates via
useForm().submit(), test both the optimistic state and the resolved state.
End-to-end (Cypress / Playwright)
- Inertia v3 no longer sets the
X-Requested-With: XMLHttpRequestheader (that was an Axios convention). If your E2E tests or server middleware check for this header to distinguish Inertia requests from full page loads, switch to checking for theX-Inertia: trueheader instead.
5. Plan Your Rollback Strategy
Migrations that touch both backend packages and frontend dependencies need a clear rollback path.
Before you start
- Lock your current working state. Tag your last v2 deployment:
git tag v2-final-deploy. - Keep the old lock file. Copy
composer.lockandpackage-lock.json(oryarn.lock) to a known location or branch. - Document the current SSR process config. If you run SSR via PM2 or Supervisor, save the current config file.
Rolling back
If something goes wrong after deploying v3:
- Revert to the tagged commit:
git checkout v2-final-deploy. - Run
composer installandnpm cito restore exact previous dependencies. - Rebuild assets:
npx vite build(andnpx vite build --ssrif applicable). - Restart the SSR Node process with the old config.
- Clear Laravel caches:
php artisan cache:clear && php artisan config:clear && php artisan view:clear.
The rollback should take under five minutes if you have the tag and lock files ready. Do not attempt a partial rollback (e.g., reverting only the frontend) — the Inertia protocol version on client and server must match.
6. Run the Production Rollout Checklist
Use this as a linear checklist. Do not skip steps.
- Pin
@inertiajs/vue3and@inertiajs/coreto3.0.0inpackage.json - Update
inertiajs/inertia-laravelto the v3-compatible release via Composer - Remove any direct
axiosimports used for Inertia requests (keep Axios only if you use it for non-Inertia API calls) - Update the SSR entry point to use
@inertiajs/vue3server export - Verify
vite.config.jsSSR settings match the new structure - Run the full backend test suite — fix any snapshot or assertion failures
- Run the full frontend test suite — remove Axios mocks, await
router.visitcalls - Run E2E tests — check for
X-Inertiaheader usage instead ofX-Requested-With - Test CSRF token handling in staging (especially cross-subdomain or SameSite setups)
- Test file uploads with progress tracking in staging
- Deploy to a canary or staging environment first
- Monitor error rates, SSR memory usage, and response times for 24 hours
- If stable, deploy to production
- Keep the
v2-final-deploytag for at least two weeks
7. Update Team Documentation and Habits
After the migration is complete, close the loop:
- Update your project README or internal wiki to reflect that the app runs on Inertia v3. New team members should not have to guess which version is in use.
- Remove beta-era workarounds. If you added shims, polyfills, or config overrides during the beta period, audit and remove them. Dead workarounds are future confusion.
- Adopt the new patterns. Optimistic updates and the Promise-based
router.visitenable cleaner code. Refactor incrementally — do not rewrite everything at once, but adopt new patterns in new features. - Set up version monitoring. Add a simple health check endpoint that returns your Inertia protocol version. This makes debugging version mismatches between client and server trivial in production.
Final Thoughts
Inertia v3 is a solid step forward for the Laravel + Vue stack. The migration is not trivial, but it is well-scoped — most of the work is in testing and deployment pipeline updates rather than rewriting application code. Do it methodically, keep a rollback path open, and you will be on the stable release within a sprint.
If you followed our beta guide, you have already done the hard thinking. This is the execution phase.