Inertia.js patterns: partial reloads, deferred props, and shared data
Inertia's pitch is attractive: no API layer, no duplicated routes, controllers render props and Vue components receive them. The tradeoff it hides in the small print is that every navigation is a full page load carrying every prop the page needs. On a dashboard with filters, tabs, and a couple of heavy widgets, that turns into a lot of data you didn't need this time.
None of this means Inertia was the wrong choice with the API bolted on later as a fix. There's a set of patterns that keeps it fast.
Partial reloads: fetch only what changed
The filter bar on a listing page is the classic case. Selecting a status doesn't need to re-send the page title, the sidebar, or the 200 rows that just rendered. A partial reload asks the same route but receives only the named props:
import { router } from '@inertiajs/vue3'
const applyFilters = (filters) => {
router.reload({
only: ['users'],
data: filters,
preserveState: true,
})
}
router.reload() hits the current URL, so the same controller produces the same props with the new query parameters. The rest of the page doesn't blink. Tabs, sorting, dropdown filters, and "load more" all reduce to this shape.
One rule to keep in mind: a partial reload only returns props that the route itself can produce. If the data lives in a different controller, you're reaching for a fetch call or an API route, and that's fine; partial reloads aren't an API substitute, they're a same-page-update tool.
Lazy and deferred props: stop shipping what isn't on screen yet
Two related tools, both part of the Laravel adapter:
Inertia::render('Dashboard', [
'recentOrders' => Order::latest()->limit(5)->get(),
'stats' => Inertia::defer(fn () => $this->computeStats()),
'export' => Inertia::lazy(fn () => $this->buildExportPayload()),
]);
- Deferred props land in a second, automatic request after the page renders. The shell paints immediately, heavy widgets stream in, and the
Deferredcomponent gives you the loading state slot for free. This is the right default for dashboard tiles nobody needs on first paint. - Lazy props are evaluated only when a partial reload names them. Good for payloads triggered by an explicit action, like an export preview or expanding a details panel.
Shared props: small on purpose
HandleInertiaRequests::share() is where props common to every page live: the auth user, flash messages, the app name. The discipline is keeping it small. It is easy to share half the database and pay for it on every single navigation, since shared props ride along everywhere. If a prop isn't needed on a majority of pages, it belongs on those pages, not in share().
State that survives a redirect
preserveState keeps component state, and the remember option restores it across navigation, which is what makes "filtered and sorted table, opened one row, came back and the filter is still applied" work by default instead of with a dozen watchers. Combine both on listing pages and back-navigation complaints mostly disappear.
Polling without sockets
Realtime needs WebSockets, and we've written about that setup separately. But a status widget refreshing every 30 seconds does not need a socket server: router.reload({ only: ['status'] }) on an interval gets you a self-updating page with one retrying HTTP request and zero new infrastructure. Rough polling is also a decent bridge if realtime is on the roadmap but not this week.
Where Inertia runs out
Multiplayer editing, offline work, or one view with thousands of live-updating elements are API territory from the start, and Inertia shouldn't get stretched over them. For most business apps, though: listing pages, forms, dashboards, admin panels, the patterns above keep it plenty fast, and the codebase stays one Laravel app instead of two.