# 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.

- URL: https://laravel-vuejs.space/inertia-partial-reloads
- Author: Anass Ez-zouaine (https://laravel-vuejs.space/author/anass-ez-zouaine)
- Published: 2026-10-10
- Topics: Laravel, VueJs, Inertia

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](https://laravel-vuejs.space/laravel-inertia-vue-crud). Counted pages versus cursors are [pagination](https://laravel-vuejs.space/laravel-vue-pagination). The app is [Inertia, a SPA, or Nuxt](https://laravel-vuejs.space/laravel-vue-in-2026).

## 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:**

`app/Http/Controllers/PostController.php`
```php
return Inertia::render('posts/Index', [
    'posts' => Post::query()->latest('id')->paginate(10)->withQueryString(),
    'categories' => Category::query()->orderBy('name')->get(['id', 'name']),
]);
```

**Runs when asked:**

`app/Http/Controllers/PostController.php`
```php
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.vue`
```vue
<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.vue`
```vue
<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.php`
```php
'permissions' => Inertia::defer(fn () => Permission::query()->orderBy('name')->get(), rescue: true),
```

`resources/js/pages/roles/Edit.vue`
```vue
<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.php`
```php
'posts' => Inertia::merge(
    fn () => Post::query()->latest('id')->paginate(10)
)->append('data'),
```

`resources/js/pages/posts/Index.vue`
```ts
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](https://laravel-vuejs.space/laravel-vue-pagination) |
| Push changes from the server | [Reverb](https://laravel-vuejs.space/laravel-reverb-vue) |

## Where this stops

Live updates from the server are [Reverb](https://laravel-vuejs.space/laravel-reverb-vue), 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

**Q: 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.

**Q: 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.

**Q: Does Inertia::merge() append on a normal visit?**

No. Merge appends only on partial reloads. A full visit replaces the prop.

**Q: 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](https://laravel-vuejs.space/laravel-vue-pagination): counted pages, simple pages and cursors.
- [Inertia 3.9: background polling](https://laravel-vuejs.space/inertia-3-9-background-polling-navigate-type): pair `usePoll` with `only`.
- [Server-render an Inertia app](https://laravel-vuejs.space/inertia-ssr): deferred props and the first HTML.
- [Inertia docs: partial reloads](https://inertiajs.com/docs/v3/data-props/partial-reloads)

## Sources

- [Inertia v3: Partial reloads](https://inertiajs.com/docs/v3/data-props/partial-reloads)
- [Inertia v3: Deferred props](https://inertiajs.com/docs/v3/data-props/deferred-props)
- [Inertia v3: Merging props](https://inertiajs.com/docs/v3/data-props/merging-props)
- [Inertia v3: Load when visible](https://inertiajs.com/docs/v3/data-props/load-when-visible)
