Files
michaelschiemer/docs/livecomponents/troubleshooting.md
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

20 KiB

LiveComponents Troubleshooting Guide

Common Issues and Solutions

This guide helps diagnose and resolve common LiveComponents problems.


Table of Contents

  1. Component Not Responding
  2. State Synchronization Issues
  3. CSRF Token Errors
  4. Rate Limiting Issues
  5. SSE Connection Problems
  6. Performance Issues
  7. File Upload Problems
  8. Fragment Rendering Issues
  9. Browser Compatibility
  10. Debugging Tools

Component Not Responding

Symptoms

  • Clicking buttons does nothing
  • Forms don't submit
  • No network requests in DevTools
  • Component appears static

Diagnostic Steps

1. Check Component Initialization

// Open browser console
console.log(LiveComponent.components);

// Should show active components
// If empty, component not initialized

2. Verify Component Markup

<!-- Required attributes -->
<div data-component-id="{component_id}" data-component-name="ComponentName">
    <!-- Content -->
</div>

3. Check JavaScript Loading

// In browser console
typeof LiveComponent !== 'undefined'  // Should be true

4. Inspect Browser Console

F12 → Console Tab
Look for JavaScript errors

Common Causes & Solutions

Missing data-component-id

<!-- ❌ Wrong - missing required attribute -->
<div class="component">
    <button data-lc-action="save">Save</button>
</div>

<!-- ✅ Correct -->
<div data-component-id="{component_id}" data-component-name="MyComponent">
    <button data-lc-action="save">Save</button>
</div>

JavaScript Not Loaded

# Rebuild JavaScript assets
npm run build

# Check if file exists
ls public/assets/js/livecomponent.js

# Development mode
npm run dev

HTTPS Not Enabled

# LiveComponents requires HTTPS
# Start with HTTPS
make up

# Access via
https://localhost  # ✅ Correct
http://localhost   # ❌ Wrong - will fail

Component Class Not Found

// Verify class exists and namespace is correct
namespace App\Application\LiveComponents;

final class MyComponent extends LiveComponent
{
    // Implementation
}

// In controller
LiveComponent::mount(MyComponent::class)  // ✅
LiveComponent::mount('MyComponent')       // ❌ Wrong

State Synchronization Issues

Symptoms

  • Property changes don't reflect in UI
  • UI updates but server state wrong
  • State resets unexpectedly

Diagnostic Steps

1. Check LiveProp Attribute

// ❌ Wrong - missing attribute
public string $name = '';

// ✅ Correct
#[LiveProp]
public string $name = '';

2. Verify Property Type Compatibility

// ✅ Supported types
#[LiveProp] public string $text;
#[LiveProp] public int $count;
#[LiveProp] public float $price;
#[LiveProp] public bool $active;
#[LiveProp] public array $items;

// ❌ Not supported - can't serialize
#[LiveProp] public \Closure $callback;
#[LiveProp] public Resource $handle;
#[LiveProp] public PDO $connection;

3. Inspect Current State

// Get component state
const component = LiveComponent.getComponent('component-id');
console.log('Current state:', component.state);

Common Causes & Solutions

Property Not Marked as LiveProp

// Problem: Property changes not synced
public string $searchTerm = '';  // ❌ Missing #[LiveProp]

#[LiveAction]
public function search(): void
{
    // $this->searchTerm not synced with client
    $this->results = $this->searchService->search($this->searchTerm);
}

// Solution: Add attribute
#[LiveProp]
public string $searchTerm = '';  // ✅ Now syncs

Complex Object Serialization

// Problem: Object can't be serialized
#[LiveProp]
public User $user;  // ❌ Complex object

// Solution: Use Value Object or primitives
#[LiveProp]
public string $userId;

private User $user;  // Not serialized

public function hydrate(array $state): void
{
    parent::hydrate($state);

    // Reload user from ID
    $this->user = $this->userRepository->find($this->userId);
}

State Checksum Mismatch

// Monitor checksum errors
window.addEventListener('livecomponent:error', (e) => {
    if (e.detail.code === 'CHECKSUM_MISMATCH') {
        console.error('State corrupted - refreshing component');
        LiveComponent.refresh(e.detail.componentId);
    }
});

CSRF Token Errors

Symptoms

  • "CSRF token mismatch" error
  • 403 Forbidden responses
  • Actions fail silently

Diagnostic Steps

1. Check CSRF Meta Tag

<!-- Must be in <head> -->
<meta name="csrf-token" content="{csrf_token}">

2. Verify Token in Requests

F12 → Network Tab → Select request → Headers
Should have: X-CSRF-Token: {token}

3. Check Token Expiry

// .env configuration
CSRF_TOKEN_LIFETIME=7200  // 2 hours (default)

Common Causes & Solutions

Missing CSRF Meta Tag

<!-- ❌ Wrong - no CSRF token -->
<head>
    <title>My App</title>
</head>

<!-- ✅ Correct - token included -->
<head>
    <title>My App</title>
    <meta name="csrf-token" content="{csrf_token}">
</head>

Token Expired

// Automatic token refresh
window.addEventListener('livecomponent:error', (e) => {
    if (e.detail.code === 'CSRF_TOKEN_EXPIRED') {
        // Refresh token
        LiveComponent.refreshCsrfToken().then(() => {
            // Retry failed action
            LiveComponent.retryLastAction();
        });
    }
});

Session Cleared

// Problem: Session expired/cleared
// Solution: Redirect to login
window.addEventListener('livecomponent:error', (e) => {
    if (e.detail.code === 'SESSION_EXPIRED') {
        window.location.href = '/login?redirect=' + window.location.pathname;
    }
});

Development vs Production

# Development - lenient
CSRF_TOKEN_LIFETIME=86400  # 24 hours

# Production - strict
CSRF_TOKEN_LIFETIME=3600   # 1 hour
CSRF_REGENERATE_ON_ACTION=true

Rate Limiting Issues

Symptoms

  • "Too many requests" error (429)
  • Actions blocked after rapid clicks
  • "Retry-After" headers in response

Diagnostic Steps

1. Check Rate Limit Configuration

LIVECOMPONENT_RATE_LIMIT=60        # Default: 60/minute
LIVECOMPONENT_RATE_LIMIT_WINDOW=60 # Window in seconds

2. Monitor Rate Limit Events

window.addEventListener('livecomponent:rate-limited', (e) => {
    console.warn('Rate limited:', {
        action: e.detail.action,
        retryAfter: e.detail.retryAfter
    });
});

3. Check Component-Specific Limits

#[RateLimit(requests: 10, window: 60)]
final class SearchComponent extends LiveComponent
{
    // More restrictive than global limit
}

Common Causes & Solutions

Too Aggressive Rate Limit

# Problem: Rate limit too low
LIVECOMPONENT_RATE_LIMIT=10  # Too restrictive

# Solution: Increase for normal usage
LIVECOMPONENT_RATE_LIMIT=60  # More reasonable

Rapid User Actions

// Problem: User clicking too fast
#[LiveAction]
public function search(): void
{
    // Triggered on every keystroke
}

// Solution: Use debouncing
<input
    type="text"
    data-lc-model.debounce.500="searchTerm"
    value="{searchTerm}"
/>

Bot/Scraper Activity

use App\Framework\Http\Middlewares\RateLimitMiddleware;

// Implement stricter limits for suspicious patterns
#[RateLimit(requests: 5, window: 300)]  // 5 per 5 minutes
final class PublicApiComponent extends LiveComponent
{
    // Public-facing, needs protection
}

SSE Connection Problems

Symptoms

  • Real-time updates not working
  • Connection drops frequently
  • "SSE disconnected" in console

Diagnostic Steps

1. Verify HTTPS

SSE requires HTTPS in production
https://localhost ✅
http://localhost  ❌

2. Check SSE Configuration

LIVECOMPONENT_SSE_ENABLED=true
LIVECOMPONENT_SSE_HEARTBEAT=15  # Heartbeat interval

3. Monitor SSE Connection

window.addEventListener('livecomponent:sse-connected', () => {
    console.log('SSE connected');
});

window.addEventListener('livecomponent:sse-disconnected', (e) => {
    console.error('SSE disconnected:', e.detail.reason);
});

4. Check Server Logs

docker logs php | grep SSE

Common Causes & Solutions

HTTPS Not Enabled

# Problem: HTTP used in production
# Solution: Enable HTTPS

# Development
make up  # Automatically uses HTTPS

# Production
# Configure SSL certificates in nginx/apache

Firewall Blocking SSE

# nginx configuration
location /livecomponent/sse {
    proxy_pass http://php-fpm;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 86400s;  # 24 hours
}

Connection Timeout

// Increase SSE timeout
set_time_limit(0);  // No timeout
ignore_user_abort(true);

Auto-Reconnection Not Working

// Manual reconnection logic
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;

window.addEventListener('livecomponent:sse-disconnected', (e) => {
    if (reconnectAttempts < maxReconnectAttempts) {
        const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);

        setTimeout(() => {
            console.log(`Reconnecting SSE (attempt ${reconnectAttempts + 1})`);
            LiveComponent.reconnectSse(e.detail.componentId);
            reconnectAttempts++;
        }, delay);
    }
});

window.addEventListener('livecomponent:sse-connected', () => {
    reconnectAttempts = 0;  // Reset on successful connection
});

Performance Issues

Symptoms

  • Slow action responses (>500ms)
  • UI freezes during updates
  • High memory usage
  • Browser lag

Diagnostic Steps

1. Enable Performance Profiling

localStorage.setItem('livecomponent_devtools', 'true');
localStorage.setItem('livecomponent_profiling', 'true');
location.reload();

2. Check Action Latency

window.addEventListener('livecomponent:action-executed', (e) => {
    if (e.detail.duration > 200) {
        console.warn('Slow action:', {
            action: e.detail.action,
            duration: e.detail.duration
        });
    }
});

3. Monitor Memory Usage

F12 → Performance Tab → Memory
Record session and analyze heap snapshots

4. Analyze Network Requests

F12 → Network Tab
Check:
- Request count (should be low with batching)
- Payload sizes
- Response times

Common Causes & Solutions

Large Component State

// Problem: Serializing too much data
#[LiveProp]
public array $allProducts = [];  // ❌ 10,000 products

// Solution: Pagination
#[LiveProp]
public int $page = 1;

#[LiveProp]
public int $perPage = 50;

private array $products = [];  // ✅ Not serialized

public function mount(): void
{
    $this->loadPage();
}

private function loadPage(): void
{
    $this->products = $this->productRepository->paginate(
        page: $this->page,
        perPage: $this->perPage
    );
}

No Fragment Rendering

// Problem: Re-rendering entire component
#[LiveAction]
public function updateStats(): void
{
    $this->stats = $this->calculateStats();
    // Full component re-render ❌
}

// Solution: Use fragments
#[LiveAction]
#[Fragment('stats-section')]
public function updateStats(): void
{
    $this->stats = $this->calculateStats();
    // Only stats fragment re-renders ✅
}

N+1 Query Problem

// Problem: N+1 queries
public function render(): string
{
    foreach ($this->orders as $order) {
        $customer = $this->customerRepository->find($order->customerId);
        // ❌ Query per order
    }
}

// Solution: Eager loading
public function mount(): void
{
    $this->orders = $this->orderRepository->findWithCustomers();
    // ✅ Single query with JOIN
}

Too Many Components

Problem: >100 components on page
Solution:
1. Lazy load components
2. Use virtual scrolling
3. Implement pagination

See Performance Guide for comprehensive optimization strategies.


File Upload Problems

Symptoms

  • Upload fails silently
  • Progress stuck at 0%
  • Chunk upload errors
  • File size exceeded errors

Diagnostic Steps

1. Check Upload Configuration

<input
    type="file"
    data-lc-upload="handleUpload"
    data-chunk-size="1048576"
    data-max-file-size="104857600"
/>

2. Monitor Upload Events

window.addEventListener('livecomponent:upload-progress', (e) => {
    console.log(`Upload: ${e.detail.progress}%`);
});

window.addEventListener('livecomponent:upload-error', (e) => {
    console.error('Upload failed:', e.detail.error);
});

3. Check Server Limits

// php.ini
upload_max_filesize = 100M
post_max_size = 100M
max_execution_time = 300

Common Causes & Solutions

File Size Exceeds Limit

<!-- Problem: No validation -->
<input type="file" data-lc-upload="handleUpload" />

<!-- Solution: Set max size -->
<input
    type="file"
    data-lc-upload="handleUpload"
    data-max-file-size="104857600"
    accept=".pdf,.jpg,.png"
/>
// Client-side validation
window.addEventListener('livecomponent:upload-error', (e) => {
    if (e.detail.code === 'FILE_TOO_LARGE') {
        alert(`File too large. Max size: ${e.detail.maxSize / 1024 / 1024}MB`);
    }
});

Chunk Upload Failure

// Server-side: Implement retry logic
final class ChunkUploadHandler
{
    public function storeChunk(
        string $uploadId,
        int $chunkIndex,
        string $data
    ): void {
        $attempts = 0;
        $maxAttempts = 3;

        while ($attempts < $maxAttempts) {
            try {
                $this->storage->put(
                    "uploads/{$uploadId}/chunk_{$chunkIndex}",
                    $data
                );
                return;
            } catch (\Exception $e) {
                $attempts++;
                if ($attempts >= $maxAttempts) {
                    throw $e;
                }
                usleep(100000 * $attempts);  // Exponential backoff
            }
        }
    }
}

Upload State Lost

// Problem: Upload state not persisted
// Solution: Store upload state
final class UploadStateRepository
{
    public function saveState(UploadState $state): void
    {
        $this->cache->set(
            "upload_state:{$state->uploadId}",
            serialize($state),
            Duration::fromHours(24)
        );
    }

    public function restoreState(string $uploadId): ?UploadState
    {
        $data = $this->cache->get("upload_state:{$uploadId}");
        return $data ? unserialize($data) : null;
    }
}

Fragment Rendering Issues

Symptoms

  • Fragment not updating
  • Entire component re-renders
  • Focus lost after update
  • Nested fragments broken

Diagnostic Steps

1. Verify Fragment Markup

<!-- Check fragment attribute -->
<div data-lc-fragment="section-name">
    <!-- Content -->
</div>

2. Check Action Attribute

#[LiveAction]
#[Fragment('section-name')]  // Must match HTML
public function updateSection(): void
{
    // Update logic
}

3. Monitor Fragment Updates

window.addEventListener('livecomponent:fragment-updated', (e) => {
    console.log('Fragment updated:', {
        name: e.detail.fragmentName,
        duration: e.detail.duration
    });
});

Common Causes & Solutions

Fragment Name Mismatch

// Problem: Names don't match
#[Fragment('user-stats')]  // ❌ kebab-case in PHP
public function updateStats(): void { }
<!-- userStats in HTML ❌ -->
<div data-lc-fragment="userStats">
// Solution: Use consistent naming
#[Fragment('user-stats')]  // ✅ kebab-case
<div data-lc-fragment="user-stats">  <!-- ✅ Matches -->

Missing Fragment Attribute

// Problem: Fragment specified but no HTML marker
#[LiveAction]
#[Fragment('results')]
public function search(): void { }

// Template missing fragment marker
<div>
    <!--  No data-lc-fragment="results" -->
    <for items="results" as="result">
        <div>{result.title}</div>
    </for>
</div>

// Solution: Add fragment marker
<div data-lc-fragment="results">  <!--  Added -->
    <for items="results" as="result">
        <div>{result.title}</div>
    </for>
</div>

Focus Lost After Update

// Problem: Input focus lost during fragment update
// Solution: Framework preserves focus automatically
// If not working, check:

// 1. Is input inside fragment?
<div data-lc-fragment="search">
    <input type="text" id="search-input" />  
</div>

// 2. Does input have stable ID?
<input type="text" id="search-input" />   Has ID
<input type="text" />   No ID

Browser Compatibility

Symptoms

  • Works in Chrome but not Safari
  • Mobile browser issues
  • Older browser errors

Supported Browsers

  • Chrome/Edge: 90+
  • Firefox: 88+
  • Safari: 14+
  • Mobile Safari: 14+
  • Mobile Chrome: 90+

Common Issues

ES2020 Features Not Supported

// Problem: Older browser lacks ES2020
// Solution: Build with polyfills

// vite.config.js
export default defineConfig({
    build: {
        target: 'es2015',  // Broader compatibility
        polyfillModulePreload: true
    }
});

EventSource Not Supported

// Check SSE support
if (typeof EventSource === 'undefined') {
    console.warn('SSE not supported - real-time updates disabled');
    // Fallback to polling
}

iOS Safari Issues

// iOS Safari has stricter CORS requirements
// Ensure proper CORS headers

// PHP
header('Access-Control-Allow-Origin: https://yourdomain.com');
header('Access-Control-Allow-Credentials: true');

Debugging Tools

DevTools Panel

Enable DevTools:

LIVECOMPONENT_DEVTOOLS_ENABLED=true

Or via localStorage:

localStorage.setItem('livecomponent_devtools', 'true');
location.reload();

Features:

  • Component tree inspection
  • Action log with timing
  • Event log
  • Performance profiling
  • Network monitoring
  • DOM badges

Console Commands

// Get all components
LiveComponent.components

// Get specific component
const component = LiveComponent.getComponent('component-id');

// Inspect state
console.log(component.state);

// Execute action manually
LiveComponent.executeAction('component-id', 'actionName', {
    param: 'value'
});

// Refresh component
LiveComponent.refresh('component-id');

// Get performance metrics
LiveComponent.getMetrics('component-id');

Server-Side Debugging

// Log component state
$this->logger->debug('Component state', [
    'component' => static::class,
    'state' => $this->getState()
]);

// Performance tracking
$start = hrtime(true);
$result = $this->expensiveOperation();
$duration = (hrtime(true) - $start) / 1e6;

$this->logger->info('Operation completed', [
    'duration_ms' => $duration
]);

Network Debugging

F12 → Network Tab → Filter: livecomponent

Check:
- Request payloads
- Response times
- Error responses
- Batching efficiency

Getting Help

Before Asking for Help

  1. Check this guide for common solutions
  2. Enable DevTools and check console
  3. Reproduce in isolation (minimal example)
  4. Gather error messages (browser console + server logs)

Reporting Issues

Include:

  • Browser + version
  • PHP version
  • Framework version
  • Component code (minimal reproduction)
  • Error messages
  • Steps to reproduce

Resources


Next: FAQ