Cutting Payloads with Inertia.js Partial Reloads
Learn how Inertia's `only` option lets you fetch just the props you need, keeping client‑side state intact while shrinking network payloads.
27 Nov 2025, 09:18 UTC

The problem: full page payloads on every interaction
Inertia.js lets you build single‑page apps with server‑side routing, but a naïve visit sends the entire page prop tree — even when only a slice of data changed. For a dashboard with a filterable table, a sidebar, and a user menu, each filter click can push megabytes of JSON and force React, Vue, or Svelte components to remount, wiping out local UI state such as open dropdowns or scroll positions.
How partial reloads work
Inertia’s only option tells the client to request a subset of props. The browser sends an X-Inertia-Partial-Data header listing the keys you want. The server‑side adapter (Laravel’s inertia() helper, Rails’ Inertia::render, etc.) reads that header and serializes only those keys. The response is still a full Inertia JSON payload, but the props object contains only the requested entries.
- Client call:
router.visit(url, { only: ['items', 'links'] })or<Link href="..." only={["items", "links"]} />. - Server automatically filters — no extra controller logic required.
- Components that don’t receive new props stay mounted; their local state (form inputs, toggles, scroll) survives.
Worked example: paginated, filterable table
Imagine a ProductsIndex page that receives three props:
{
"filters": { "search": "", "category": "all" },
"items": [ ... ],
"links": { "first": "...", "last": "...", "prev": null, "next": "..." }
}
The filter form lives in a sibling component that only cares about filters. The table component needs items and links. When the user changes a filter, we want fresh data without remounting the filter form.
// In the filter component (React example)
import { useForm, Head } from '@inertiajs/react';
const FilterForm = ({ initialFilters }) => {
const { data, setData, post } = useForm(initialFilters);
const submit = (e) => {
e.preventDefault();
// Only ask for the table data; keep `filters` locally
router.visit(route('products.index'), {
method: 'get',
data: data(),
only: ['items', 'links'],
preserveState: true // avoid new history entry
});
};
return (
…
Apply
);
};
The server controller stays unchanged:
public function index(Request $request)
{
$products = Product::query()
->when($request->search, fn($q) => $q->where('name', 'like', "%{$request->search}%"))
->when($request->category !== 'all', fn($q) => $q->where('category', $request->category))
->paginate(15);
return inertia('Products/Index', [
'filters' => $request->only(['search', 'category']),
'items' => $products->items(),
'links' => (string) $products->links(),
]);
}
Because the request carries X-Inertia-Partial-Data: items,links, Inertia’s Laravel adapter returns only those two keys. The FilterForm component never receives new filters props, so its internal useForm state remains untouched.
Trade‑offs and gotchas
Server still does the full work
The controller runs the same query and builds the entire prop array; filtering happens at serialization time. Partial reloads save network bytes and client reconciliation, not CPU cycles.
Missing keys retain old values
If the server accidentally omits a key you listed in only, Inertia merges the response with the existing page props — the stale value stays. Always ensure the only list matches what the component actually consumes.
Lifecycle events differ
Partial reloads fire inertia:request-start and inertia:request-finish but skip inertia:navigate. Code that relies on the navigation event (e.g., analytics pageview tracking) won’t run unless you trigger it manually.
Browser history
By default a partial reload creates a new history entry. Pass preserveState: true to replace the current entry, or omit it when the URL genuinely changes (e.g., pagination links).
TypeScript safety
Inertia’s generated types describe the full page prop shape. When you request a subset, TypeScript still expects the full object. You’ll need a local type or a utility like Pick to keep the compiler happy.
Quick verification checklist
- Open the browser dev‑tools Network tab.
- Trigger a partial reload (e.g., click a pagination link that uses
only). - Inspect the response JSON — the
propsobject should contain only the keys you requested. - Add a
console.login a sibling component’suseEffect/onMountedand confirm it does not fire during the partial reload. - Submit a form that fails validation, then call
router.visit(url, { only: ['errors'] }); verify the error bag updates without a full page flash.
Takeaway
Partial reloads are a lightweight lever: they shrink the JSON payload, keep mounted components alive, and require almost no server‑side changes. Use them for high‑frequency updates — pagination, infinite scroll, live filters — but remember they don’t reduce server work and they shift a few responsibilities (history, typing, lifecycle) onto you. Start by adding only to a single Link or router.visit, verify the network payload, then expand the pattern where the UX gain is clear.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.