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:
$refreshes 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:
Ejecuta la acción (
$refresh)Renderiza solo el island solicitado
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>
@endislandUsa un Intersection Observer internamente.
defer: true
Carga inmediatamente después del page load, sin esperar al scroll:
@island(defer: true)
<div>{{ $this->otherData }}</div>
@endislandPlaceholder 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>
@endislandEl 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>
@endislandEl 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>
@endislandEl 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 }}
@endislandNo 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
@endislandState 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 -->
@endislandParámetros
Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
|
|
| Nombre para targetear con |
|
|
| Carga cuando entra en viewport |
|
|
| Carga después del page load |
|
|
| No renderiza inicialmente |
|
|
| Se actualiza con el padre |
Atributo wire:island
Sintaxis | Mode | Comportamiento |
|---|---|---|
|
| Reemplaza contenido |
|
| Agrega al final |
|
| 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.
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.
star Incluido en cualquier suscripción