skip to content

In Livewire 4, how do #[Lazy] and #[Defer] keep a slow sales-chart component from blocking the page, and how do they differ?

level: middleimportance: must knowfreq 42%

answer

  1. page renders first, component after
  2. placeholder() or @placeholder shows meanwhile
  3. Lazy: when scrolled into view
  4. Defer: right after page load
  5. mount() runs in the second request

basics

~20 s

Both render a placeholder instead of the component on the first page load and fetch the real component in a separate request; #[Lazy] loads it when it scrolls into view, #[Defer] loads it immediately after the page loads.

solid answer

~40 s

With `#[Lazy]` or `#[Defer]` on the class, or `lazy` / `defer` on the tag, Livewire skips the component's `mount()` and render on the initial request and outputs a **placeholder** instead: the component's `placeholder()` method, an `@placeholder` block in a single-file or multi-file component, the `component_placeholder` config view, or an empty `<div>`. The browser then asks the server for the real component. `#[Lazy]` triggers that request when the placeholder **enters the viewport**; `#[Defer]`, new in Livewire 4, triggers it **as soon as the page has loaded**. So a chart below the fold suits Lazy and one at the top suits Defer. Each loads in its own parallel request by default; `bundle: true` groups them into one. Props passed in are serialised until the second request, and `:lazy="false"` or `:defer="false"` switches it off for one usage.

code

php · 20 lines
php
<?php

use App\Models\Order;
use Livewire\Attributes\Defer;
use Livewire\Component;

new #[Defer] class extends Component {
    public array $series = [];

    public function mount(string $range = 'year'): void
    {
        // slow aggregate, now off the critical path
        $this->series = Order::revenueByMonth($range);
    }

    public function placeholder(array $params = [])
    {
        return view('placeholders.chart-skeleton', $params);
    }
};

go deeper

for a junior

Recall that Lazy and Defer show a placeholder first and load the component in a second request.

for a middle

Explain viewport-triggered Lazy versus load-triggered Defer, the placeholder sources, and that mount() moves to the follow-up request.

for a senior

Weigh perceived speed against extra requests, decide on bundling by load-time similarity, and watch serialised props.

for a principal

Set guidelines for which dashboard widgets may block the first paint and which must load after it.

## The problem A **sales dashboard** often has one expensive component, such as a revenue chart that aggregates a year of orders in `mount()`. Rendered normally, the whole page waits for that query: the server cannot send any HTML until every component on the page has rendered. **Lazy and deferred loading** move the expensive component out of the first response so the rest of the page appears immediately. ## How it works 1. On the first request Livewire sees the component should be delayed, **skips `mount()`** and the real render, and outputs placeholder HTML instead. 2. The placeholder carries a small instruction that calls back to the server, with the component's mount parameters serialised into it. 3. The browser sends a second request; Livewire now runs `mount()` with those parameters and renders the real component, which replaces the placeholder. The two modes differ only in **when step 3 starts**: | | `#[Lazy]` / `lazy` | `#[Defer]` / `defer` | |---|---|---| | Loads when | the placeholder enters the viewport | immediately after the page loads | | Browser trigger | an intersection observer | an init hook on page load | | Best for | below-the-fold widgets | above-the-fold slow widgets | | Available | before v4 | new in v4 (older syntax: `lazy="on-load"`) | ## The placeholder Livewire picks the placeholder in this order: a `placeholder()` method on the component (it receives the mount parameters as `$params` and may return a view), an `@placeholder ... @endplaceholder` block in a single-file or multi-file component's view, the view named in the `component_placeholder` config key (default `null`), and otherwise an empty `<div></div>`. The placeholder's **root element must match** the component's root element type, or the swap misbehaves. A skeleton with the chart's dimensions avoids layout shift when the real chart arrives. ## Choosing per usage and bundling - The attributes set a component-wide default; `<livewire:sales-chart :lazy="false" />` or `:defer="false"` turns it off for one usage, and `lazy` or `defer` on the tag turns it on for one usage. - By default every lazy or deferred component loads in **its own request, in parallel**. With many similar widgets, `#[Lazy(bundle: true)]`, `#[Defer(bundle: true)]` or the `lazy.bundle` / `defer.bundle` tag modifiers send them in one request; the docs warn that a slow widget then holds up the fast ones. - Full-page components can be delayed from the route: `Route::livewire('/dashboard', 'pages::dashboard')->lazy()` or `->defer()`. ## Trade-offs - **More requests.** Each delayed component costs one extra HTTP round trip and one more Livewire request on the server; the page feels faster, but the server does the same work plus overhead. - **Serialised props.** Parameters passed to a lazy component are dehydrated into the placeholder and hydrated again for the second request; an Eloquent model is re-queried from the database. - **Tests see placeholders.** Component tests render the placeholder unless lazy loading is switched off, which the testing topic covers. ## Common mistakes 1. Using `#[Lazy]` for a chart at the top of the page, then wondering why it waits for a scroll event that never fires on tall screens; that is what `#[Defer]` is for. 2. A placeholder whose root is a `<span>` for a component whose root is a `<div>`. 3. Bundling a slow chart with fast counters, so all of them wait for the slowest. 4. Expecting `mount()` to have run during the first page render of a lazy component. ## How to decide on the dashboard Start by asking what the user must see first. The KPI totals the user opened the page for should render normally, so the page is useful immediately. The heavy revenue chart at the top becomes `#[Defer]`, so it starts loading at once but no longer holds up the first response. A "regional breakdown" table further down becomes `#[Lazy]`, so users who never scroll never pay for it. Four small, similar widgets in a sidebar can be bundled into one deferred request. Each choice trades one extra round trip for a faster first paint, and the placeholder skeleton keeps the layout stable while the real content arrives.

  • Why can passing an Eloquent model into a lazy component cause an extra query?
    The first request never runs the component's `mount()`, so Livewire serialises the passed parameters into the placeholder. On the follow-up request it hydrates them again, which for a model means re-querying it by key before `mount()` receives it.
  • When is bundling several deferred widgets a bad idea?
    When their load times differ a lot. A bundled request returns only when every component in it has rendered, so one slow chart delays the fast counters that would otherwise have appeared first. Bundle similar, cheap widgets; leave the slow one isolated.

saying these in an interview costs you the question

  • Says #[Lazy] makes the server render the component later within the same request.
  • Thinks #[Defer] waits for the component to scroll into view.
  • Believes lazy loading reduces the total server work for the component.
  • Expects mount() to have run during the placeholder render.
  • Uses a placeholder root element different from the component's root.