Your filter dropdown re-runs the expensive category query on every click. Your “load more” button replaces the list instead of growing it. Partial reloads fix both: ask Laravel for one prop, defer the slow ones, and append rows on demand.
TL;DR
- Wrap props in a closure, then
router.reload({ only: ['posts'] })evaluates just that one. Inertia::defer()leaves a prop out of the first response and fetches it right after the first paint.Inertia::merge()->append('data')appends paginator rows, but only on partial reloads.
A normal Inertia visit runs every prop on the page. Partial reloads exist so a filter, a poll, or a “load more” button does not redo the expensive ones. The visit has to target the page you are already on. A link to a different component always loads the full page.
The list those props come from is the CRUD guide. Counted pages versus cursors are pagination. The app is Inertia, a SPA, or Nuxt.
Reload one prop with only and except#
Wrap props you can skip in a closure. Inertia evaluates the closure when that key is needed. A value computed before Inertia::render runs on every visit, including a partial one.
Runs every visit, then Runs when asked:
return Inertia::render('posts/Index', [
'posts' => Post::query()->latest('id')->paginate(10)->withQueryString(),
'categories' => Category::query()->orderBy('name')->get(['id', 'name']),
]);return Inertia::render('posts/Index', [
'posts' => fn () => Post::query()->latest('id')->paginate(10)->withQueryString(),
'categories' => fn () => Category::query()->orderBy('name')->get(['id', 'name']),
]);Then ask for the one you need:
<script setup lang="ts">
import { router } from "@inertiajs/vue3";
function refreshPosts() {
router.reload({ only: ["posts"] });
}
</script>only: ['posts'] tells Laravel to skip categories. except: ['categories'] is the same idea written the other way. A <Link> can do it without a function.
<Link href="/posts?published=1" :only="['posts']">Published</Link>Heads up: reloads, not form posts
:only is a partial reload, not a partial form post.
Dot keys work for nested props. only: ['auth.notifications'] reloads that nested value and leaves the rest of auth alone.
Defer slow props until after the first paint#
Inertia::defer leaves the prop out of the first response and fetches it immediately after the first paint. The Vue <Deferred> component renders the fallback until the key arrives. #default="{ reloading }" is how you dim the list during a later reload. #rescue renders when you passed rescue: true and the callback threw.
'permissions' => Inertia::defer(fn () => Permission::query()->orderBy('name')->get(), rescue: true),<script setup lang="ts">
import { Deferred, router } from "@inertiajs/vue3";
defineProps<{
permissions?: { id: number; name: string }[];
}>();
</script>
<template>
<Deferred data="permissions" #default="{ reloading }">
<template #fallback>
<p>Loading permissions...</p>
</template>
<template #rescue="{ reloading }">
<button
type="button"
:disabled="reloading"
@click="router.reload({ only: ['permissions'] })"
>
Retry
</button>
</template>
<ul :class="{ 'opacity-50': reloading }">
<li v-for="permission in permissions" :key="permission.id">
{{ permission.name }}
</li>
</ul>
</Deferred>
</template>Gotcha: Deferred waits for a prop, not a URL
permissions on that page is still a prop. <Deferred> does not fetch a URL you invent. It waits for the prop named in data.
once() and WhenVisible#
->once() on a deferred prop is remembered only while later pages still include that prop. A visit that leaves it out forgets the value, and the next page that includes it resolves the callback again. Reload that one prop when you need a fresh value sooner.
Inertia also ships WhenVisible for a deferred prop that should wait until the block scrolls into view. The prop name is the same contract. Use it for a block below the fold, not for the main list.
Load more with Inertia::merge()#
Inertia::merge() appends on a partial reload. A full visit replaces the prop even if you marked it for merging. The documented way to append a paginator’s rows is append('data').
'posts' => Inertia::merge(
fn () => Post::query()->latest('id')->paginate(10)
)->append('data'),function more() {
router.reload({
only: ["posts"],
data: { page: props.posts.current_page + 1 },
});
}
function search(value: string) {
router.reload({
only: ["posts"],
data: { search: value, page: 1 },
reset: ["posts"],
});
}Gotcha: reset when the query changes
Reset the prop when the search changes, or page two of a new query sticks to the end of the old rows.
Previous and Next links that replace the list, instead of appending, stay on the pagination page. simplePaginate is the right object when you do not want a total. Merge is the wrong tool for that footer.
See what a partial reload sends
Open the Network tab, click a link with :only="['posts']", and look at the request headers: X-Inertia-Partial-Data: posts and X-Inertia-Partial-Component name the prop and the page. The JSON response holds only posts.
Which tool for which job#
| You want | Use |
|---|---|
| Refresh one prop (filter, poll) | Closure prop + router.reload({ only }) or <Link :only> |
| Show the page fast, load a slow prop after | Inertia::defer() + <Deferred> |
| Load a block when it scrolls into view | WhenVisible |
| Append the next page of rows | Inertia::merge()->append('data') + reset on new searches |
| Replace the list with Previous/Next | Plain pagination |
| Push changes from the server | Reverb |
Where this stops#
Live updates from the server are Reverb, not a timer that calls router.reload. A reload still asks Laravel. A broadcast pushes.
Recap#
- Closures make props lazy;
only/exceptpick which ones run on a partial reload. - Partial reloads only work on the page you are on.
Inertia::defer()+<Deferred>shows a fallback, then the prop;rescue: trueadds a retry slot.Inertia::merge()appends on partial reloads only; passresetwhen the query changes.
FAQ#
Why does my partial reload still run every query?
Props computed before Inertia::render always run. Wrap each optional prop in a closure (fn () => ...) so Inertia only evaluates the ones you ask for.
Can a partial reload load a different page?
No. The visit must target the component you are on. A link to another component always loads the full page.
Does Inertia::merge() append on a normal visit?
No. Merge appends only on partial reloads. A full visit replaces the prop.
What is the difference between Deferred and WhenVisible?
<Deferred> fetches the prop right after the first paint. WhenVisible waits until the block scrolls into view, which suits content below the fold.
Next steps#
- Paginate a Laravel list in Vue: counted pages, simple pages and cursors.
- Inertia 3.9: background polling: pair
usePollwithonly. - Server-render an Inertia app: deferred props and the first HTML.
- Inertia docs: partial reloads



