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
lazydefersmount()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:initmounts the shell immediately and fetches data after; prefer it when the empty state is meaningful UI.- Combine strategies: a
lazyouter component whose inner feed useswire:initgives you two-phase progressive loading with a single extra request. - Always define a
placeholder()method forlazycomponents — the default empty<div>causes layout shift.