Inertia 3.x changes how Laravel and Vue applications handle HTTP requests. Axios is no longer the default dependency. Inertia now includes a lightweight XHR client for router visits, forms, and standalone requests.
The change removes a common dependency from your frontend bundle. It also gives useHttp the same reactive experience as useForm.
This matters when you use Laravel as your PHP web framework and want one consistent request layer across your SPA. You can build REST APIs with PHP, submit Inertia forms, upload files, and manage real-time events without switching between several clients.
Inertia 3.x replaces Axios by default:
Inertia 3.x uses a built-in XHR client for all internal HTTP communication. New applications do not need to install Axios for Inertia to work.
import { createInertiaApp } from '@inertiajs/vue3'
createInertiaApp()
The client handles the headers required by the Inertia protocol. It also supports request cancellation, upload progress, response handling, and interceptors.
Axios remains available. You can add it through the official adapter if your application already depends on Axios-specific behavior.
The built-in client applies to:
-
routervisits -
useFormsubmissions - The
<Form>component -
useHttprequests
This gives your PHP developer tools a smaller and more consistent frontend surface.

useHttp handles standalone requests:
Not every request should trigger an Inertia visit. Search, autocomplete, polling, file uploads, and dashboard widgets often need plain JSON responses.
Inertia 3.x adds useHttp for these cases. It sends standard HTTP requests without the X-Inertia navigation lifecycle.
<script setup>
import { useHttp } from '@inertiajs/vue3'
const search = useHttp({
query: '',
})
function findProjects() {
search.get('/api/projects', {
onSuccess: (response) => {
console.log(response)
},
})
}
</script>
<template>
<input
v-model="search.query"
@input="findProjects"
placeholder="Search projects"
/>
<span v-if="search.processing">
Searching...
</span>
<p v-if="search.errors.query">
{{ search.errors.query }}
</p>
</template>
The hook exposes reactive state similar to useForm:
processingerrorshasErrorsprogresswasSuccessfulrecentlySuccessfulisDirty
It also provides get, post, put, patch, delete, and submit methods. Each method returns a promise with parsed response data.
Use useHttp when your Laravel endpoint returns JSON:
return response()->json([
'projects' => $projects,
]);
Use useForm or router when your controller returns an Inertia page:
return Inertia::render('Projects/Index', [
'projects' => $projects,
]);
This distinction keeps navigation requests and API requests predictable.
Precognition 2.x adds live validation:
Laravel Precognition lets your frontend validate data using the same rules as your Laravel backend. It sends a validation request before the final controller action runs.
Inertia 3.x integrates Precognition directly into useHttp. Enable it by binding the method and endpoint:
import { useHttp } from '@inertiajs/vue3'
const form = useHttp({
name: '',
email: '',
}).withPrecognition('post', '/api/users')
You can validate fields as users interact with them:
<template>
<input
v-model="form.email"
@blur="form.validate('email')"
/>
<span v-if="form.invalid('email')">
{{ form.errors.email }}
</span>
<span v-if="form.valid('email')">
Email is available.
</span>
</template>
The hook also provides touch(), touched(), valid(), and invalid() helpers. Validation state remains reactive in your Vue component.
On the Laravel side, apply Precognition middleware to the route and keep validation rules in a form request:
use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
Route::post('/api/users', function (StoreUserRequest $request) {
// Store the user...
})->middleware(HandlePrecognitiveRequests::class);
A precognitive request runs middleware and validation. It does not execute the controller action. That prevents duplicate records and other side effects.
Precognition 2.x can therefore work with useHttp without adding a separate Axios flow to your Inertia forms.
Optimistic updates roll back safely:
Inertia 3.x adds optimistic updates to the router, useForm, and useHttp.
The pattern is useful for likes, bookmarks, toggles, and small list changes. The interface updates immediately. The server confirms the change in the background.
With useHttp, the optimistic callback receives the hook’s current data:
const like = useHttp({
likes: 0,
})
function addLike() {
like.optimistic((data) => ({
likes: data.likes + 1,
})).post('/api/posts/42/like')
}
If the request succeeds, the server response becomes the source of truth. If it fails, Inertia restores the previous value automatically.
Rollback occurs for:
- Validation errors with status
422 - Server errors
- Network failures
- Interrupted requests
The same approach works with useForm:
const form = useForm({
title: '',
})
form.optimistic((props) => ({
posts: [
...props.posts,
{ title: form.title },
],
})).post('/posts')
It also works with router visits:
import { router } from '@inertiajs/vue3'
router.optimistic((props) => ({
post: {
...props.post,
likes: props.post.likes + 1,
},
})).post(`/posts/${post.id}/like`)
Inertia tracks changed keys and supports concurrent optimistic requests. One request will not incorrectly overwrite state updated by another request.

Migrating from Axios:
Most migrations do not require a full rewrite. Start by separating navigation requests from JSON requests.
A typical Axios call may look like this:
import axios from 'axios'
async function archiveProject(id) {
await axios.post(`/api/projects/${id}/archive`)
}
The equivalent useHttp implementation is:
<script setup>
import { useHttp } from '@inertiajs/vue3'
const archive = useHttp({})
function archiveProject(id) {
archive.post(`/api/projects/${id}/archive`)
}
</script>
You now get reactive processing, errors, success state, and cancellation. You also remove the need to manage those states around every Axios promise.
For an existing application, migrate in this order:
- Upgrade the Inertia client packages.
- Identify Axios calls that perform Inertia navigation.
- Replace those calls with
routeroruseForm. - Replace standalone JSON calls with
useHttp. - Move shared request headers into Inertia interceptors.
- Test uploads, authentication, validation, and broadcasting.
You can configure the built-in client with request and response interceptors:
import { http } from '@inertiajs/vue3'
http.onRequest((config) => {
config.headers['X-App-Version'] = '3'
return config
})
http.onResponse((response) => {
console.debug('HTTP status:', response.status)
return response
})
http.onError((error) => {
console.error('HTTP request failed:', error)
})
Each interceptor returns a cleanup function. Keep that function when registering handlers inside a component that later unmounts.
Keeping Axios is still an option:
Some applications already rely on Axios interceptors, custom adapters, or third-party integrations. You do not need to remove Axios immediately.
Install it directly and configure the Inertia adapter:
import axios from 'axios'
import { axiosAdapter } from '@inertiajs/core'
import { createInertiaApp } from '@inertiajs/vue3'
const instance = axios.create({
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
})
createInertiaApp({
http: axiosAdapter(instance),
})
This routes Inertia requests through your Axios instance. Existing authentication, logging, CSRF, and socket headers can remain in place.
You can also provide a completely custom HTTP client. It must implement a request method that accepts an Inertia HttpRequestConfig and returns an HttpResponse.
Choose the built-in client when you want fewer dependencies. Keep Axios when your application has a large interceptor layer or depends on Axios-specific behavior.
useSocketId supports Echo integration:
Laravel Echo assigns a socket ID to the current WebSocket connection. Laravel uses the X-Socket-ID header with toOthers() to prevent the current browser from receiving its own broadcast.
Inertia 3.x adds HTTP interceptors that can attach this value to every request:
import { http } from '@inertiajs/vue3'
http.onRequest((config) => {
const socketId = window.Echo?.socketId()
if (socketId) {
config.headers['X-Socket-ID'] = socketId
}
return config
})
The Laravel broadcasting documentation covers this header flow.
For reactive access inside a Vue component, use the Echo hook:
<script setup>
import { useSocketId } from '@laravel/echo-vue'
const socketId = useSocketId()
</script>
<template>
<small v-if="socketId">
Connected: {{ socketId }}
</small>
</template>
useSocketId updates when Echo reconnects. Use the global interceptor for outgoing request headers. Use the hook when your UI needs to display or react to the current connection.

A cleaner request layer for Laravel and Vue:
Inertia 3.x does more than remove Axios from the default bundle. It gives common request patterns a shared API.
Use router for page navigation. Use useForm for Inertia form submissions. Use useHttp for standalone JSON requests. Add Precognition when fields need live Laravel validation, and use optimistic updates when the interface should respond before the server does.
You can start with the built-in XHR client and keep Axios through the adapter where needed. That makes migration incremental instead of disruptive.
For new projects, Laravel’s starter kits provide a practical foundation for Laravel, Vue, and Inertia. Existing applications can migrate request by request and keep their current backend contracts.
The result is a simpler frontend boundary. Your Laravel application remains responsible for validation and business rules. Your Vue components receive reactive request state without rebuilding it around every API call.