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

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

4 min read Mohamed Said Mohamed Said

Three Async Strategies, One Common Mistake

Most teams discover Livewire v3's async rendering features by accident — they add lazy to a slow component and call it a day. That works, but it leaves two more powerful tools on the table and creates subtle bugs when the wrong strategy is applied. Let's be precise about what each one does.


lazy: Defer the Initial Mount

The lazy attribute tells Livewire to skip the component's mount() and first render on the server-side page load. Instead, the browser receives a placeholder, then fires a subsequent request to fully hydrate the component.

// In your Blade view
<livewire:revenue-chart lazy />
// RevenueChart.php
class RevenueChart extends Component
{
    public function placeholder(): View
    {
        return view('livewire.placeholders.chart-skeleton');
    }

    public function render(): View
    {
        return view('livewire.revenue-chart', [
            'data' => RevenueQuery::run(),
        ]);
    }
}

The placeholder is rendered inline during the initial page response — it must be pure Blade, no Livewire state. The real component arrives via a single background request after DOMContentLoaded.

When to use it: Components whose mount() hits the database or an external API and whose absence doesn't break the page layout.

Pitfall: Every lazy component on a page fires its own HTTP round-trip. Ten lazy components = ten extra requests. Livewire does not batch them.


Islands: Opt-Out of Full-Page Re-renders

Livewire v3 introduced the concept of islands — components that are isolated from their parent's re-render cycle. When a parent component updates, island children do not re-render unless they themselves receive a direct interaction or explicit $refresh.

// Parent blade
<livewire:order-table />
<livewire:sidebar-stats :wire:key="'sidebar'" /> {{-- island by isolation --}}

True island behaviour is achieved by ensuring the child has no reactive properties bound to the parent. Livewire's morphing algorithm will leave the child's DOM subtree untouched if its snapshot hasn't changed.

For explicit isolation, wrap in a component that accepts no @entangle or wire:model bindings from the parent:

class SidebarStats extends Component
{
    // No public properties fed from parent — pure island
    public function render(): View
    {
        return view('livewire.sidebar-stats', [
            'totals' => Cache::remember('sidebar-totals', 60, fn () => Totals::compute()),
        ]);
    }
}

When to use it: Expensive widgets (charts, aggregates) that sit alongside frequently-updating lists. Prevents unnecessary re-hydration.


Deferred Loading with wire:init

wire:init fires a component method immediately after the component is rendered in the browser — before any user interaction. It's the right tool when you want the component shell to appear instantly and populate itself client-side after mount.

class ActivityFeed extends Component
{
    public array $items = [];

    public function loadItems(): void
    {
        $this->items = Activity::latest()->limit(20)->get()->toArray();
    }

    public function render(): View
    {
        return view('livewire.activity-feed');
    }
}
<div wire:init="loadItems">
    @forelse($items as $item)
        <x-activity-item :item="$item" />
    @empty
        <x-skeleton lines="5" />
    @endforelse
</div>

Unlike lazy, wire:init still mounts the component fully on the server — it only defers the data-heavy method call. This means the component's initial state (empty $items) is part of the first response, keeping the skeleton visible without a flash of unstyled content.

When to use it: Feeds, notifications, or any list where the empty shell is meaningful and you want a single, predictable loading state.


Choosing the Right Tool

| Strategy | Server mount? | Extra request? | Best for | |---|---|---|---| | lazy | Deferred | Yes (per component) | Slow mounts, below-fold widgets | | Island isolation | Full | No | Preventing parent re-render bleed | | wire:init | Full (empty state) | Yes (one method call) | Progressive data population |


Key Takeaways

  • lazy defers mount() entirely — every lazy component costs one extra HTTP request.
  • Islands are architectural: keep child components free of parent reactive bindings to prevent unnecessary morphing.
  • wire:init mounts the shell immediately and fetches data after; prefer it when the empty state is meaningful UI.
  • Combine strategies: a lazy outer component whose inner feed uses wire:init gives you two-phase progressive loading with a single extra request.
  • Always define a placeholder() method for lazy components — the default empty <div> causes layout shift.

Found this useful?

Frequently Asked Questions

3 questions
Q01 Can I use `lazy` and `wire:init` on the same component?
Yes, but understand the sequence: `lazy` defers the entire mount until after page load, then `wire:init` fires after that deferred render completes. You get two sequential async steps, which can feel slow. Only combine them if the mount itself is expensive AND the data fetch inside the method is also expensive and separate.
Q02 Does Livewire batch lazy component requests to reduce HTTP overhead?
No. As of Livewire v3, each lazy component fires its own independent POST request to /livewire/update. If you have many lazy components, consider consolidating them into a single parent component that loads all data in one mount, or use `wire:init` instead to keep the request count predictable.
Q03 How do islands interact with Livewire's morphing algorithm?
Livewire morphs the DOM by comparing snapshots. If a child component's snapshot is unchanged after a parent update, Livewire skips re-rendering that subtree. Keeping child components free of parent-bound reactive properties ensures their snapshots stay stable, effectively making them islands without any special directive.

Continue reading

More Articles

View all