v3/getting-started/upgrade-guide.mdx
import V3BetaWarning from "/snippets/v3-beta-warning.mdx";
<V3BetaWarning />You can find the legacy docs for Inertia.js v2.0 at inertiajs.com/docs/v2.
Inertia.js v3.0 is a major release focused on simplicity and developer experience. Axios has been replaced with a built-in XHR client for a smaller bundle, SSR now works out of the box during development without a separate Node.js server, and the new @inertiajs/vite plugin handles page resolution and SSR configuration automatically. This release also introduces standalone HTTP requests via the useHttp hook, optimistic updates with automatic rollback, layout props for sharing data between pages and layouts, and improved exception handling.
This release also includes several additional improvements:
Inertia::render() responsescreateInertiaApppreserveErrors option to preserve validation errors during partial reloadsTo upgrade to Inertia.js v3.0, first use npm to install the client-side adapter of your choice:
<CodeGroup>npm install @inertiajs/vue3@^3.0.0-beta
npm install @inertiajs/react@^3.0.0-beta
npm install @inertiajs/svelte@^3.0.0-beta
You may also install the new optional Vite plugin, which provides a simplified SSR setup and a pages shorthand for component resolution:
npm install @inertiajs/vite@^3.0.0-beta
Next, upgrade the inertiajs/inertia-laravel package:
composer require inertiajs/inertia-laravel:^3.0.0-beta
After upgrading, republish the Inertia configuration file since it has been restructured in v3. You should review the updated config file and re-apply any customizations:
php artisan vendor:publish --provider="Inertia\\ServiceProvider" --force
You should also clear your cached views since the @inertia Blade directive output has changed:
php artisan view:clear
The Laravel adapter now requires PHP 8.2 and Laravel 11 at a minimum.
The React adapter now requires React 19. React 18 and below are no longer supported.
The Svelte adapter now requires Svelte 5. Svelte 4 and below are no longer supported. All Svelte code should be updated to use the Svelte 5 runes syntax ($props(), $state(), $effect(), etc).
Inertia no longer ships with or requires Axios. For most applications, this requires no changes. The built-in XHR client supports interceptors as well, so Axios interceptors may be migrated directly. You may also continue using Axios via the Axios adapter, or provide a fully custom HTTP client.
qs Dependency RemovedThe qs package has been replaced with a built-in query string implementation and is no longer included as a dependency of @inertiajs/core. Inertia's internal query string handling remains the same, but you should install qs directly if your application imports it.
npm install qs
Two global events have been renamed for clarity:
| v2 Name | v3 Name | Document Event |
|---|---|---|
invalid | httpException | inertia:httpException |
exception | networkError | inertia:networkError |
Global event listeners should be updated accordingly:
// Before (v2)
router.on('invalid', (event) => { ... })
router.on('exception', (event) => { ... })
// After (v3)
router.on('httpException', (event) => { ... })
router.on('networkError', (event) => { ... })
You may also handle these events per-visit using the new onHttpException and onNetworkError callbacks:
router.post('/users', data, {
onHttpException: (response) => { ... },
onNetworkError: (error) => { ... },
})
Returning false from the onHttpException callback or calling event.preventDefault() on the global httpException event will prevent Inertia from navigating to the error page. This allows you to handle HTTP exceptions (4xx and 5xx responses) without leaving the current page.
router.post('/users', data, {
onHttpException: (response) => {
// Handle the error without navigating
return false
},
})
router.cancel() ReplacedThe router.cancel() method has been replaced by router.cancelAll(), which provides granular control over which request types to cancel:
// Before (v2)
router.cancel()
// After (v3)
router.cancelAll()
router.cancelAll({ async: false, prefetch: false }) // Cancel only sync requests
See the visit cancellation documentation for more details.
The future configuration namespace has been removed. All four future options from v2 are now always enabled and no longer configurable:
future.preserveEqualPropsfuture.useDataInertiaHeadAttributefuture.useDialogForErrorModalfuture.useScriptElementForInitialPage// Before (v2)
createInertiaApp({
defaults: {
future: {
preserveEqualProps: true,
useDataInertiaHeadAttribute: true,
useDialogForErrorModal: true,
useScriptElementForInitialPage: true,
},
},
})
// After (v3) - just remove the `future` block
createInertiaApp({
// ...
})
Initial page data is now always passed via a <script type="application/json"> element. The legacy data-page attribute approach is no longer supported.
The named exports hideProgress() and revealProgress() have been removed. If needed, use the progress object directly:
import { progress } from '@inertiajs/vue3'
progress.hide()
progress.reveal()
The React <Deferred> component no longer resets to show the fallback during partial reloads. Previously, the fallback was shown each time a partial reload was triggered. Now the existing content remains visible while new data loads, consistent with the Vue and Svelte behavior.
A new reloading slot prop is available across all adapters, allowing you to show a loading indicator during partial reloads while keeping the existing content visible. See the deferred props documentation for details.
The useForm helper now only resets processing and progress state in the onFinish callback, rather than immediately upon receiving a response. This ensures the processing state remains true until the visit is fully complete.
The Inertia::lazy() method and LazyProp class, deprecated in v2, have been removed. Use Inertia::optional() instead, which provides the same functionality:
// Before (v2)
return Inertia::render('Users/Index', [
'users' => Inertia::lazy(fn () => User::all()),
]);
// After (v3)
return Inertia::render('Users/Index', [
'users' => Inertia::optional(fn () => User::all()),
]);
The Laravel configuration file has been restructured. Page-related settings are now nested under pages, and the testing section has been simplified:
// Before (v2) - config/inertia.php
'testing' => [
'ensure_pages_exist' => true,
'page_paths' => [resource_path('js/Pages')],
'page_extensions' => ['js', 'jsx', 'svelte', 'ts', 'tsx', 'vue'],
],
// After (v3) - config/inertia.php
'pages' => [
'ensure_pages_exist' => false,
'paths' => [resource_path('js/Pages')],
'extensions' => ['js', 'jsx', 'svelte', 'ts', 'tsx', 'vue'],
],
'testing' => [
'ensure_pages_exist' => true,
],
The updated config file should have already been republished as part of the upgrade dependencies step above.
The deprecated Inertia\Testing\Concerns\Has, Inertia\Testing\Concerns\Matching, and Inertia\Testing\Concerns\Debugging traits have been removed. These traits were deprecated in v1 and replaced by the AssertableInertia class. No action is required unless your application references these traits directly.
When using the new @inertiajs/vite plugin, SSR works automatically during development by simply running npm run dev. You no longer need to build your SSR bundle with vite build --ssr or start a separate Node.js server with php artisan inertia:start-ssr during development. These commands are now only required for production deployments.
The Inertia middleware is now automatically registered in Laravel's middleware priority list, ensuring it runs before middleware like ThrottleRequests. This fixes an issue where PUT/PATCH/DELETE requests that were rate-limited could receive a 302 redirect instead of the correct 303, causing the browser to retry the original request method on the redirect target. No action is required.
Prop types like Inertia::optional(), Inertia::defer(), and Inertia::merge() now work inside closures and nested arrays. Inertia resolves them at any depth and uses dot-notation paths in partial reload metadata.
return Inertia::render('Dashboard', [
'auth' => fn () => [
'user' => Auth::user(),
'notifications' => Inertia::defer(fn () => Auth::user()->unreadNotifications),
'invoices' => Inertia::optional(fn () => Auth::user()->invoices),
],
]);
On the client side, the only and except options, as well as the Deferred and WhenVisible components, all support dot-notation for targeting nested props.
router.reload({ only: ['auth.notifications'] })
Classes implementing the ProvidesInertiaProperties interface also work at any nesting level.
return Inertia::render('Dashboard', [
'auth' => [
new AuthProps,
'team' => 'Inertia',
],
]);
All Inertia packages now ship as ES Modules only. CommonJS require() imports are no longer supported. You should update any require() calls to use import statements instead.
The clearHistory and encryptHistory properties in the page object are now optional and only included in the response when true. Previously, every response included "clearHistory": false and "encryptHistory": false even when history wasn't being cleared or encrypted.