Preserving Form State and Validation Errors with Inertia.js in a Laravel‑Vue Stack
Learn how to keep user input and server‑side validation messages intact when navigating between pages without full reloads using Inertia.js, Laravel, and Vue 3.
15 Jan 2026, 04:56 UTC

Desired Outcome
When a user submits a form that fails validation, the page should:
- Stay on the same URL without a full document reload.
- Retain all entered field values.
- Display the server‑side validation errors returned by Laravel.
- Clear the form state after a successful submission and redirect to the intended route.
Prerequisites
Before you begin, ensure you have:
- Laravel backend with the
inertiajs/inertia-laraveladapter installed (version compatible with your Vue adapter). - Vue 3 frontend set up with
@inertiajs/vue3and@inertiajs/progress. - Node.js and npm available for compiling assets (
npm run devornpm run build). - A basic understanding of Laravel routes, controllers, and Vue single‑file components.
Procedure
-
Define a route that returns an Inertia response
In
routes/web.php:use App\Http\Controllers\PostController; use Illuminate\Support\Facades\Route; Route::get('/posts/create', [PostController::class, 'create'])->name('posts.create'); Route::post('/posts', [PostController::class, 'store'])->name('posts.store'); -
Create the controller method that handles GET and POST
The GET returns the form view. The POST validates input and either redirects back with errors or redirects to a success page.
namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; use Inertia\Inertia; class PostController extends Controller { public function create() { return Inertia::render('Posts/Create', []); } public function store(Request $request) { $validator = Validator::make($request->all(), [ 'title' => 'required|string|max:255', 'body' => 'required|string', ]); if ($validator->fails()) { // Preserve input and errors for Inertia return redirect()->back() ->withInput() ->withErrors($validator); } // Successful storage logic (omitted for brevity) // return redirect()->route('posts.index'); } } -
Vue component using Inertia’s
useFormhelperCreate
resources/js/Pages/Posts/Create.vue:<template> <form @submit.prevent="post"> <div> <label>Title</label> <input type="text" v-model="form.title" class="border" /> <span v-if="form.errors.title" class="text-red-500"> {{ form.errors.title[0] }} </span> </div> <div> <label>Body</label> <textarea v-model="form.body" class="border" t> </textarea> <span v-if="form.errors.body" class="text-red-500"> {{ form.errors.body[0] }} </span> </div> <button type="submit">Create Post</button> </form> </template> <script setup> import { useForm } from '@inertiajs/vue3'; const form = useForm({ title: '', body: '', }); async function post() { await form.post(route('posts.store'), { // Optional: preserve scroll position preserveScroll: true, // On success, form will be reset automatically onSuccess: () => form.reset(), }); } </script> -
Enable client‑side navigation with
<Link>orrouter.navigate()For any navigation that should not reload the page, use Inertia’s provided components:
<Link :href="route('posts.create')">New Post</Link>Or programmatically:
import { useRouter } from '@inertiajs/vue3'; const router = useRouter(); router.visit(route('posts.index'));
Expected Checks
- Open browser DevTools → Network tab, enable “Preserve log”, filter by
XHR. Navigate between pages using<Link>orrouter.navigate(). Verify that only XHR requests appear and no full document reloads (documentreload). - Submit the form with invalid data (e.g., empty title). Observe:
- The URL stays the same (
/posts/create). - The request is an XHR (
POST /posts). - The response contains a
redirectheader pointing back to the same URL withwithInput()andwithErrors()data. - Form fields retain the values you typed.
- Validation messages appear beneath the corresponding fields.
- Submit the form with valid data. After the request:
- You are redirected to the success route (e.g.,
/posts). - The URL updates accordingly.
- The form’s
useForm()object returns empty strings (form.data.title === '').
Recovery Options
If something goes wrong, you can:
- Disable Inertia’s state preservation for a specific navigation by setting
preserveState: falsein the visit options, which forces a full page reload:
router.visit(route('posts.create'), { preserveState: false });
form.reset();
page:exception event to surface unexpected server errors and fallback to a full reload if needed:window.addEventListener('page:exception', (page) => {
console.error('Inertia error:', page.exception);
// Optional fallback
window.location.reload();
});
Version Assumptions
This guide assumes:
- Laravel ≥ 9 with the latest
inertiajs/inertia-laraveladapter. - Vue 3 (≥ 3.2) and
@inertiajs/vue3≥ 2.0. - Node.js ≥ 14 for asset compilation.
By following the steps above, you achieve seamless client‑side navigation while preserving user input and server‑side validation messages, delivering a smoother experience without sacrificing the reliability of traditional server‑side rendering.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.