Refresh One Prop Without Re-Rendering the Page: Inertia.js Partial Reloads
Refresh a single expensive prop without remounting the page: how Inertia partial reloads work over the wire, a Laravel + Vue example with lazy props, and the mistakes that bite in production.
02 Jun 2026, 12:05 UTC

Your dashboard page loads fast except for one expensive stats query, and every refresh re-runs everything and blows away the user's scroll position and local component state. The fix is a partial reload: ask the server for just the one prop you need, and Inertia merges it into the current page without unmounting anything. Pair it with a lazy prop on the server and the expensive query only runs when someone actually asks for fresh numbers.
What a partial reload actually does
A normal Inertia visit returns the full prop set for a page. A partial reload is still a GET visit, but the client adds two headers: X-Inertia-Partial-Data (a comma-separated list of prop keys you want) and X-Inertia-Partial-Component (the name of the page component currently mounted). The server-side adapter sees those headers and includes only the requested keys in the JSON response. The client then merges those keys into the existing page props.
Because the page component never unmounts, local state — form inputs, open modals, scroll position, timers — survives. Props you didn't request keep their existing client-side values; they are not re-sent and not reset.
Worked example: Laravel backend, Vue 3 frontend
The pattern has two halves that only pay off together: a lazy prop on the server, and a targeted reload on the client.
Server: make the expensive prop lazy
In your controller (or in HandleInertiaRequests::share if it's shared), wrap the expensive computation so it is excluded from the initial payload:
use Inertia\Inertia;
public function index()
{
return Inertia::render('Dashboard', [
'projects' => Project::forUser(auth()->user())->get(),
// Only evaluated when explicitly requested via `only`
'stats' => Inertia::lazy(fn () => Stats::dashboard()),
]);
}
Stats::dashboard() is a placeholder for your own expensive query or aggregation. On the first full page load, stats is simply absent from the response; the closure is not evaluated. When a partial reload requests stats, the adapter evaluates the closure and returns just that key.
Client: reload only that prop
In the Vue page component, add a refresh button — or poll on an interval:
<script setup>
import { router } from '@inertiajs/vue3'
import { onUnmounted } from 'vue'
defineProps({ projects: Array, stats: Object })
const refreshStats = () => router.reload({ only: ['stats'] })
// Optional polling — must be cleaned up
const timer = setInterval(refreshStats, 30000)
onUnmounted(() => clearInterval(timer))
</script>
<template>
<button @click="refreshStats">Refresh stats</button>
<!-- render stats, projects -->
</template>
The same only option works on <Link> and router.visit for GET visits.
Verifying it works
- Open browser devtools, trigger the reload, and confirm the request carries
X-Inertia-Partial-Data: statsandX-Inertia-Partial-Component: Dashboard, and that the JSON response contains only thestatskey. - Put a temporary
Log::info()inside the lazy closure. It should not appear on the initial page load, and should appear when the partial reload fires. - Log inside the component's
onMounted. It should fire once across many partial reloads — proof the page is not remounting.
Limits to design around
GET visits only. Partial data applies to Link, router.visit, and router.reload. Form submissions via useForm do not accept only — after a POST the server returns the full prop set for the redirected page. If you need "submit, then refresh one prop," do the submit first and fire router.reload({ only: [...] }) in the success callback.
Component mismatch falls back to full props. If the X-Inertia-Partial-Component header doesn't match the component the server resolves, adapters return all props rather than risk merging data into the wrong page. This is a safety net, not an error — but it means a stale or mismatched component name silently costs you the optimization.
It's an optimization, not a boundary. Partial filtering reduces payload size; it is not access control. The server must still authorize every request, and "the client didn't ask for it" is not a security mechanism.
Version skew during deploys. If your asset version changes between the initial load and a partial reload, Inertia returns a 409 and forces a full page visit. While debugging, this can make partial reloads look randomly broken.
Common mistakes
- Expecting lifecycle hooks to re-run. They don't — the component stays mounted. If you need to react to fresh stats,
watch(() => props.stats, ...)instead of reaching foronMounted. - Assuming shared props refresh too. Unrequested props like the auth user keep their existing client-side values. If a background action might have changed them, include those keys in
onlyexplicitly. - Leaking polling intervals. A
setIntervalthat isn't cleared inonUnmountedkeeps firing after the user navigates away, producing duplicate or orphaned requests. Always pair the timer with cleanup, as in the example above. - Relying on lazy for performance without testing. Whether non-requested closures are evaluated during a partial reload depends on adapter internals and version. Verify with the logging check above rather than assuming.
A note on versions
Header names and helpers like Inertia::lazy() vary by adapter (Laravel, Rails, community adapters) and by major version. In the Inertia 2.x era, deferred props (Inertia::defer() in the Laravel adapter) automate a similar pattern by fetching props after the initial render — worth evaluating if you're on 2.x, but behavior differs on 1.x installs. Check your installed versions in composer.json and package.json and confirm the API against the docs for that major before shipping.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.