Inertia partial reloads in Vue

router.reload can ask Laravel for one prop. Deferred props wait until after the first paint. Merge appends on those partial visits only.

For your AI agent

Copies a prompt. Paste it into the AI agent you already have open.

Read https://laravel-vuejs.space/inertia-partial-reloads.md
router.reload can ask Laravel for one prop. Deferred props wait until after the first paint. Merge appends on those partial visits only.
Show the diff. Do not commit.

Next: Upgrade a Laravel 13 + Vue app from Inertia 2 to Inertia 3: the practical checklist

On this page · 9 sections
  1. Reload one prop with only and except
  2. Defer slow props until after the first paint
  3. once() and WhenVisible
  4. Load more with Inertia::merge()
  5. Which tool for which job
  6. Where this stops
  7. Recap
  8. FAQ
  9. Next steps
  10. Sources

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.

What you'll learn

  • only and except on router.reload and <Link>
  • Deferred props with the <Deferred> component
  • Load more with Inertia::merge()

Who it's for: Laravel + Vue developers using Inertia 3

Applies to: Laravel 13 · inertia-laravel 3 · @inertiajs/vue3 3.9 · Vue 3.5

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:

app/Http/Controllers/PostController.phpphp
return Inertia::render('posts/Index', [
    'posts' => Post::query()->latest('id')->paginate(10)->withQueryString(),
    'categories' => Category::query()->orderBy('name')->get(['id', 'name']),
]);
app/Http/Controllers/PostController.phpphp
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:

resources/js/pages/posts/Index.vuevue
<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.

resources/js/pages/posts/Index.vuevue
<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.

app/Http/Controllers/RoleController.phpphp
'permissions' => Inertia::defer(fn () => Permission::query()->orderBy('name')->get(), rescue: true),
resources/js/pages/roles/Edit.vuevue
<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').

app/Http/Controllers/PostController.phpphp
'posts' => Inertia::merge(
    fn () => Post::query()->latest('id')->paginate(10)
)->append('data'),
resources/js/pages/posts/Index.vuetypescript
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 wantUse
Refresh one prop (filter, poll)Closure prop + router.reload({ only }) or <Link :only>
Show the page fast, load a slow prop afterInertia::defer() + <Deferred>
Load a block when it scrolls into viewWhenVisible
Append the next page of rowsInertia::merge()->append('data') + reset on new searches
Replace the list with Previous/NextPlain pagination
Push changes from the serverReverb

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/except pick 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: true adds a retry slot.
  • Inertia::merge() appends on partial reloads only; pass reset when 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#

Sources#

Found this useful?

Ask questions, share what you built.

Join thousands of Laravel and Vue developers in our Facebook group, or get new posts in your Telegram feed. Sharing this article helps too.

Keep reading

Newsletter

Get the best of Laravel & Vue, once a month.

One short email with new guides, release notes worth reading and hand-picked community links. No spam, unsubscribe any time.