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

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

1 min read Mohamed Said Mohamed Said

Why Islands Matter in Livewire v3

A full-page Livewire component tree hydrates every component on every request. For dashboards with a dozen widgets, that means twelve round-trips of PHP work before the user sees anything interactive. Livewire v3 solves this with lazy components — components that render a placeholder on the initial page load and hydrate asynchronously over a subsequent HTTP request.

This is the "islands" mental model: static HTML is the sea, and interactive Livewire components are islands that boot independently.


The lazy Attribute

Making a component lazy is a single attribute on the <livewire: tag:

{{-- resources/views/dashboard.blade.php --}}
<livewire:revenue-chart lazy />
<livewire:recent-orders lazy />
<livewire:support-queue lazy />

Each component fires a separate GET request after the page loads, hydrates in isolation, and swaps its placeholder. The initial HTML response is tiny and fast.

Providing a Placeholder

Without a placeholder, Livewire renders nothing until the component boots. Define one inside the component class:

<?php

namespace App\Livewire;

use Livewire\Component;

class RevenueChart extends Component
{
    public function placeholder(): string
    {
        return <<<'HTML'
        <div class="animate-pulse h-48 bg-gray-100 rounded-xl"></div>
        HTML;
    }

    public function render()
    {
        return view('livewire.revenue-chart', [
            'data' => $this->loadChartData(),
        ]);
    }

    private function loadChartData(): array
    {
        // expensive query — safe to run lazily
        return RevenueAggregator::monthly();
    }
}

The placeholder is rendered server-side on the first pass, so it participates in SSR and is immediately visible — no layout shift.


Passing Data Into Lazy Components

Props are serialised into the lazy request automatically. Pass them as normal:

<livewire:order-table :team-id="$team->id" lazy />
class OrderTable extends Component
{
    public int $teamId;

    public function mount(int $teamId): void
    {
        $this->teamId = $teamId;
    }
}

Security note: Props are included in the signed snapshot. They are tamper-evident but not secret. Never pass sensitive tokens as props.


Deferred Loading vs. Lazy Components

These two terms are often conflated. In Livewire v3:

  • Lazy component — the entire component boots on a deferred request. The component class is not instantiated on the initial render.
  • Deferred property / action — a property or method call that is batched and sent after the initial render, within an already-hydrated component.

For a widget that is always on screen but has one slow sub-query, a deferred property is cleaner than making the whole component lazy:

use Livewire\Attributes\Lazy;

class SupportQueue extends Component
{
    #[\Livewire\Attributes\Computed(persist: true)]
    public function tickets()
    {
        return Ticket::open()->with('assignee')->latest()->get();
    }
}

For widgets that may never be scrolled into view (below the fold), full lazy loading wins.


Conditional Lazy Loading

You can make laziness dynamic by overriding the lazy property at runtime:

<livewire:analytics-breakdown :lazy="! $user->prefersEagerLoad()" />

This is useful for power users who have opted into richer, faster dashboards.


Testing Lazy Components with Pest

Livewire's test helpers respect laziness. Use ->assertSeeHtml() on the placeholder, then call ->dispatch('livewire:load') to trigger hydration:

use Livewire\Livewire;

it('renders a placeholder before hydration', function () {
    Livewire::test(RevenueChart::class)
        ->assertSeeHtml('animate-pulse');
});

it('renders chart data after hydration', function () {
    Livewire::test(RevenueChart::class)
        ->call('$refresh')
        ->assertSee('Monthly Revenue');
});

Key Takeaways

  • Add lazy to any component whose data is expensive and not needed for the initial paint.
  • Always define a placeholder() method to avoid layout shift and give users immediate feedback.
  • Props passed to lazy components are serialised and signed — treat them as public, not secret.
  • Distinguish between lazy components (deferred boot) and deferred properties (deferred data within a booted component).
  • Test placeholders and hydrated states separately; Livewire's test suite handles both cleanly.

Found this useful?

Frequently Asked Questions

3 questions
Q01 Does making a component lazy affect SEO or server-side rendering?
The placeholder is rendered server-side and included in the initial HTML, so crawlers see it. The actual component content loads via a subsequent XHR request and is not in the initial HTML. If the content must be indexed, avoid lazy loading for that component or use SSR-friendly alternatives.
Q02 Can lazy components still receive events dispatched from parent components?
Yes, but only after the lazy component has hydrated. Events dispatched before hydration are queued by Livewire's JS layer and replayed once the component boots. Be aware of timing if you dispatch events immediately on page load.
Q03 Is there a performance cost to having many lazy components on one page?
Each lazy component fires one additional HTTP request. Ten lazy components mean ten extra requests. Use browser DevTools to confirm they load in parallel. For very high counts, consider grouping related data into a single lazy component to reduce request overhead.

Continue reading

More Articles

View all