Files
michaelschiemer/docs/livecomponents/island-directive.md
2025-11-24 21:28:25 +01:00

8.0 KiB

Island Directive

Isolated Component Rendering - Render heavy components separately for better performance.

Overview

The #[Island] directive enables isolated rendering of LiveComponents, allowing them to be rendered separately from the main template flow. This is particularly useful for resource-intensive components that can slow down page rendering.

Features

  • Isolated Rendering: Components are rendered separately without template wrapper
  • Lazy Loading: Optional lazy loading when component enters viewport
  • Independent Caching: Islands can have their own caching strategies
  • Isolated Events: Islands don't receive parent component events

Basic Usage

Simple Island Component

use App\Framework\LiveComponents\Attributes\LiveComponent;
use App\Framework\LiveComponents\Attributes\Island;

#[LiveComponent('heavy-widget')]
#[Island]
final readonly class HeavyWidgetComponent implements LiveComponentContract
{
    // Component implementation
}

Lazy Island Component

#[LiveComponent('metrics-dashboard')]
#[Island(isolated: true, lazy: true, placeholder: 'Loading dashboard...')]
final readonly class MetricsDashboardComponent implements LiveComponentContract
{
    // Component implementation
}

Configuration Options

isolated (bool, default: true)

Whether the component should be rendered in isolation. When true, the component is rendered via the /island endpoint without template wrapper.

#[Island(isolated: true)]  // Isolated rendering (default)
#[Island(isolated: false)] // Normal rendering (not recommended)

lazy (bool, default: false)

Whether the component should be lazy-loaded when entering the viewport. When true, a placeholder is generated and the component is loaded via IntersectionObserver.

#[Island(lazy: true)]  // Lazy load on viewport entry
#[Island(lazy: false)] // Load immediately (default)

placeholder (string|null, default: null)

Custom placeholder text shown while the component is loading (only used when lazy: true).

#[Island(lazy: true, placeholder: 'Loading widget...')]

Template Usage

In Templates

Island components are used the same way as regular LiveComponents:

<x-heavy-widget id="user-123" />

When processed, lazy Islands generate a placeholder:

<div data-island-component="true"
     data-live-component-lazy="heavy-widget:user-123"
     data-lazy-priority="normal"
     data-lazy-threshold="0.1">
  <div class="island-placeholder">Loading widget...</div>
</div>

Island vs. Lazy Component

Lazy Component (data-live-component-lazy)

  • Loaded when entering viewport
  • Part of normal template flow
  • Shares state/events with parent components
  • Uses /lazy-load endpoint

Island Component (data-island-component)

  • Rendered in isolation (separate request)
  • No template wrapper (no layout/meta)
  • Isolated event context (no parent events)
  • Optional lazy loading on viewport entry
  • Uses /island endpoint

Performance Benefits

Isolation

Islands reduce template processing overhead for heavy components by rendering them separately. This means:

  • Heavy components don't block main page rendering
  • Independent error handling (one island failure doesn't affect others)
  • Separate caching strategies per island

Lazy Loading

When combined with lazy loading, Islands provide:

  • Reduced initial page load time
  • Faster Time to Interactive (TTI)
  • Better Core Web Vitals scores

Example Performance Impact

// Without Island: 500ms page load (heavy component blocks rendering)
// With Island: 200ms page load + 300ms island load (non-blocking)

Use Cases

Heavy Widgets

#[LiveComponent('analytics-dashboard')]
#[Island(isolated: true, lazy: true, placeholder: 'Loading analytics...')]
final readonly class AnalyticsDashboardComponent implements LiveComponentContract
{
    // Heavy component with complex data processing
}

Third-Party Integrations

#[LiveComponent('external-map')]
#[Island(isolated: true, lazy: true)]
final readonly class ExternalMapComponent implements LiveComponentContract
{
    // Component that loads external resources
}

Conditional Components

#[LiveComponent('admin-panel')]
#[Island(isolated: true)]
final readonly class AdminPanelComponent implements LiveComponentContract
{
    // Component only visible to admins
    // Isolated to prevent template processing for non-admin users
}

Technical Details

Endpoint

Islands are rendered via:

GET /live-component/{id}/island

This endpoint:

  • Renders component HTML without template wrapper
  • Returns JSON with html, state, csrf_token
  • Uses ComponentRegistry.render() directly (no wrapper)

Frontend Integration

Islands are automatically detected and handled by LazyComponentLoader:

  • Lazy islands: Loaded when entering viewport
  • Non-lazy islands: Loaded immediately
  • Isolated initialization: Islands don't receive parent events

Event Isolation

Islands have isolated event contexts:

  • No parent component events
  • No shared state with parent components
  • Independent SSE channels (if configured)

Best Practices

  1. Use Islands for Heavy Components: Only use #[Island] for components that significantly impact page load time

  2. Combine with Lazy Loading: Use lazy: true for below-the-fold content

  3. Provide Placeholders: Always provide meaningful placeholder text for better UX

  4. Test Isolation: Verify that islands work correctly in isolation

  5. Monitor Performance: Track island load times and optimize as needed

Examples

Complete Example

<?php

declare(strict_types=1);

namespace App\Application\LiveComponents\Dashboard;

use App\Framework\LiveComponents\Attributes\LiveComponent;
use App\Framework\LiveComponents\Attributes\Island;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\ComponentRenderData;
use App\Framework\LiveComponents\ValueObjects\ComponentState;

#[LiveComponent('metrics-dashboard')]
#[Island(isolated: true, lazy: true, placeholder: 'Loading metrics...')]
final readonly class MetricsDashboardComponent implements LiveComponentContract
{
    public function __construct(
        public ComponentId $id,
        public ComponentState $state
    ) {
    }

    public function getRenderData(): ComponentRenderData
    {
        return new ComponentRenderData(
            templatePath: 'livecomponent-metrics-dashboard',
            data: [
                'componentId' => $this->id->toString(),
                'metrics' => $this->loadMetrics(),
            ]
        );
    }

    private function loadMetrics(): array
    {
        // Heavy operation - isolated rendering prevents blocking
        return [
            'users' => 12345,
            'orders' => 6789,
            'revenue' => 123456.78,
        ];
    }
}

Migration Guide

Converting Regular Component to Island

  1. Add #[Island] attribute to component class
  2. Test component rendering (should work identically)
  3. Optionally enable lazy loading with lazy: true
  4. Monitor performance improvements

Backward Compatibility

  • Components without #[Island] work exactly as before
  • #[Island] is opt-in (no breaking changes)
  • Existing lazy components continue to work

Troubleshooting

Island Not Loading

  • Check browser console for errors
  • Verify component is registered in ComponentRegistry
  • Ensure /island endpoint is accessible

Placeholder Not Showing

  • Verify lazy: true is set
  • Check placeholder parameter is provided
  • Inspect generated HTML for data-island-component attribute

Events Not Working

  • Islands have isolated event contexts
  • Use component-specific events, not parent events
  • Check SSE channel configuration if using real-time updates

See Also