skip to content

A Laravel Inertia dashboard waits two seconds on a revenue chart query; how would you use Inertia::defer and <Deferred>, and what does it cost?

level: seniorimportance: should knowfreq 30%

answer

  1. announced in deferredProps, not sent
  2. one partial reload per group
  3. <Deferred data=... fallback=...>
  4. the controller runs again for each group
  5. rescue: true since 3.1

basics

~20 s

Wrap the chart in Inertia::defer(fn () => ...): the first response only announces it, the client fetches it in a follow-up partial reload, and <Deferred> shows a fallback. Costs: extra requests that re-run the controller, and no chart in the first HTML.

solid answer

~40 s

`'revenueChart' => Inertia::defer(fn () => $reports->revenueByDay())` leaves the prop out of the first response and announces it in the page object's `deferredProps`, grouped under `default` unless you name a group. After mounting, the client sends one partial reload per group with `only` set to that group's props, so slow props in separate groups load in parallel. On the page, `<Deferred data="revenueChart" fallback={<ChartSkeleton />}>` renders the fallback until the prop arrives. The costs: every group is another request through middleware and the controller, so eager, non-closure work in the action runs again each time; the chart is absent from the first HTML and from SSR output; and a failing closure fails that request unless you pass `rescue: true` (inertia-laravel 3.1+), which reports the exception, omits the prop and lets `<Deferred>` render its `rescue` slot.

code

php · 23 lines
php
<?php

namespace App\Http\Controllers;

use App\Services\Reports;
use Inertia\Inertia;
use Inertia\Response;

class DashboardController extends Controller
{
    public function __invoke(Reports $reports): Response
    {
        return Inertia::render('Dashboard', [
            'kpis' => fn () => $reports->kpis(),
            'topProducts' => Inertia::defer(fn () => $reports->topProducts()),
            'revenueChart' => Inertia::defer(
                fn () => $reports->revenueByDay(days: 90),
                group: 'charts',
                rescue: true,
            ),
        ]);
    }
}

go deeper

for a junior

Recall that Inertia::defer lets the page render first and loads slow data afterwards, with <Deferred> showing a fallback.

for a middle

Explain deferredProps in the page object, one reload per group, the <Deferred> fallback and reloading states, and choosing groups.

for a senior

Weigh extra requests and controller re-runs against perceived speed, wrap sibling props in closures, and use rescue for resilient pages.

for a principal

Decide when to defer, cache or precompute slow data across the product, balancing server load, SEO needs and perceived performance.

## The problem deferred props solve Without deferral, the dashboard's first response waits for its slowest prop: a two-second revenue aggregation holds back the KPIs, the header and everything else. **Deferred props** let the page render with the fast data and fetch the slow data in a follow-up request. ## Server side `Inertia::defer(callable $callback, string $group = 'default', bool $rescue = false)` returns a `DeferProp`. On a full visit the adapter does not call the callback; it adds the prop's key to the page object's `deferredProps`, grouped by name: ```json "deferredProps": { "default": ["topProducts"], "charts": ["revenueChart"] } ``` Groups are arbitrary strings. Everything in one group arrives in one request; different groups arrive in separate requests. ## Client side After the page mounts, the client reads `deferredProps` and, for each group, runs a reload with `only` set to that group's keys. These reloads are asynchronous and preserve state and scroll, so the groups load in parallel. On the page: - `<Deferred data="revenueChart" fallback={<ChartSkeleton />}>` renders the fallback until the prop exists, then its children. - `data` accepts an array to wait for several props. - In Inertia 3 the component exposes a `reloading` flag to its children while a later partial reload refreshes the data; React's `<Deferred>` no longer drops back to the fallback during reloads. - A `rescue` slot renders when the server rescued a failed prop. ## What it costs | Cost | Detail | |---|---| | Extra requests | one per group; each passes through the full middleware stack, session and authentication | | Controller re-runs | the deferred request hits the same action, so plain, non-closure props are computed again and discarded | | First paint without data | the chart is missing from the initial HTML and from any SSR output | | Layout shift | the fallback must reserve space, or the page jumps when the data lands | | Error handling | a throwing callback fails that request unless rescued | The second row is the one teams miss. If the action also computes `'kpis' => $service->kpis()` as a plain value, the KPIs are calculated on the first request and again for every deferred group. Wrap the other expensive props in closures so the follow-up requests only do the deferred work. ## Failures: `rescue` Since inertia-laravel 3.1, `Inertia::defer(fn () => ..., rescue: true)` catches an exception from the callback, reports it through Laravel's exception handler, omits the prop and lists its key in `rescuedProps`. The response is still `200` with every other prop. The page's `<Deferred>` shows its `rescue` slot, where a retry button can call `router.reload({ only: ['revenueChart'] })`. ## Design choices - **Group by latency.** Put independent slow props in separate groups so a slow chart does not hold back a moderately slow table. - **Do not over-split.** Each group is a full request; ten groups for ten small props costs more than it saves. - **Combine with once.** `Inertia::defer(fn () => ...)->once()` resolves the data once and lets the client remember it across pages that include it. - **Prefer deferral to optional props when the data is always needed.** `Inertia::optional()` waits for an explicit request; `defer` fetches automatically. - **Consider caching first.** If the aggregation can be cached, a warm cache may remove the need to defer at all. ## A checklist before deferring - Measure first: is the prop really the slow part, or is it the whole action? - Confirm the page can render meaningfully without it; a page that is mostly the chart gains little. - Give the fallback a fixed size that matches the chart. - Wrap the action's other expensive props in closures. - Decide whether a failed chart should fail the request or render the `rescue` slot. ## Testing The `assertInertia` object offers `loadDeferredProps('charts', fn ($page) => $page->has('revenueChart'))`, which reads `deferredProps` from the first response and performs the follow-up partial reload inside the test.

  • When would you choose Inertia::optional over Inertia::defer?
    When the data is not needed unless the user asks, such as a preview behind a button. An optional prop is neither sent nor announced on a full visit and loads only when a partial reload selects it. A deferred prop is announced in `deferredProps` and fetched automatically right after the page mounts, so it suits data the page always shows but should not wait for.
  • Why can deferring make total server work higher rather than lower?
    Each group is an extra request that runs middleware, session handling, authentication and the controller again. Plain props computed eagerly in the action are recalculated and thrown away on each follow-up request. Unless the other expensive props are closures, the deferred version of the page can cost more server time in total even though it feels faster.

saying these in an interview costs you the question

  • Deferred props are streamed in the same response after the HTML.
  • Every deferred prop gets its own request regardless of group.
  • The deferred request only runs the deferred closure, not the controller.
  • Deferred props are included in the server-side rendered HTML.
  • A throwing deferred closure is always swallowed and returns null.