Livewire v3 Lazy Components, Islands, and Deferred Loading in Practice
#livewire #laravel #performance #frontend

Livewire v3 Lazy Components, Islands, and Deferred Loading in Practice

1 min read Mohamed Said Mohamed Said

Why Initial Page Weight Kills Perceived Performance

Every Livewire component mounted on a page adds a JSON snapshot to the initial HTML payload. For dashboards with a dozen widgets, that snapshot cost compounds quickly. Livewire v3 ships three complementary tools to defer that cost: lazy components, islands, and the wire:init / #[Lazy] deferred-loading pattern. They are not interchangeable — knowing which to reach for is the real skill.


Lazy Components with #[Lazy]

Adding the #[Lazy] attribute to a component class tells Livewire to skip the full mount during the initial render and instead emit a lightweight placeholder. A subsequent HTTP request hydrates the real component.

<?php

namespace App\Livewire\Dashboard;

use Livewire\Attributes\Lazy;
use Livewire\Component;

#[Lazy]
class RevenueChart extends Component
{
    public array $series = [];

    public function mount(): void
    {
        // Runs only on the deferred request, not on initial page load.
        $this->series = RevenueQuery::forCurrentMonth();
    }

    public function render()
    {
        return view('livewire.dashboard.revenue-chart');
    }
}

The placeholder is whatever your placeholder() method or a placeholder.blade.php sibling view returns:

public function placeholder()
{
    return <<<'BLADE'
    <div class="animate-pulse h-48 bg-gray-100 rounded-xl"></div>
    BLADE;
}

Isolating Lazy Requests

By default, all lazy components on a page are batched into a single deferred request. If one component is slow, it blocks the others. Pass isolate: true to give a component its own request:

#[Lazy(isolate: true)]
class SlowExternalFeed extends Component { /* ... */ }

Use isolate: true only when a component's data source is genuinely independent and slow. Batching is cheaper for components that finish quickly.


wire:init for Inline Deferred Calls

wire:init fires a component action immediately after the component is rendered in the browser. It is lighter than #[Lazy] because the component is mounted on the server — only the data-fetching action is deferred.

<div wire:init="loadStats">
    @if($loaded)
        <x-stats-grid :stats="$stats" />
    @else
        <x-skeleton-grid />
    @endif
</div>
public bool $loaded = false;
public array $stats = [];

public function loadStats(): void
{
    $this->stats = StatsService::summary();
    $this->loaded = true;
}

This pattern is ideal when you need the component's reactive properties available immediately (e.g., for a filter bar) but want to defer the expensive query.


Islands: Truly Independent Component Trees

Livewire v3 islands are components that opt out of the parent component's update cycle. They do not re-render when an ancestor updates, making them perfect for static-ish widgets embedded inside highly reactive pages.

Declare an island by extending Livewire\Component normally — the "island" behaviour comes from how you embed it:

{{-- Inside a parent Livewire blade view --}}
@livewire('dashboard.activity-feed', lazy: true)

Combining lazy: true on the embed with #[Lazy] on the class gives you both deferred loading and snapshot isolation in one shot.

Pitfall: Islands still share the same Livewire JS bundle. They are not micro-frontends. If you need full JS isolation, that is a different architecture entirely.


Choosing the Right Tool

| Scenario | Tool | |---|---| | Heavy query, no interactivity until loaded | #[Lazy] | | Component needs reactive state immediately | wire:init | | Widget must not re-render with parent | Island + lazy: true | | Multiple slow widgets, independent sources | #[Lazy(isolate: true)] |


Key Takeaways

  • #[Lazy] defers the entire mount; wire:init defers only a method call after mount.
  • Batch lazy requests by default; use isolate: true only for genuinely slow, independent components.
  • Islands prevent ancestor re-renders from cascading into a widget — combine with lazy loading for maximum effect.
  • Always provide a meaningful placeholder; a blank flash is worse UX than a skeleton.
  • Profile with browser DevTools network tab: look for the livewire/update XHR calls to confirm batching behaviour.

Found this useful?

Frequently Asked Questions

3 questions
Q01 What is the difference between `#[Lazy]` and `wire:init` in Livewire v3?
`#[Lazy]` skips the component mount entirely on the initial page render and sends a deferred HTTP request to fully mount it later. `wire:init` mounts the component normally but fires a specific action method in the browser immediately after render, deferring only that method's work rather than the whole mount lifecycle.
Q02 Does using `isolate: true` on every lazy component improve performance?
Not necessarily. Each isolated component fires its own HTTP request. For components that resolve quickly, batching them into a single request is cheaper. Reserve `isolate: true` for components with genuinely slow or independent data sources where one should not block the others.
Q03 Can I combine `#[Lazy]` with Livewire polling?
Yes. Once the deferred mount completes and the real component is in the DOM, `wire:poll` works normally. Just ensure your placeholder markup does not include `wire:poll`, or it will fire before the component is hydrated.

Continue reading

More Articles

View all