# LiveComponents Performance Guide

Comprehensive guide to optimizing LiveComponent performance through caching, batching, fragment updates, and advanced patterns.

## Table of Contents

- [Overview](#overview)
- [Fragment-Based Rendering](#fragment-based-rendering)
- [Component Caching](#component-caching)
- [Advanced Caching: varyBy](#advanced-caching-varyby)
- [Stale-While-Revalidate (SWR)](#stale-while-revalidate-swr)
- [Request Batching](#request-batching)
- [Polling Optimization](#polling-optimization)
- [Performance Monitoring](#performance-monitoring)
- [Best Practices](#best-practices)

---

## Overview

LiveComponents provides multiple performance optimization strategies:

1. **Fragment Rendering** - Update only changed parts of the DOM (60-90% bandwidth reduction)
2. **Component Caching** - Cache rendered components with intelligent invalidation
3. **VaryBy Context** - Cache variations based on user, locale, feature flags
4. **Stale-While-Revalidate** - Serve stale data while refreshing in background
5. **Request Batching** - Combine multiple actions into single request
6. **Optimized Polling** - Intelligent intervals with backoff strategies

### Performance Impact

| Optimization | Bandwidth Reduction | Latency Reduction | User Experience |
|-------------|---------------------|-------------------|-----------------|
| Fragment Updates | 60-90% | 50-80% | Instant updates |
| Component Caching | 95-99% | 90-95% | Near-instant loads |
| VaryBy Context | +10-20% | Minimal | Personalized caching |
| SWR Pattern | Variable | 100% (perceived) | Zero waiting |
| Request Batching | 40-70% | 60-80% | Fewer requests |

---

## Fragment-Based Rendering

### What Are Fragments?

Fragments are **marked sections** of your component template that can be updated independently:

```html
<div data-lc-component="dashboard">
    <!-- Fragment 1: Only updates when stats change -->
    <div data-lc-fragment="user-stats">
        <h3>Statistics</h3>
        <p>Total: {stats.total}</p>
        <p>Active: {stats.active}</p>
    </div>

    <!-- Fragment 2: Only updates when activities change -->
    <div data-lc-fragment="recent-activity">
        <h3>Recent Activity</h3>
        <ul>
            <for items="activities" as="activity">
                <li>{activity.name} - {activity.time}</li>
            </for>
        </ul>
    </div>

    <!-- Fragment 3: Updates independently -->
    <div data-lc-fragment="notifications">
        <span class="badge">{notification_count}</span>
    </div>
</div>
```

### Using Fragments

**Automatic Fragment Updates**:
```html
<!-- Button updates only user-stats fragment -->
<button
    data-lc-action="refreshStats"
    data-lc-fragments="user-stats"
>
    Refresh Stats Only
</button>

<!-- Button updates multiple fragments -->
<button
    data-lc-action="refreshAll"
    data-lc-fragments="user-stats,recent-activity"
>
    Refresh Stats & Activity
</button>
```

**JavaScript Fragment Updates**:
```javascript
// Update specific fragments
await liveComponent.executeAction('refreshStats', {}, {
    fragments: ['user-stats']
});

// Update multiple fragments
await liveComponent.executeAction('updateDashboard', {}, {
    fragments: ['user-stats', 'recent-activity', 'notifications']
});
```

### Backend Fragment Support

```php
use App\Framework\LiveComponents\Rendering\FragmentRenderer;

#[LiveComponent('dashboard')]
final readonly class DashboardComponent implements LiveComponentContract
{
    public function __construct(
        public ComponentId $id,
        public DashboardState $state,
        private FragmentRenderer $fragmentRenderer
    ) {}

    #[Action]
    public function refreshStats(): DashboardState
    {
        // Update only stats in state
        $newStats = $this->statsService->getLatest();

        return $this->state->withStats($newStats);
    }

    // Framework automatically extracts requested fragments
    // No manual fragment handling needed!
}
```

### Fragment Performance Characteristics

**Bandwidth Reduction**:
```
Full Render:     50 KB HTML
Fragment Update:  5 KB HTML (90% reduction)

Full Render:     20 KB HTML (dashboard)
Fragment Update:  2 KB HTML (stats only) (90% reduction)

Full Render:     100 KB HTML (product list)
Fragment Update:  8 KB HTML (grid only) (92% reduction)
```

**DOM Operations**:
```
Full Render:     500 nodes replaced
Fragment Update: 30 nodes replaced (94% reduction)

Full Render:     1000 nodes traversed
Fragment Update: 100 nodes traversed (90% reduction)
```

**Perceived Performance**:
- **Full Render**: 200-500ms (visible flash, scroll reset)
- **Fragment Update**: 50-100ms (smooth, no interruption)

### Fragment Best Practices

**1. Identify Independent Sections**

```html
<!-- ✅ Good - Independent fragments with clear boundaries -->
<div data-lc-fragment="sidebar"><!-- Sidebar content --></div>
<div data-lc-fragment="main-content"><!-- Main content --></div>
<div data-lc-fragment="footer"><!-- Footer --></div>

<!-- ❌ Bad - Overlapping fragments -->
<div data-lc-fragment="header">
    <div data-lc-fragment="nav"><!-- Nested fragment causes confusion --></div>
</div>
```

**2. Use Fragments for High-Frequency Updates**

```html
<!-- ✅ Good - Frequently updated data as fragment -->
<div data-lc-fragment="stock-price">
    ${current_price} <span class="change">{change}%</span>
</div>

<!-- Poll only updates the fragment, not entire page -->
<button data-lc-action="poll" data-lc-fragments="stock-price">Refresh</button>
```

**3. Combine Related Updates**

```javascript
// ✅ Good - Update related fragments together
liveComponent.executeAction('updateDashboard', {}, {
    fragments: ['stats', 'chart', 'summary']
});

// ❌ Bad - Multiple separate requests
liveComponent.executeAction('updateStats');
liveComponent.executeAction('updateChart');
liveComponent.executeAction('updateSummary');
```

---

## Component Caching

### Enable Caching

```php
use App\Framework\LiveComponents\Attributes\Action;
use App\Framework\LiveComponents\Contracts\Cacheable;
use App\Framework\Core\ValueObjects\Duration;

#[LiveComponent('product-list')]
final readonly class ProductListComponent implements
    LiveComponentContract,
    Cacheable
{
    // Implement Cacheable interface
    public function getCacheTTL(): Duration
    {
        return Duration::fromMinutes(5);
    }

    public function getCacheKey(): string
    {
        return "products-{$this->state->categoryId}";
    }

    public function shouldCache(): bool
    {
        // Don't cache for admin users
        return !$this->state->isAdmin;
    }

    #[Action]
    public function loadProducts(): ProductListState
    {
        // Expensive database query
        $products = $this->productRepository->findByCategory(
            $this->state->categoryId
        );

        return $this->state->withProducts($products);
    }
}
```

### Cache Invalidation

**Manual Invalidation**:
```php
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;

public function updateProduct(Product $product): void
{
    $this->productRepository->save($product);

    // Invalidate component cache
    $this->cache->forget(
        CacheKey::fromString("livecomponent:product-list:products-{$product->categoryId}")
    );
}
```

**Event-Based Invalidation**:
```php
use App\Framework\Event\EventHandler;

#[EventHandler]
final readonly class InvalidateProductCacheHandler
{
    public function handle(ProductUpdatedEvent $event): void
    {
        // Invalidate all product list caches for this category
        $this->cache->invalidateTag("product-category-{$event->categoryId}");
    }
}
```

### Cache Tags

```php
public function getCacheTags(): array
{
    return [
        "product-category-{$this->state->categoryId}",
        "user-{$this->state->userId}",
        'products'
    ];
}

// Invalidate all products
$this->cache->invalidateTag('products');

// Invalidate specific category
$this->cache->invalidateTag('product-category-123');
```

---

## Advanced Caching: varyBy

### What is varyBy?

**varyBy** allows you to cache different versions of the same component based on context:

- **User ID**: Different cache per user
- **Locale**: Different cache per language
- **Roles**: Different cache per permission level
- **Feature Flags**: Different cache when features enabled
- **Custom**: Any custom factor (theme, region, etc.)

### Basic varyBy Usage

```php
use App\Framework\LiveComponents\Contracts\Cacheable;
use App\Framework\LiveComponents\Caching\VaryBy;
use App\Framework\Core\ValueObjects\Duration;

#[LiveComponent('user-dashboard')]
final readonly class UserDashboardComponent implements
    LiveComponentContract,
    Cacheable
{
    public function getCacheTTL(): Duration
    {
        return Duration::fromMinutes(10);
    }

    public function getCacheKey(): string
    {
        return 'dashboard';
    }

    // Cache varies by user ID and locale
    public function getVaryBy(): ?VaryBy
    {
        return VaryBy::userAndLocale();
    }

    // Results in cache keys like:
    // livecomponent:user-dashboard:dashboard:u:123:l:en
    // livecomponent:user-dashboard:dashboard:u:456:l:de
}
```

### VaryBy Factory Methods

```php
// No variation - global cache
VaryBy::none();

// Vary by user ID only
VaryBy::userId();

// Vary by locale only
VaryBy::locale();

// Vary by user and locale (most common)
VaryBy::userAndLocale();

// Vary by everything
VaryBy::all();

// Vary by feature flags
VaryBy::featureFlags(['new-ui', 'dark-mode']);

// Vary by custom factors
VaryBy::custom(['theme', 'region']);
```

### Fluent VaryBy Builder

```php
public function getVaryBy(): ?VaryBy
{
    return VaryBy::none()
        ->withUserId()           // Add user variation
        ->withLocale()           // Add locale variation
        ->withFeatureFlags([     // Add feature flag variation
            'new-ui',
            'dark-mode',
            'beta-features'
        ])
        ->withCustom(['theme']); // Add custom variation
}

// Results in:
// livecomponent:dashboard:main:u:123:l:en:f:new-ui,dark-mode:c:theme=dark
```

### VaryBy Examples

**User-Specific Dashboard**:
```php
#[LiveComponent('user-dashboard')]
final readonly class UserDashboardComponent implements Cacheable
{
    public function getVaryBy(): ?VaryBy
    {
        return VaryBy::userId();
    }

    // Each user gets their own cached version
    // User 123: livecomponent:user-dashboard:main:u:123
    // User 456: livecomponent:user-dashboard:main:u:456
}
```

**Localized Content**:
```php
#[LiveComponent('product-catalog')]
final readonly class ProductCatalogComponent implements Cacheable
{
    public function getVaryBy(): ?VaryBy
    {
        return VaryBy::locale();
    }

    // Each language gets its own cache
    // English: livecomponent:product-catalog:products:l:en
    // German:  livecomponent:product-catalog:products:l:de
}
```

**Permission-Based Content**:
```php
#[LiveComponent('admin-panel')]
final readonly class AdminPanelComponent implements Cacheable
{
    public function getVaryBy(): ?VaryBy
    {
        return VaryBy::roles();
    }

    // Each role combination gets its own cache
    // Admin:     livecomponent:admin-panel:main:r:admin
    // Moderator: livecomponent:admin-panel:main:r:moderator
    // Both:      livecomponent:admin-panel:main:r:admin,moderator
}
```

**Feature Flag Variations**:
```php
#[LiveComponent('homepage')]
final readonly class HomepageComponent implements Cacheable
{
    public function getVaryBy(): ?VaryBy
    {
        return VaryBy::featureFlags(['new-design', 'personalized-feed']);
    }

    // Different cache for each feature combination
    // Old design:           livecomponent:homepage:main:global
    // New design only:      livecomponent:homepage:main:f:new-design
    // Both features:        livecomponent:homepage:main:f:new-design,personalized-feed
}
```

**Complex Multi-Factor Caching**:
```php
#[LiveComponent('shopping-cart')]
final readonly class ShoppingCartComponent implements Cacheable
{
    public function getVaryBy(): ?VaryBy
    {
        return VaryBy::none()
            ->withUserId()                      // Different per user
            ->withLocale()                      // Different per language
            ->withFeatureFlags(['express-checkout'])  // Different if feature enabled
            ->withCustom(['currency', 'region']);     // Different per currency/region
    }

    // Results in keys like:
    // livecomponent:cart:main:u:123:l:en:f:express-checkout:c:currency=USD,region=US
    // livecomponent:cart:main:u:123:l:de:c:currency=EUR,region=DE
}
```

### CacheContext and Runtime Context

The framework automatically provides `CacheContext` based on the current request:

```php
// Framework automatically creates CacheContext from:
$context = CacheContext::create(
    userId: $currentUser?->id,              // From auth session
    locale: $request->getLocale(),          // From request
    roles: $currentUser?->roles ?? [],      // From user object
    featureFlags: $featureFlags->getActive(), // From feature service
    custom: [
        'theme' => $request->cookie->get('theme'),
        'region' => $request->getRegion()
    ]
);

// VaryBy then applies this context to generate cache key suffix
$suffix = $varyBy->apply($context);
```

### VaryBy Performance Impact

**Cache Hit Rate**:
```
No varyBy (global):           95% hit rate, 1 cache entry
varyBy userId:                85% hit rate, 1000 cache entries
varyBy userId + locale:       75% hit rate, 3000 cache entries
varyBy userId + locale + flags: 60% hit rate, 10000 cache entries
```

**Trade-offs**:
- **More specific varyBy** = More cache entries = Lower hit rate = More storage
- **Less specific varyBy** = Fewer cache entries = Higher hit rate = Less personalization

**Recommendation**: Start with `VaryBy::userId()` or `VaryBy::userAndLocale()`, add more factors only if needed.

---

## Stale-While-Revalidate (SWR)

### What is SWR?

**Stale-While-Revalidate** serves cached content immediately (even if expired) while refreshing in the background:

```
Traditional:
Request → Cache Miss → Database → Response (500ms)

SWR:
Request → Stale Cache → Response (10ms) + Background Refresh → Updated Cache
```

### Enable SWR

```php
use App\Framework\LiveComponents\Contracts\Cacheable;
use App\Framework\Core\ValueObjects\Duration;

#[LiveComponent('news-feed')]
final readonly class NewsFeedComponent implements Cacheable
{
    public function getCacheTTL(): Duration
    {
        return Duration::fromMinutes(5); // Fresh for 5 minutes
    }

    public function getStaleWhileRevalidate(): ?Duration
    {
        return Duration::fromMinutes(30); // Serve stale up to 30 minutes
    }

    public function getCacheKey(): string
    {
        return 'feed-latest';
    }
}
```

**How it works**:
1. **0-5 min**: Cache fresh → Serve from cache (instant)
2. **5-30 min**: Cache stale → Serve stale (instant) + refresh in background
3. **30+ min**: Cache expired → Fetch fresh → Update cache → Serve

### SWR with varyBy

```php
public function getVaryBy(): ?VaryBy
{
    return VaryBy::userId();
}

public function getStaleWhileRevalidate(): ?Duration
{
    return Duration::fromHours(1);
}

// Each user gets their own SWR cache:
// User 123: Fresh for 5min, stale-ok for 1h
// User 456: Fresh for 5min, stale-ok for 1h
```

### SWR Metrics

The framework provides metrics for SWR performance:

```php
use App\Framework\LiveComponents\Caching\CacheMetrics;

// After rendering component
$metrics = $component->getCacheMetrics();

if ($metrics->isStale) {
    $this->logger->info('SWR: Served stale content', [
        'age_seconds' => $metrics->ageSeconds,
        'is_refreshing' => $metrics->isRefreshing,
        'cache_key' => $metrics->cacheKey
    ]);
}
```

### SWR Use Cases

**✅ Perfect for SWR**:
- News feeds (5min fresh, 1h stale-ok)
- Product listings (10min fresh, 2h stale-ok)
- User profiles (15min fresh, 24h stale-ok)
- Dashboards (5min fresh, 30min stale-ok)

**❌ Not suitable for SWR**:
- Real-time stock prices
- Shopping cart totals
- Payment confirmations
- Authentication status

### SWR Response Headers

The framework automatically adds cache headers:

```http
Cache-Control: max-age=300, stale-while-revalidate=1800
Age: 450
X-Cache-Status: STALE
X-Cache-Refreshing: true
```

Client-side interpretation:
```javascript
const cacheStatus = response.headers.get('X-Cache-Status');

if (cacheStatus === 'STALE') {
    // Show indicator: "Updating..."
    showRefreshIndicator();

    // Listen for updated data
    liveComponent.on('cache:refreshed', (data) => {
        hideRefreshIndicator();
        // Data automatically updated
    });
}
```

---

## Request Batching

### What is Batching?

Combine multiple component actions into a single HTTP request:

```
Without Batching:
Action 1 → HTTP Request → Response (100ms)
Action 2 → HTTP Request → Response (100ms)
Action 3 → HTTP Request → Response (100ms)
Total: 300ms + 3x overhead

With Batching:
Actions 1+2+3 → Single HTTP Request → Response (120ms)
Total: 120ms + 1x overhead
```

### Enable Batching

```javascript
import { LiveComponent } from './live-component.js';

const component = new LiveComponent('dashboard:main', {
    batching: {
        enabled: true,
        maxBatchSize: 10,      // Max actions per batch
        batchWindow: 50        // Wait 50ms to collect actions
    }
});

// Execute multiple actions
component.executeAction('updateStats');
component.executeAction('updateChart');
component.executeAction('updateNotifications');

// Framework automatically batches these into single request
```

### Batching Strategies

**Auto-Batching** (default):
```javascript
// Framework detects rapid actions and batches automatically
for (let i = 0; i < 5; i++) {
    liveComponent.executeAction('increment');
}
// Sent as single batched request
```

**Manual Batching**:
```javascript
liveComponent.batch([
    { action: 'updateStats', params: {} },
    { action: 'updateChart', params: { period: '7d' } },
    { action: 'refreshNotifications', params: {} }
]);
```

**Conditional Batching**:
```javascript
// Only batch if multiple actions pending
const config = {
    batching: {
        enabled: true,
        minBatchSize: 2  // Only batch if 2+ actions
    }
};
```

### Backend Batch Processing

```php
use App\Framework\LiveComponents\Batching\BatchRequest;
use App\Framework\LiveComponents\Batching\BatchResponse;

// Framework automatically handles batched requests
// Your component actions work the same way

#[Action]
public function updateStats(): DashboardState
{
    // Normal action implementation
    // Framework handles batching transparently
}
```

### Batching Performance

**Bandwidth Reduction**:
```
3 separate requests:  3x overhead (headers, handshake) = ~2KB
1 batched request:    1x overhead = ~0.7KB (65% reduction)
```

**Latency Reduction**:
```
3 sequential requests: 300ms
3 parallel requests:   100ms (but 3x connections)
1 batched request:     120ms (60% faster than sequential)
```

---

## Polling Optimization

### Adaptive Polling Intervals

```php
use App\Framework\LiveComponents\Contracts\Pollable;
use App\Framework\Core\ValueObjects\Duration;

#[LiveComponent('stock-ticker')]
final readonly class StockTickerComponent implements
    LiveComponentContract,
    Pollable
{
    public function poll(): StockTickerState
    {
        $price = $this->stockService->getCurrentPrice($this->state->symbol);

        return $this->state->withPrice($price);
    }

    public function getPollInterval(): int
    {
        // Adaptive interval based on market hours
        $isMarketOpen = $this->timeService->isMarketOpen();

        return $isMarketOpen
            ? 5000   // 5 seconds during market hours
            : 60000; // 1 minute when market closed
    }
}
```

### Backoff Strategies

```javascript
// Exponential backoff on errors
const component = new LiveComponent('notifications:main', {
    polling: {
        enabled: true,
        interval: 10000,        // Start with 10s
        maxInterval: 300000,    // Max 5 minutes
        backoffMultiplier: 2,   // Double on each error
        backoffReset: 60000     // Reset after 1min success
    }
});

// After error:
// Attempt 1: 10s
// Attempt 2: 20s (error)
// Attempt 3: 40s (error)
// Attempt 4: 80s (error)
// Attempt 5: 160s (error)
// Attempt 6: 300s (capped at max)
```

### Conditional Polling

```php
public function shouldPoll(): bool
{
    // Don't poll if user inactive
    if ($this->state->lastActivityAt < time() - 300) {
        return false;
    }

    // Don't poll if data is fresh
    if ($this->state->updatedAt > time() - 60) {
        return false;
    }

    return true;
}
```

### Visibility-Based Polling

```javascript
// Stop polling when tab hidden, resume when visible
document.addEventListener('visibilitychange', () => {
    if (document.hidden) {
        liveComponent.pausePolling();
    } else {
        liveComponent.resumePolling();
    }
});
```

---

## Performance Monitoring

### Cache Metrics

```php
use App\Framework\LiveComponents\Caching\CacheMetrics;

// Log cache performance
$this->logger->info('Component cached', [
    'hit' => $metrics->hit,
    'age_seconds' => $metrics->ageSeconds,
    'is_stale' => $metrics->isStale,
    'freshness_percent' => $metrics->getFreshnessPercent(),
    'cache_key' => $metrics->cacheKey
]);
```

### Performance Tracking

```php
use App\Framework\Performance\PerformanceCollector;

#[Action]
public function loadData(): ComponentState
{
    $start = microtime(true);

    $data = $this->expensiveOperation();

    $this->performance->track('component.action.load-data', [
        'duration_ms' => (microtime(true) - $start) * 1000,
        'cache_hit' => $this->wasCached(),
        'fragment_count' => count($this->getFragments())
    ]);

    return $this->state->withData($data);
}
```

### Client-Side Performance

```javascript
// Track render performance
liveComponent.on('render:complete', (metrics) => {
    console.log('Render performance:', {
        duration: metrics.renderDuration,
        fragmentCount: metrics.fragmentsUpdated,
        nodesPatched: metrics.nodesPatched,
        wasCached: metrics.fromCache
    });

    // Send to analytics
    analytics.track('livecomponent.render', metrics);
});
```

---

## Best Practices

### 1. Use Fragments for Large Components

```html
<!-- ✅ Good - Fragment-based dashboard -->
<div data-lc-component="dashboard">
    <div data-lc-fragment="stats"><!-- 2KB --></div>
    <div data-lc-fragment="chart"><!-- 15KB --></div>
    <div data-lc-fragment="activity"><!-- 8KB --></div>
</div>

<!-- Update only stats fragment (2KB instead of 25KB) -->
<button data-lc-action="refreshStats" data-lc-fragments="stats">Refresh</button>
```

### 2. Cache Expensive Renders

```php
// ✅ Good - Cache expensive database queries
public function getCacheTTL(): Duration
{
    return Duration::fromMinutes(5);
}

public function getVaryBy(): ?VaryBy
{
    return VaryBy::userId();
}
```

### 3. Use SWR for Non-Critical Data

```php
// ✅ Good - News feed with SWR
public function getCacheTTL(): Duration
{
    return Duration::fromMinutes(5);
}

public function getStaleWhileRevalidate(): ?Duration
{
    return Duration::fromHours(1); // Serve stale up to 1 hour
}
```

### 4. Batch Related Actions

```javascript
// ✅ Good - Batch dashboard updates
component.batch([
    { action: 'updateStats' },
    { action: 'updateChart' },
    { action: 'refreshActivity' }
]);

// ❌ Bad - Separate requests
component.executeAction('updateStats');
component.executeAction('updateChart');
component.executeAction('refreshActivity');
```

### 5. Optimize Polling

```php
// ✅ Good - Adaptive polling interval
public function getPollInterval(): int
{
    return $this->isHighPriority() ? 5000 : 30000;
}

// ❌ Bad - Aggressive fixed interval
public function getPollInterval(): int
{
    return 1000; // Too frequent!
}
```

### 6. Monitor Performance

```php
// ✅ Good - Track and optimize
$this->performance->track('component.render', [
    'duration_ms' => $duration,
    'cache_hit' => $cacheHit,
    'fragment_count' => $fragmentCount
]);

// Alert if performance degrades
if ($duration > 500) {
    $this->alerting->send('Slow component render', $context);
}
```

---

## Performance Checklist

Before deploying to production:

- [ ] Identify large components and add fragments
- [ ] Enable caching for expensive renders
- [ ] Configure appropriate varyBy context
- [ ] Use SWR for non-critical data
- [ ] Batch related actions
- [ ] Optimize polling intervals
- [ ] Add performance monitoring
- [ ] Run performance benchmarks
- [ ] Test with realistic data volumes
- [ ] Monitor cache hit rates

---

## Summary

LiveComponents performance optimizations:

✅ **Fragment Rendering** - 60-90% bandwidth reduction
✅ **Component Caching** - 95-99% faster repeated renders
✅ **VaryBy Context** - Intelligent cache variations
✅ **Stale-While-Revalidate** - Zero perceived latency
✅ **Request Batching** - 40-70% fewer requests
✅ **Adaptive Polling** - Optimized real-time updates

Apply these strategies based on your use case for optimal performance.
