Architecture Note: Using Inertia.js preserveState for Form‑Data Retention
How to retain form values, scroll position and component state across client‑side navigations with Inertia.js visit preserveState, including requirements, minimal design, trust boundaries, operational checks and failure modes.
01 Feb 2026, 19:54 UTC

Requirements
To use preserveState you need:
- An Inertia.js adapter installed (Laravel, Rails, Node/Express, etc.) – the server must render the initial page and respond to
X-Inertiarequests with JSON. - A page component that mounts a form (or any stateful UI) and expects to keep its internal state across navigations.
- Client‑side JavaScript bundle that includes
@inertiajs/inertia(or the framework‑specific wrapper).
The goal is to avoid losing user‑entered data when navigating away from a form and back, without building a separate API layer.
Smallest Suitable Design
The minimal implementation adds the preserveState option to the visit call that triggers the navigation. No extra stores, middleware or versioning are required.
Example: Form with preserveState
// resources/js/Pages/Post/Create.js (Vue example)
import { useForm } from '@inertiajs/inertia-vue3'
export default {
setup() {
const form = useForm({
title: '',
body: '',
})
const submit = () => {
form.post(route('posts.store'), {
preserveState: true, // <‑‑ keep form data after success
onSuccess: () => form.reset(),
})
}
return { form, submit }
}
}
When the user clicks “Save”, Inertia sends a POST request via visit. If the server redirects back to the same /posts/create page, Inertia reuses the cached page response and merges the preserved form values (title, body) into the newly mounted component, so the fields remain filled.
Trust/Data Boundaries
Inertia treats the JSON page payload as trusted data coming from the server. The preserveState option only copies values that were already present in the client‑side component’s state (form fields, scroll position, mounted component instances). It does not transmit arbitrary client data to the server; the server still receives only the fields submitted in the request body.
Thus the trust boundary remains at the server: the client cannot inject new props via preserveState. Any attempt to manipulate the preserved state would be limited to the same component instance and would be overwritten on the next full page load.
Operational Checks
To verify that preserveState is working:
- Open the browser DevTools → Network tab.
- Filter for requests containing the header
X-Inertia. - Submit the form and observe a POST request with JSON body.
- After the server redirects (typically a 302 to the same page), look for a subsequent GET request also with
X-Inertia. - In the response preview of that GET, note the
componentfield (e.g.,Posts/Create) and thepropsobject. - In the Elements panel, locate
<div id="__inertia">– it holds the initial props sent on the first load. - After navigation, inspect the form fields in the DOM; they should retain the values you entered before the submit.
- Run
console.log(window.Inertia.visit)in the console to confirm the method exists and inspect its signature (the third argument is the options object wherepreserveStatelives).
No special logging is required; the presence of the X-Inertia header and the unchanged form values indicate correct behavior.
Failure Modes
- Stale data: If the server updates the underlying data source (e.g., a post’s title changes via another user) between the initial visit and the preserved navigation, the form will show the outdated values because Inertia reuses the cached JSON. Mitigation: after a known mutation, call
visit(route('posts.create'), { preserveState: false, replace: true })to force a fresh fetch, or rely on server‑side validation to reject stale submissions. - Component mismatch:
preserveStateonly works when the destination page renders the same component as the source. Navigating to a different component (e.g., from/posts/createto/posts/edit/1) will discard the preserved state because Inertia cannot merge props for a different component tree. - Non‑serializable state: Only plain‑serializable values (strings, numbers, booleans, null, arrays, plain objects) are preserved. Instances of classes, functions, or DOM nodes are lost. If you need to keep complex state, lift it into a prop or use a client‑side store outside Inertia’s scope.
- History‑API limitations: If the application manually manipulates
history.stateor useshistory.replaceStateoutside Inertia’s flow, the preserved state may become desynchronized.
Conditions That Would Change the Design
Reconsider using preserveState if:
- Your application frequently navigates between different component trees (e.g., a dashboard with many distinct widgets). In that case, a global client‑side store (Redux, Vuex, Pinia) may be more appropriate.
- You require optimistic UI updates that need to roll back on error;
preserveStateonly retains successful navigation state. - The server cannot guarantee idempotent GET responses for the same URL (e.g., it includes timestamps or random nonces). Caching would then serve stale or inconsistent data, breaking the assumption that a cached page is safe to reuse.
- You need to support browsers without the History API (very old browsers). Inertia’s core navigation relies on
pushState/replaceState; a fallback would require full page loads, makingpreserveStateirrelevant.
In those scenarios, you might opt for explicit prop passing, a dedicated API layer, or a client‑side cache invalidation strategy instead of relying on Inertia’s automatic page caching.
Summary
The preserveState option on Inertia.js’s visit method provides a lightweight way to keep form data, scroll position and component mount state across client‑side navigations without extra state management. It works when the source and destination pages render the same component, the server returns cacheable JSON responses, and the client state is serializable. Verify its operation by checking for X-Inertia requests, inspecting the __inertia div, and confirming that form values persist after navigation. Be aware of stale data, component mismatches and non‑serializable state as the primary failure modes, and consider alternative designs when those conditions arise.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.