Islands in Laravel 13 Livewire 4: Complete Guide and Use Cases

Video thumbnail

You have surely heard me say that Livewire can be somewhat abstract, and the Islands concept is the perfect example. It is a new and powerful feature, but its usefulness depends entirely on how you approach your development.

To explain it, I have prepared a practical example supported by AI, since simulating a "heavy" data load requires a specific scenario that we will see next.

What is an Island?

In simple terms, an island is a section of your component that loads in an isolated or parallel manner.

  • The problem: Normally, if a component has to perform a heavy query, the entire page (or the entire component) waits for that query to finish.
  • The solution: By wrapping that part in an "island", we allow the rest of the component to load instantly, while the heavy part is processed independently.

Islands are, basically, a way to load heavy parts of a component in an isolated or parallel manner.

If you have a very large component, you can divide it into small parts (like subcomponents within a component), and make those parts execute tasks independently.

Difference between Island and Subcomponent

Here comes the philosophical part. You might ask yourself: "Why not simply create a child component?".
The difference is that, even if you use child components, if they render within the initial request, they can slow down the main thread. Islands allow those "heavy tasks" (like computed properties that query thousands of records) not to block the initial HTML load.

Loading Strategies (Lazy, Defer and more)

Slow processes have been simulated using the sleep() function. This allows us to see how the different island directives work:

  • lazy: true: The island does not load until it is visible in the browser (ideal for elements that require scrolling).
  • defer: true: Loads the island immediately after the main component is ready, without blocking the initial rendering.
  • Default behavior: If you don't use any option, the island behaves as part of the component.
  • wire:island (with name): Allows identifying a specific island to refresh it individually from anywhere, even with buttons outside the island itself.

resources\views\pages\dashboard\island\⚡index.blade.php

<flux:card>
    <flux:heading level="2">1. Island Básico</flux:heading>
    <flux:text>Se actualiza independientemente sin re-renderizar toda la página {{ time() }}.</flux:text>
    {{-- si comentas es island se recarga TODO el componente junt con el time --}}
    @island
        <div class="p-4 bg-zinc-50 dark:bg-zinc-800 rounded-lg">
            <flux:heading level="3">Total de Posts: {{ $this->totalPosts }}</flux:heading>
            <flux:text class="mt-2">Este island usa un Computed property que se recalcula solo cuando el island se
                actualiza.</flux:text>
            <flux:button size="sm" wire:click="$refresh" class="mt-2">
                Actualizar
            </flux:button>
            <div class="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
                <p class="text-red">El tiempo {{ time() }} sera mayor y solo se calcula al momento de que LA ISLA
                    ES VISIBLE</p>
                <flux:heading level="3">Posts Recientes</flux:heading>
                <ul class="mt-2 space-y-2">
                    @foreach ($this->recentPosts as $post)
                        <li class="flex items-center gap-2">
                            <flux:badge>{{ $post->id }}</flux:badge>
                            {{ $post->title }}
                        </li>
                    @endforeach
                </ul>
            </div>
        </div>
    @endisland
</flux:card>
<div class="h-96"></div>
<div class="h-96"></div>
<div class="h-96"></div>
<flux:card>
    <flux:heading level="2">2. Island con Lista (Lazy - carga al hacer scroll)</flux:heading>
    <flux:text>Este island carga solo cuando se hace scroll y es visible en el viewport.</flux:text>
    <p class="text-red">El tiempo {{ time() }} sera el mismo que el de <strong>1. Island Básico</strong> al
        cargar la página</p>
    @island(lazy: true)
        <div class="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
            <p class="text-red">El tiempo {{ time() }} sera mayor y solo se calcula al momento de que LA ISLA ES
                VISIBLE</p>
            <flux:heading level="3">Posts Recientes (SOLO se GENERA EL LISTADO AL CARGAR LA PAGINA) </flux:heading>
            <ul class="mt-2 space-y-2">
                @foreach ($this->recentPosts as $post)
                    <li class="flex items-center gap-2">
                        <flux:badge>{{ $post->id }}</flux:badge>
                        {{ $post->title }}
                    </li>
                @endforeach
            </ul>
        </div>
    @endisland
</flux:card>

Computed properties and deferred loading

Before entering the example, remember that computed properties are usually used for heavy or repetitive operations.

They are accessed with this, and they allow avoiding unnecessary calculations. In this case, we use them to simulate heavy loads along with sleep.

The idea is that the main component loads fast, and then certain parts update on demand.

This is the logical part:

resources\views\pages\dashboard\island\⚡index.blade.php

<flux:card>
    <flux:heading level="2">6. Island Hija Custom Placeholder</flux:heading>
    <flux:text>Personaliza el estado de carga con @placeholder.</flux:text>
    {{-- @livewire('dashboard.island-lazy') --}}
    @island(lazy: true)
        @placeholder
            
            {{-- @livewire('dashboard.island-lazy')  --}}
            {{-- NO SIRVE, no puedes cargar componentes en islas, carga pero bloquea 3s --}}
            <div class="animate-pulse h-24 flex justify-center items-center bg-gray-500 border rounded-2xl">Cargando comentarios...</div>
        @endplaceholder
        
            @foreach ($this->heavyPosts() as $post)
                <li class="flex items-center gap-2">
                    <flux:badge>{{ $post->id }}</flux:badge>
                    {{ $post->title }}
                </li>
            @endforeach
            <flux:button wire:click="$refresh">
                Refresh with placeholder
            </flux:button>
    @endisland
    <div class="h-64"></div>
</flux:card>

1. Example with scroll (lazy)

In this case:

  • The main component loads first
  • The island does not load until you scroll

resources\views\pages\dashboard\island\⚡index.blade.php

<flux:card>
    <flux:heading level="2">1. Island Básico</flux:heading>
    <flux:text>Se actualiza independientemente sin re-renderizar toda la página {{ time() }}.</flux:text>
    {{-- si comentas es island se recarga TODO el componente junt con el time --}}
    @island
        <div class="p-4 bg-zinc-50 dark:bg-zinc-800 rounded-lg">
            <flux:heading level="3">Total de Posts: {{ $this->totalPosts }}</flux:heading>
            <flux:text class="mt-2">Este island usa un Computed property que se recalcula solo cuando el island se
                actualiza.</flux:text>
            <flux:button size="sm" wire:click="$refresh" class="mt-2">
                Actualizar
            </flux:button>
            <div class="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
                <p class="text-red">El tiempo {{ time() }} sera mayor y solo se calcula al momento de que LA ISLA
                    ES VISIBLE</p>
                <flux:heading level="3">Posts Recientes</flux:heading>
                <ul class="mt-2 space-y-2">
                    @foreach ($this->recentPosts as $post)
                        <li class="flex items-center gap-2">
                            <flux:badge>{{ $post->id }}</flux:badge>
                            {{ $post->title }}
                        </li>
                    @endforeach
                </ul>
            </div>
        </div>
    @endisland
</flux:card>

This can be noticed with time: if you take 5 seconds to scroll, the island will show a longer time.

2. Named Islands

You can also assign names to islands.

This allows doing things like:

  • Reloading a specific island from outside
  • Controlling exactly which part of the component updates

For example:

An external button can reload only a specific island
The others remain intact

resources\views\pages\dashboard\island\⚡index.blade.php

<flux:card>
    <flux:heading level="2">4. Named Island con wire:island</flux:heading>
    <flux:text>Los named islands pueden ser actualizados desde cualquier lugar con wire:island. {{ time() }}</flux:text>
    @island()
        <div class="p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
            <flux:heading level="3">Estadísticas (Isla sin nombre :/)</flux:heading>
            <flux:text>Posts publicados: {{ $this->totalPosts }}</flux:text>
            <flux:button wire:click="$refresh">
                U
            </flux:button>
        </div>
    @endisland
    @island(name: 'stats')
        <div class="p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg my-4">
            <flux:heading level="3">Estadísticas (Isla stats)</flux:heading>
            <flux:text>Posts publicados: {{ $this->totalPosts }}</flux:text>
            <flux:button wire:click="$refresh">
                U
            </flux:button>
        </div>
    @endisland
    @island(name: 'stats2')
        <div class="p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
            <flux:heading level="3">Estadísticas (Isla stats2)</flux:heading>
            <flux:text>Posts publicados: {{ $this->totalPosts }}</flux:text>
            <flux:button wire:click="$refresh">
                U
            </flux:button>
        </div>
    @endisland
    <div class="mt-4 flex flex-col gap-2">
        <flux:button wire:click="$refresh" wire:island="stats">
            Actualizar Isla con nombre stats (Estoy fuera de la isla)
        </flux:button>
        <flux:button wire:click="$refresh">
            Actualizar Stats (NO actualiza a nadie, NO esta dentro de ningula isla)
        </flux:button>
    </div>
</flux:card>

3. Example with Placeholder: The user experience

We can define a Placeholder (a "loading...") while the island processes the information. This is vital so that the user does not see a blank gap. In the example you will see a "Loading comments..." message that disappears once the data is ready.

resources\views\pages\dashboard\island\⚡index.blade.php

<?php
use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\Post;
new class extends Component {
    public $loadHeavy = false;
    #[Computed]
    public function totalPosts()
    {
        // return Post::count();
        return rand(4, 100);
    }
    #[Computed]
    public function recentPosts()
    {
        // if($this->loadHeavy){
        //     sleep(3);
        // }
        return Post::inRandomOrder()->take(rand(4, 20))->get();
    }
    
    public function heavyPosts()
    {
        sleep(3);
        return Post::inRandomOrder()->take(rand(4, 20))->get();
    }
    // En tu componente Livewire/Volt
    // public function with()
    // {
    //     sleep(3); // Esto mantendrá el placeholder visible por 3 segundos
    //     return [];
    // }
};
?>

Current limitations

It is important to mention that, for now, islands have restrictions:

  • They do not accept Livewire components inside them (only HTML/Blade).
  • They do not work inside @foreach loops or @if conditionals directly.
  • They cannot reference variables declared in the main component dynamically.

Important: context and limitations

There are several important points to keep in mind:

  • You cannot use islands inside foreach
  • They do not work well with dynamic conditionals
  • Everything must be contained within the island

This reinforces the idea that they are an isolated rendering unit.

For me, islands are a tool for very specific cases where performance is critical. Although the definition of "component" is usually something small and modular, islands seem to invite us to create larger components with internal asynchronous sections. It is an inverse approach, but very interesting for optimizing the main thread of our application.

I invite you to download the code from the repository and play with the loading times to see the difference live!

Explore the concept of islands in Livewire. Optimize Laravel components using defer and parallel loading. High-performance interactive apps.

I agree to receive announcements of interest about this Blog.

Andrés Cruz

ES En español
<script> window.addEventListener('scroll', function() { if (window.scriptsLoaded) return; loadThirdPartyScripts(); }, { once: true }); window.addEventListener('mousemove', function() { if (window.scriptsLoaded) return; loadThirdPartyScripts(); }, { once: true }); window.addEventListener('touchstart', function() { if (window.scriptsLoaded) return; loadThirdPartyScripts(); }, { once: true }); // Fallback if no interaction window.addEventListener('load', function() { setTimeout(function() { if (!window.scriptsLoaded) loadThirdPartyScripts(); }, 8000); }); function loadThirdPartyScripts() { if (window.scriptsLoaded) return; window.scriptsLoaded = true; console.log('Loading third party scripts...'); // Google Analytics var gtagScript = document.createElement('script'); gtagScript.src = 'https://www.googletagmanager.com/gtag/js?id=G-F22688T9RL'; gtagScript.async = true; document.head.appendChild(gtagScript); gtagScript.onload = function() { window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'G-F22688T9RL'); }; // Google ADS const adScript = document.createElement('script'); adScript.src = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"; adScript.setAttribute('data-ad-client', 'ca-pub-5280469223132298'); adScript.async = true; document.head.appendChild(adScript); // Facebook Pixel (function(f, b, e, v, n, t, s) { if (f.fbq) return; n = f.fbq = function() { n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments) }; if (!f._fbq) f._fbq = n; n.push = n; n.loaded = !0; n.version = '2.0'; n.queue = []; t = b.createElement(e); t.async = !0; t.src = v; s = b.getElementsByTagName(e)[0]; s.parentNode.insertBefore(t, s); })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js'); fbq('init', '1643487712945352'); fbq('track', 'PageView'); } </script> <noscript> <img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1643487712945352&ev=PageView&noscript=1" /> </noscript>