5 min read

Livewire 4: Islands, Lazy Loading, Optimistic UI, and What Changed

Livewire 4 ships single-file components, islands, lazy/defer bundling, wire:sort, optimistic UI, and seven security hardening fixes. Here's the full breakdown.

Featured image for "Livewire 4: Islands, Lazy Loading, Optimistic UI, and What Changed"

Livewire 4 landed in January 2026 and has been iterating steadily since. The v4.2.0 release in February added Laravel 13 support and seven security hardening improvements. v4.3.0 followed in May with Alpine v3.15.10 compatibility, island token stability for zero-downtime deployments, and a round of wire:sort fixes. If you have not upgraded yet, or you have been following the changelogs from a distance, here is what the v4 line actually changed and why it matters.

Single File Components

Livewire 4 defaults to Single File Components (SFCs). When you run php artisan make:livewire counter, instead of getting two files, you get one: resources/views/components/⚡counter.blade.php. The prefix is intentional — it visually distinguishes Livewire components from plain Blade components in the same directory.

<?php

use Livewire\Component;

new class extends Component
{
    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }
};
?>

<div>
    <button wire:click="increment">Count: {{ $count }}</button>
</div>

If you prefer the old class + blade file split, you can either pass --class to the make command, or change the default in livewire.php after running php artisan livewire:config. Nothing from Livewire 3 breaks here; the class-based approach is still fully supported. The SFC default is just a better starting point for most components.

Islands

Islands solve a specific pain point: components that are mostly fast, with one slow part dragging down the whole render. Instead of breaking the component into nested sub-components and managing event communication between them, you wrap the expensive section in @island and @endisland directives.

<?php

use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\Revenue;

new class extends Component {
    #[Computed]
    public function yearToDate()
    {
        // Expensive query -- runs only inside the island
        return Revenue::yearToDate()->sum('amount');
    }
};
?>

<div>
    @island
        <div>
            YTD Revenue: ${{ number_format($this->yearToDate) }}
            <button wire:click="$refresh">Refresh</button>
        </div>
    @endisland

    <p>Other fast content here...</p>
</div>

At compile time, Livewire extracts the island into its own blade file. Actions triggered inside the island only re-render the island, not the whole component. Computed properties are important here because they run only when accessed — if you use #[Computed] on the expensive method, nothing runs unless the island actually needs it.

Lazy, Defer, and Bundle

Lazy loading is not new to Livewire, but v4 cleaned up the API and added two new modes alongside it.

lazy delays component initialization until it enters the viewport:

<livewire:revenue lazy />

You can add a placeholder that shows while the component loads:

<div>
    @placeholder
        <div class="animate-pulse h-8 bg-gray-200 rounded"></div>
    @endplaceholder

    Revenue: ${{ $amount }}
</div>

defer is similar but loads after the initial page render regardless of viewport position. bundle groups multiple lazy-loaded components into a single request, which matters when you have a dashboard with eight or ten lazy components — without it, each one fires its own HTTP request.

<livewire:revenue lazy.bundle />
<livewire:transactions lazy.bundle />
<livewire:user-stats lazy.bundle />

All three bundle into one round trip instead of three.

Optimistic UI

Livewire 4 ships two directives specifically for optimistic updates: wire:show and wire:text. Both update the DOM immediately on the client without waiting for a server response.

wire:show replaces most @if conditionals:

<button wire:click="toggleDetails">Show Details</button>

<div wire:show="showDetails" wire:cloak>
    Here are the details...
</div>

The wire:cloak attribute prevents the element from flashing visible before Livewire initializes.

wire:text handles live text updates:

<button wire:click="addToCart">Add to Cart</button>
<span wire:text="cartCount">{{ $cartCount }}</span>

Instead of the whole component re-rendering, just the text node updates instantly.

For loading states, v4 dropped wire:loading in favor of a data-loading HTML attribute that Livewire adds automatically during any network request. You can target it directly in Tailwind:

<button wire:click="save" class="data-loading:opacity-50 data-loading:cursor-wait">
    Save
</button>

wire:sort

wire:sort brings drag-and-drop reordering without a JavaScript library. Add it to the list container, wire:sort:item to each item, and handle the sort callback server-side:

public function reorder($itemId, $position): void
{
    $item = $this->list->items()->findOrFail($itemId);
    // update position in database
}
<ul wire:sort="reorder">
    @foreach ($list->items as $item)
        <li wire:sort:item="{{ $item->id }}">
            {{ $item->title }}
        </li>
    @endforeach
</ul>

The v4.3.0 release fixed a position calculation bug that was causing incorrect order values when items were dragged to the end of the list.

Security Hardening in v4.2.0

This is worth paying attention to if you are running Livewire in a production application. v4.2.0 shipped seven targeted security improvements:

Lifecycle method protection. Lifecycle methods (mount, boot, hydrate, etc.) can no longer be invoked through frontend requests. Previously, a crafted request could call them directly.

Payload schema validation. Incoming payloads are now validated against a schema before processing. Invalid payloads get tiered error responses rather than passing through.

Timing-safe checksum comparison. Checksum verification now uses hash_equals() instead of ===, which prevents timing attacks from measuring response time differences to guess valid checksums.

Request surface tightening. Update requests now require the X-Livewire header and a JSON content type. This blocks a class of CSRF-adjacent attacks that relied on sending non-Livewire requests to Livewire update endpoints.

CollectionSynth type validation. Type checking was added to prevent arbitrary class instantiation through the collection synth, which could otherwise be abused to instantiate unintended classes during hydration.

If you are running custom Livewire update routes, the new web middleware enforcement and the stricter header requirements may affect your setup. Review PR #9965 and PR #9971 before upgrading.

Upgrading from Livewire 3

Most applications can upgrade with a composer bump and a cache clear:

composer require livewire/livewire:^4.0
php artisan optimize:clear

The breaking changes are narrow: custom update routes, some advanced lifecycle hook patterns, and applications relying on direct model property binding without explicit casting. The official upgrade guide covers each breaking change with before/after examples.

The v4 line is now the current stable release. Livewire 3 will continue receiving security patches for a transition period, but new features are v4-only going forward.

Sources