With Inertia on Laravel, when do Inertia::once and shareOnce save work, and how can a remembered once prop go stale?
answer
- resolved once, remembered by the client
- X-Inertia-Except-Once-Props header
- fresh(), until(), as()
- forgotten on pages without the prop
- null for guests, once when signed in
basics
~20 sInertia::once(fn () => ...) resolves a prop once; the client remembers it and lists it in X-Inertia-Except-Once-Props so later visits skip it. It goes stale when the data changes and nothing forces fresh(), an expiry or a partial reload.
solid answer
~50 s`Inertia::once(fn () => Team::all())` marks a prop to be resolved once. The page object records it in `onceProps` with an optional `expiresAt`, and on later visits the client sends the loaded, unexpired keys in `X-Inertia-Except-Once-Props`; the server then skips the callback and leaves the prop out while the client reuses its copy. `Inertia::shareOnce('teams', fn () => ...)`, or a `shareOnce()` method in `HandleInertiaRequests`, does the same for shared props. It saves a query and payload on every visit for data that rarely changes. Staleness is the trade-off: the server cannot know the client's copy is outdated. Refresh with `->fresh()` or `->fresh($condition)`, bound the lifetime with `->until(now()->addHour())`, or reload it with `router.reload({ only: ['teams'] })`. The classic trap is the signed-in user: share `null` for guests and a once prop only when signed in, so login and logout do not leave an old user remembered.
code
php · 26 lines<?php
namespace App\Http\Middleware;
use App\Models\Team;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Middleware;
class HandleInertiaRequests extends Middleware
{
public function share(Request $request): array
{
return [
...parent::share($request),
'auth' => $request->user()
? Inertia::once(fn () => $request->user()->only('id', 'name'))
: null,
];
}
public function shareOnce(Request $request): array
{
return ['teams' => fn () => Team::get(['id', 'name'])];
}
}go deeper
Recall that once props are sent once and then reused by the client while it navigates between pages that include them.
Explain the onceProps metadata, the X-Inertia-Except-Once-Props header, and the fresh, until and as modifiers.
Identify staleness risks such as identity and permissions, apply the null-for-guests pattern, and refresh after mutating actions.
Decide which shared data may be remembered on the client, weighing payload savings against staleness and security.
## What a once prop is Some data is needed on many pages, rarely changes and costs something to build: the teams a user can filter by, a country list, a plan catalogue. Sending it on every visit wastes a query and payload. A **once prop** is resolved on the first page that includes it and then **remembered by the client**. ## How the exchange works 1. The server resolves `Inertia::once(fn () => Team::all())` normally and adds an entry to the page object's `onceProps`, mapping a key to `{ prop, expiresAt }`. 2. On the next Inertia visit, the client sends `X-Inertia-Except-Once-Props: teams`, listing once props it holds and that have not expired. 3. The adapter sees the key, **skips the callback** and omits the prop from `props`, while still emitting its `onceProps` entry. 4. The client keeps using its stored value. Two boundaries apply: - A first HTML load, with no `X-Inertia` header, always resolves once props. - The client remembers a once prop only while it moves between pages that include it; visiting a page without it forgets the value, and the next page that has it resolves it again. ## Declaring them | Where | How | |---|---| | A page prop | `'teams' => Inertia::once(fn () => Team::all())` | | Shared, inline | `Inertia::share('teams', Inertia::once(fn () => ...))` | | Shared, shortcut | `Inertia::shareOnce('teams', fn () => ...)` | | Middleware | a `shareOnce(Request $request): array` method in `HandleInertiaRequests` | | Combined | `Inertia::defer(fn () => ...)->once()`, and likewise on merge and optional props | ## Controlling freshness - `->fresh()` or `->fresh($condition)` forces the server to resolve and send the prop even when the client listed it as loaded; the new value and expiry replace the client's copy. - `->until($time)` sets an expiry from a `DateTimeInterface`, a `DateInterval` or a number of seconds; after it passes, the client stops listing the key. - `->as('teams')` gives a custom key, so two pages with different prop names, such as `teamFilter` and `availableTeams`, share one remembered value. - A **partial reload** that requests the prop always resolves it, because the except-once header is ignored on partial reloads. ## How once props go stale The server has no view of the client's copy, so staleness is the price of the saving: 1. **Data changes elsewhere.** A colleague creates a team; this user's filter keeps the old list until an expiry, a `fresh()` condition or a reload. 2. **Identity changes.** Sharing `'auth' => Inertia::once(fn () => $request->user())` looks attractive, but login and logout usually redirect to a page that does not explicitly request `auth`, so the remembered user survives. The documented pattern returns `null` for guests, which overwrites the remembered value, and a once prop only when signed in. 3. **Actions on the same page.** After renaming a team, flash a success toast and return a response that marks the prop `fresh()`, or have the client reload it. ## Observing it in the browser The network tab makes the exchange visible. The first Inertia visit to a page with `teams` returns the value and an `onceProps.teams` entry. The next visit's request carries `X-Inertia-Except-Once-Props: teams`, and its response has the `onceProps` entry but no `teams` in `props`. If the header is missing, check that the previous page included the prop and that `expiresAt` has not passed. ## When not to use it - Values that change on most requests, such as an unread-notification count. - Anything security-sensitive whose staleness matters, such as permissions after a role change, unless it has a short `until()` and is refreshed on the actions that change it. - Tiny values where the header and bookkeeping cost more than the saving.
- Why does a once prop get resolved again after visiting a page that does not include it?The client remembers once props only while navigating between pages that include them. When a visited page lacks the prop, the stored value is forgotten, so the next page that includes it does not list it in `X-Inertia-Except-Once-Props`, and the server resolves it again. Sharing the prop, or giving related pages the same key with `as()`, keeps it alive.
- How do once props interact with a partial reload that requests them?A partial reload that names a once prop always resolves it; the adapter ignores the except-once list on partial reloads. That makes `router.reload({ only: ['teams'] })` the client-side way to refresh a remembered value, for example after the user creates a team in a modal.
A regular at a cafe tells the waiter "I still have a menu", so no new one is brought. If the kitchen changes the menu, the regular keeps reading the old one until the waiter is told to hand over a fresh copy. The client's header is the regular's remark; fresh() is the instruction to bring a new menu anyway.
saying these in an interview costs you the question
- Once props are cached on the server between requests.
- A once prop is resolved only once per session, even on full reloads.
- Calling Inertia::flushShared() on logout clears the client's copy.
- until() accepts only a number of minutes.
- Once props suit values that change on most requests.