Saltar al contenido

Islands es sin duda la característica más importante de Livewire 4. Si aún no conoces las novedades de esta versión, te recomiendo revisar primero mi guía completa de Livewire 4. En este artículo vamos a profundizar en Islands con ejemplos prácticos y un análisis de cómo funciona internamente.


El Problema que Resuelve Islands

Imagina un dashboard típico con varias secciones: métricas de revenue, gastos, lista de actividades recientes y un reloj del servidor. En Livewire 3, cuando hacías click en "Refresh" en cualquier parte, todo el componente se re-renderizaba, ejecutando todas las queries, incluso las que no necesitabas actualizar.

La solución tradicional era crear componentes hijos separados. Pero eso implica:

  • Crear múltiples archivos

  • Gestionar props entre padre e hijos

  • Manejar eventos para comunicación

  • Más complejidad arquitectónica

Islands resuelve esto de forma elegante: regiones aisladas dentro del mismo componente que se actualizan de forma independiente.


Sintaxis Básica

Un island se define con la directiva @island:

<?php

use Livewire\Attributes\Computed;
use Livewire\Component;

new class extends Component {
    #[Computed]
    public function revenue()
    {
        // Query costosa que tarda 1 segundo
        sleep(1);
        return Revenue::yearToDate();
    }

    #[Computed]
    public function expenses()
    {
        // Otra query costosa
        sleep(1);
        return Expense::yearToDate();
    }
};
?>

<div>
    @island
        <div>
            Revenue: {{ $this->revenue }}
            <button wire:click="$refresh">Refresh Revenue</button>
        </div>
    @endisland

    @island
        <div>
            Expenses: {{ $this->expenses }}
            <button wire:click="$refresh">Refresh Expenses</button>
        </div>
    @endisland
</div>

Cuando haces click en "Refresh Revenue", solo se ejecuta el computed revenue(). El computed expenses() ni se toca. Cada island es completamente independiente.

Nota: $refresh es un método mágico de Livewire que fuerza el re-render. No necesitas definirlo en tu clase. Cuando se usa dentro de un island, solo re-renderiza ese island específico.


Cómo Funciona Internamente

Analicé las requests en DevTools y esto es lo que ocurre cuando actualizas un island.

El Request

Cuando haces click en un botón dentro de un island, Livewire envía un payload como este:

{
  calls: [{
    method: "$refresh",
    params: [],
    metadata: {
      island: {
        name: "revenue",
        mode: "morph"
      }
    }
  }],
  snapshot: "{...}"
}

La clave está en metadata.island. Le dice al servidor exactamente qué island debe renderizarse.

El Snapshot

El snapshot incluye la lista de todos los islands registrados en el componente:

{
  data: { ... },
  memo: {
    islands: [
      { name: "revenue", token: "e46fe94d-1" },
      { name: "expenses", token: "e46fe94d-2" },
      { name: "activities", token: "e46fe94d-3" }
    ]
  }
}

Cada island tiene un token único que Livewire usa para identificarlo en el DOM.

El Servidor

Cuando Laravel procesa la request:

  1. Ejecuta la acción ($refresh)

  2. Renderiza solo el island solicitado

  3. Los computed properties fuera del island no se ejecutan

La Respuesta

El servidor devuelve únicamente el HTML del island, y Livewire hace un morph parcial en el frontend, actualizando solo esa región del DOM.


Named Islands

Puedes nombrar islands para controlarlos desde cualquier parte del componente:

@island(name: 'stats')
    <div>
        Revenue: {{ $this->revenue }}
    </div>
@endisland

<!-- Botón FUERA del island que lo actualiza -->
<button wire:click="$refresh" wire:island="stats">
    Actualizar estadísticas
</button>

El atributo wire:island="stats" indica que la acción debe ejecutarse en el contexto de ese island específico.

Islands con el mismo nombre

Si tienes múltiples islands con el mismo nombre, se actualizan juntos:

@island(name: 'revenue')
    <div class="sidebar">Revenue: {{ $this->revenue }}</div>
@endisland

@island(name: 'revenue')
    <div class="header">Revenue: {{ $this->revenue }}</div>
@endisland

<button wire:click="$refresh" wire:island="revenue">
    Refresh (actualiza ambos)
</button>

Útil cuando necesitas mostrar el mismo dato en diferentes partes de la UI.


Modos de Renderizado

Los islands soportan tres modos de renderizado que controlan cómo se actualiza el contenido.

Mode: morph (default)

Reemplaza el contenido del island haciendo un diff inteligente del DOM:

<button wire:click="$refresh" wire:island="feed">
    Refresh
</button>

En el request verás mode: "morph".

Mode: append

Agrega el nuevo contenido al final sin borrar el existente:

@island(name: 'feed')
    @foreach ($this->activities as $activity)
        <div>{{ $activity->description }}</div>
    @endforeach
@endisland

<button wire:click="loadMore" wire:island.append="feed">
    Cargar más
</button>

En el request verás mode: "append". Perfecto para infinite scroll o paginación.

Mode: prepend

Agrega el nuevo contenido al inicio:

<button wire:click="loadNew" wire:island.prepend="notifications">
    Cargar nuevas notificaciones
</button>

En el request verás mode: "prepend". Ideal para feeds en tiempo real donde lo nuevo aparece arriba.


Lazy Loading

Los islands pueden cargarse de forma diferida para no bloquear el render inicial de la página.

lazy: true

Carga el island cuando entra en el viewport (scroll):

@island(lazy: true)
    <div>{{ $this->expensiveData }}</div>
@endisland

Usa un Intersection Observer internamente.

defer: true

Carga inmediatamente después del page load, sin esperar al scroll:

@island(defer: true)
    <div>{{ $this->otherData }}</div>
@endisland

Placeholder personalizado

Muestra un skeleton o loading state mientras el island carga:

@island(lazy: true)
    @placeholder
        <div class="animate-pulse">
            <div class="h-32 bg-gray-200 rounded"></div>
        </div>
    @endplaceholder

    <div>{{ $this->revenue }}</div>
@endisland

El placeholder se muestra hasta que el island termina de cargar.


Polling Aislado

Puedes usar wire:poll dentro de un island y solo ese island se actualizará en cada intervalo:

@island(name: 'clock')
    <div wire:poll.3s>
        Hora del servidor: {{ $this->serverTime }}
    </div>
@endisland

El resto del componente permanece intacto. Antes de Islands, un wire:poll re-renderizaba todo el componente.


Skip Initial Render

El parámetro skip evita que el island se renderice inicialmente. Útil para contenido on-demand:

@island(skip: true)
    @placeholder
        <button wire:click="$refresh">
            Mostrar detalles
        </button>
    @endplaceholder

    <div>
        {{ $this->expensiveDetails }}
    </div>
@endisland

El island muestra el placeholder hasta que el usuario hace click.


Always Render con el Padre

Por defecto, cuando el componente padre se actualiza, los islands se saltan. Usa always: true para forzar que un island se actualice junto con el padre:

@island(always: true)
    <div>{{ $this->criticalData }}</div>
@endisland

Útil para datos que siempre deben estar sincronizados.


Islands Anidados

Los islands pueden anidarse. Cuando el island exterior se actualiza, los interiores se saltan:

@island(name: 'outer')
    <div>
        Datos externos: {{ $this->outerData }}

        @island(name: 'inner')
            <div>
                Datos internos: {{ $this->innerData }}
                <button wire:click="$refresh">Refresh inner</button>
            </div>
        @endisland

        <button wire:click="$refresh">Refresh outer</button>
    </div>
@endisland

"Refresh outer" actualiza solo el island exterior, dejando el interior intacto.


Limitaciones Importantes

Sin acceso a variables del template

Los islands no tienen acceso a variables @php definidas en el template padre:

@php
    $localVar = 'valor';
@endphp

@island
    {{-- ERROR: $localVar no está disponible --}}
    {{ $localVar }}

    {{-- OK: propiedades del componente sí funcionan --}}
    {{ $this->revenue }}
@endisland

No funcionan dentro de loops o condicionales

Los islands no pueden usarse dentro de @foreach, @if u otras estructuras de control:

{{-- NO funciona --}}
@foreach ($items as $item)
    @island
        {{ $item->name }}
    @endisland
@endforeach

{{-- Solución: pon el loop DENTRO del island --}}
@island
    @foreach ($this->items as $item)
        {{ $item->name }}
    @endforeach
@endisland

State divergente

Si múltiples islands hacen requests en paralelo y modifican el mismo estado, puede haber divergencia. El último response en llegar "gana".


Islands vs Componentes Hijos

¿Cuándo usar cada uno?

Usa Islands cuando:

  • Solo necesitas optimizar rendimiento

  • El código es específico de ese componente

  • No necesitas reutilizar la lógica en otro lugar

  • Quieres lazy loading sin complejidad

Usa Componentes Hijos cuando:

  • Necesitas reutilizar en múltiples lugares

  • El componente tiene su propio lifecycle (mount, updated, etc.)

  • Tiene estado y lógica complejos que merecen encapsulación

  • Estás construyendo una librería de componentes

Mi recomendación: empieza con islands por simplicidad. Si después necesitas reutilizar o la lógica crece, refactoriza a un componente hijo.


Ejemplo Práctico: Dashboard

Aquí tienes un ejemplo completo de un dashboard que demuestra todas las características:

<?php

use App\Models\Activity;
use App\Models\Metric;
use Livewire\Attributes\Computed;
use Livewire\Component;

new class extends Component {
    public int $page = 1;

    #[Computed]
    public function revenue(): string
    {
        return number_format(Metric::where('type', 'revenue')->sum('value'), 2);
    }

    #[Computed]
    public function activities()
    {
        return Activity::latest()->forPage($this->page, 5)->get();
    }

    #[Computed]
    public function serverTime(): string
    {
        return now()->format('H:i:s');
    }

    public function loadMore(): void
    {
        $this->page++;
    }
};
?>

<div>
    {{-- Island básico con refresh independiente --}}
    @island(name: 'revenue')
        <div class="card">
            <h2>Revenue</h2>
            <p>${{ $this->revenue }}</p>
            <button wire:click="$refresh">Refresh</button>
        </div>
    @endisland

    {{-- Island con lazy loading --}}
    @island(name: 'activities', lazy: true)
        @placeholder
            <div class="animate-pulse">Cargando actividades...</div>
        @endplaceholder

        <div class="card">
            <h2>Actividades Recientes</h2>
            @foreach ($this->activities as $activity)
                <div wire:key="activity-{{ $activity->id }}">
                    {{ $activity->description }}
                </div>
            @endforeach
        </div>
    @endisland

    {{-- Botón con append mode --}}
    <button wire:click="loadMore" wire:island.append="activities">
        Cargar más
    </button>

    {{-- Island con polling --}}
    @island(name: 'clock')
        <div wire:poll.3s>
            Hora: {{ $this->serverTime }}
        </div>
    @endisland

    {{-- Control externo de islands --}}
    <div class="controls">
        <button wire:click="$refresh" wire:island="revenue">
            Refresh Revenue
        </button>
        <button wire:click="$refresh" wire:island="clock">
            Refresh Clock
        </button>
        <button wire:click="$refresh">
            Refresh TODO (sin island)
        </button>
    </div>
</div>

Referencia Rápida

Directiva @island

@island(
    ?string $name = null,
    bool $lazy = false,
    bool $defer = false,
    bool $skip = false,
    bool $always = false,
)
    <!-- Contenido -->
@endisland

Parámetros

Parámetro

Tipo

Default

Descripción

name

?string

null

Nombre para targetear con wire:island

lazy

bool

false

Carga cuando entra en viewport

defer

bool

false

Carga después del page load

skip

bool

false

No renderiza inicialmente

always

bool

false

Se actualiza con el padre

Atributo wire:island

Sintaxis

Mode

Comportamiento

wire:island="name"

morph

Reemplaza contenido

wire:island.append="name"

append

Agrega al final

wire:island.prepend="name"

prepend

Agrega al inicio


Conclusión

Islands es una solución elegante para un problema real. Obtienes el rendimiento de componentes pequeños sin la complejidad de crearlos y mantenerlos.

Lo más potente es que puedes empezar simple (un @island básico) e ir añadiendo características según las necesites: nombres para control externo, lazy loading para rendimiento, append/prepend para paginación.

Si vienes de Livewire 3 y tenías componentes con queries costosas que se re-ejecutaban innecesariamente, Islands es exactamente lo que necesitabas.

school Curso completo

Curso Laravel 12
Completo 2026

El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.

access_time 8 horas de contenido
layers 4 tecnologías en 1
update 100% actualizado
code Proyectos prácticos
Ver Curso Laravel 12 arrow_forward

star Incluido en cualquier suscripción

Rutas de aprendizaje