# LiveComponents Best Practices Production-ready patterns and best practices for building scalable, maintainable LiveComponents. ## Table of Contents - [Component Design](#component-design) - [State Management](#state-management) - [Performance Optimization](#performance-optimization) - [Security Best Practices](#security-best-practices) - [Testing Strategies](#testing-strategies) - [Error Handling](#error-handling) - [Accessibility](#accessibility) - [Production Deployment](#production-deployment) --- ## Component Design ### Single Responsibility Principle **✅ Good**: Component does one thing well ```php // Focused component final class SearchBox extends LiveComponent { #[LiveProp(writable: true)] public string $query = ''; #[LiveProp] public array $suggestions = []; #[LiveAction(debounce: 300)] public function search(): void { $this->suggestions = $this->searchService->getSuggestions($this->query); } } ``` **❌ Bad**: Component tries to do everything ```php // God component (anti-pattern) final class Dashboard extends LiveComponent { // User management #[LiveAction] public function createUser() { } #[LiveAction] public function deleteUser() { } // Analytics #[LiveAction] public function generateReport() { } // Settings #[LiveAction] public function updateSettings() { } // Notifications #[LiveAction] public function sendNotification() { } // ... 50 more actions } ``` **Solution**: Split into focused components ```php final class UserManagement extends LiveComponent { } final class AnalyticsDashboard extends LiveComponent { } final class SettingsPanel extends LiveComponent { } final class NotificationCenter extends LiveComponent { } ``` ### Component Composition **✅ Good**: Compose components from smaller pieces ```html
``` **Benefits**: - Reusable components - Easier testing - Better performance (independent updates) - Clearer separation of concerns ### Keep Components Stateless When Possible **✅ Good**: Stateless, reusable component ```php final class UserCard extends LiveComponent { #[LiveProp] public string $userId; #[LiveProp] public string $name; #[LiveProp] public string $avatar; // No mutable state, just display props } ``` **❌ Bad**: Unnecessary internal state ```php final class UserCard extends LiveComponent { #[LiveProp] public string $userId; #[LiveProp] private bool $isExpanded = false; // Could be CSS-only #[LiveProp] private int $hoverCount = 0; // Useless tracking } ``` ### Use Value Objects **✅ Good**: Type-safe with Value Objects ```php final class OrderForm extends LiveComponent { #[LiveProp] public Money $total; #[LiveProp] public Address $shippingAddress; #[LiveProp] public OrderStatus $status; } ``` **❌ Bad**: Primitive obsession ```php final class OrderForm extends LiveComponent { #[LiveProp] public float $total; // No currency info #[LiveProp] public array $shippingAddress; // Unstructured #[LiveProp] public string $status; // No validation } ``` --- ## State Management ### Minimize State **✅ Good**: Only essential state ```php final class ProductList extends LiveComponent { #[LiveProp] public array $filters = []; // User input #[LiveProp] public array $products = []; // Query results // Everything else computed from these two } ``` **❌ Bad**: Redundant state ```php final class ProductList extends LiveComponent { #[LiveProp] public array $filters = []; #[LiveProp] public array $products = []; #[LiveProp] public int $totalProducts; // Redundant: count($products) #[LiveProp] public bool $hasProducts; // Redundant: !empty($products) #[LiveProp] public array $productIds; // Redundant: array_column($products, 'id') } ``` ### Computed Properties for Derived State **✅ Good**: Compute derived values ```php final class ShoppingCart extends LiveComponent { #[LiveProp] public array $items = []; #[LiveProp(computed: true)] public function subtotal(): Money { return array_reduce( $this->items, fn($sum, $item) => $sum->add($item->price->multiply($item->quantity)), Money::zero() ); } #[LiveProp(computed: true)] public function itemCount(): int { return array_sum(array_column($this->items, 'quantity')); } } ``` ### Normalize Complex State **✅ Good**: Normalized state ```php final class MessageList extends LiveComponent { #[LiveProp] public array $messageIds = [1, 2, 3]; // Order preserved #[LiveProp] public array $messagesById = [ // Fast lookup 1 => ['id' => 1, 'text' => '...'], 2 => ['id' => 2, 'text' => '...'], 3 => ['id' => 3, 'text' => '...'], ]; #[LiveAction] public function deleteMessage(int $id): void { $this->messageIds = array_filter($this->messageIds, fn($msgId) => $msgId !== $id); unset($this->messagesById[$id]); } } ``` **❌ Bad**: Nested arrays ```php final class MessageList extends LiveComponent { #[LiveProp] public array $messages = [ ['id' => 1, 'text' => '...'], ['id' => 2, 'text' => '...'], ['id' => 3, 'text' => '...'], ]; #[LiveAction] public function deleteMessage(int $id): void { // Slow O(n) search $this->messages = array_filter( $this->messages, fn($msg) => $msg['id'] !== $id ); } } ``` ### Use Writable Props Sparingly **✅ Good**: Writable only for user input ```php final class ContactForm extends LiveComponent { #[LiveProp(writable: true)] public string $name = ''; #[LiveProp(writable: true)] public string $email = ''; #[LiveProp] // Read-only public bool $submitted = false; } ``` **❌ Bad**: Everything writable ```php final class ContactForm extends LiveComponent { #[LiveProp(writable: true)] public string $name = ''; #[LiveProp(writable: true)] public bool $submitted = false; // Client could fake this #[LiveProp(writable: true)] public string $userId; // Security risk! } ``` --- ## Performance Optimization ### Use Fragments for Large Components **✅ Good**: Fragment-based updates ```php final class Dashboard extends LiveComponent { #[LiveAction] #[Fragment('stats')] public function refreshStats(): void { // Only re-renders stats section $this->stats = $this->statsService->getLatest(); } #[LiveAction] #[Fragment('activity')] public function refreshActivity(): void { // Only re-renders activity feed $this->activities = $this->activityService->getRecent(); } } ``` **Performance Impact**: 70-90% reduction in DOM updates ### Debounce Expensive Actions **✅ Good**: Debounced search ```php final class SearchBox extends LiveComponent { #[LiveProp(writable: true)] public string $query = ''; #[LiveAction(debounce: 300)] public function search(): void { // Waits 300ms after user stops typing $this->results = $this->searchService->search($this->query); } } ``` **Performance Impact**: 80%+ reduction in API calls ### Cache Expensive Operations **✅ Good**: Cached expensive query ```php final class ReportViewer extends LiveComponent { #[LiveAction] #[Cached(ttl: 1800, tags: ['reports'])] public function generateReport(): void { // Expensive operation cached for 30 minutes $this->reportData = $this->reportService->generate($this->reportId); } } ``` ### Lazy Load Non-Critical Data **✅ Good**: Lazy loading ```php final class UserProfile extends LiveComponent { #[LiveProp] public string $userId; #[LiveProp(lazy: true)] public array $activityHistory = []; #[LiveAction] public function loadActivityHistory(): void { // Only loads when user requests it $this->activityHistory = $this->activityService->getForUser($this->userId); } } ``` ### Optimize Template Rendering **✅ Good**: Efficient template ```html
``` **❌ Bad**: Monolithic template ```html
``` ### Use Optimistic Updates for User Interactions **✅ Good**: Instant feedback ```php final class LikeButton extends LiveComponent { #[LiveAction] #[Optimistic(updateProps: ['likes', 'isLiked'])] public function toggleLike(): void { // Client updates UI immediately (<50ms) // Server confirms asynchronously $this->isLiked = !$this->isLiked; $this->likes += $this->isLiked ? 1 : -1; } } ``` --- ## Security Best Practices ### Rate Limit All Public Actions **✅ Good**: Protected endpoints ```php final class ContactForm extends LiveComponent { #[LiveAction] #[RateLimit(requests: 3, window: 3600, key: 'ip')] public function submitForm(array $data): void { // Max 3 submissions per hour per IP $this->emailService->send($data); } } ``` ### Authorize Sensitive Actions **✅ Good**: Protected actions ```php final class UserManagement extends LiveComponent { #[LiveAction] #[Authorize(roles: ['admin'])] #[RateLimit(requests: 10, window: 60, key: 'user')] public function deleteUser(string $userId): void { // Only admins can delete, max 10 per minute $this->userService->delete($userId); } } ``` ### Use Idempotency for Critical Actions **✅ Good**: Idempotent operations ```php final class PaymentForm extends LiveComponent { #[LiveAction] #[Idempotent(ttl: 86400)] #[Authorize(permissions: ['payments.process'])] public function processPayment(string $idempotencyKey, array $paymentData): void { // Duplicate requests return cached result // Prevents double-charging $this->paymentService->charge($paymentData); } } ``` ### Validate All Input **✅ Good**: Server-side validation ```php final class RegistrationForm extends LiveComponent { #[LiveAction] #[Validated(rules: [ 'email' => ['required', 'email', 'unique:users'], 'password' => ['required', 'min:8', 'confirmed'], ])] #[RateLimit(requests: 5, window: 3600, key: 'ip')] public function register(string $email, string $password): void { // Input validated before execution $this->authService->register($email, $password); } } ``` ### Encrypt Sensitive Data **✅ Good**: Encrypted sensitive props ```php final class PaymentForm extends LiveComponent { #[LiveProp] #[Encrypted] public string $creditCardNumber = ''; #[LiveProp] #[Encrypted] public string $cvv = ''; #[LiveAction] #[Idempotent] #[Authorize(permissions: ['payments.process'])] public function processPayment(): void { // Properties automatically decrypted server-side $this->paymentService->charge($this->creditCardNumber, $this->cvv); } } ``` ### Never Trust Client State **✅ Good**: Validate server-side ```php final class CheckoutForm extends LiveComponent { #[LiveProp(writable: true)] public array $cartItems = []; #[LiveAction] public function checkout(): void { // ALWAYS recalculate totals server-side $total = $this->cartService->calculateTotal($this->cartItems); // Don't trust client-provided total $this->paymentService->charge($total); } } ``` **❌ Bad**: Trust client state ```php final class CheckoutForm extends LiveComponent { #[LiveProp(writable: true)] public float $total; // Client could modify this! #[LiveAction] public function checkout(): void { // Dangerous: charging client-provided amount $this->paymentService->charge($this->total); } } ``` --- ## Testing Strategies ### Unit Test Component Logic **✅ Good**: Test business logic ```php it('calculates cart total correctly', function () { $cart = new ShoppingCart(); $cart->items = [ new CartItem(price: Money::fromCents(1000), quantity: 2), new CartItem(price: Money::fromCents(500), quantity: 1), ]; expect($cart->subtotal()->toCents())->toBe(2500); }); ``` ### Integration Test Actions **✅ Good**: Test action execution ```php it('adds item to cart', function () { $cart = new ShoppingCart(); $cart->addItem(productId: '123', quantity: 2); expect($cart->items)->toHaveCount(1); expect($cart->items[0]->productId)->toBe('123'); expect($cart->items[0]->quantity)->toBe(2); }); ``` ### E2E Test User Workflows **✅ Good**: Test complete flows ```javascript test('user can complete checkout', async ({ page }) => { // Add product to cart await page.goto('/products/123'); await page.click('[data-lc-action="addToCart"]'); // Go to cart await page.goto('/cart'); await expect(page.locator('.cart-item')).toHaveCount(1); // Proceed to checkout await page.click('[data-lc-action="checkout"]'); // Fill payment form await page.fill('[name="cardNumber"]', '4242424242424242'); await page.fill('[name="cvv"]', '123'); // Submit await page.click('[data-lc-action="processPayment"]'); // Verify success await expect(page).toHaveURL('/order/confirmation'); }); ``` ### Test Fragment Updates **✅ Good**: Verify partial rendering ```javascript test('updates only search results fragment', async ({ page }) => { const headerHtml = await page.locator('[data-lc-fragment="header"]').innerHTML(); await page.fill('[data-lc-model="searchQuery"]', 'test'); await page.click('[data-lc-action="search"]'); await page.waitForResponse(r => r.url().includes('/livecomponent')); // Header unchanged const newHeaderHtml = await page.locator('[data-lc-fragment="header"]').innerHTML(); expect(newHeaderHtml).toBe(headerHtml); // Results updated await expect(page.locator('[data-lc-fragment="results"] .result-item')).toHaveCount(5); }); ``` ### Test Error Handling **✅ Good**: Test failure scenarios ```php it('handles validation errors', function () { $form = new RegistrationForm(); expect(fn() => $form->register('invalid-email', 'short')) ->toThrow(ValidationException::class); expect($form->errors)->toHaveKey('email'); expect($form->errors)->toHaveKey('password'); }); ``` --- ## Error Handling ### Provide Clear Error Messages **✅ Good**: User-friendly errors ```php final class PaymentForm extends LiveComponent { #[LiveAction] public function processPayment(array $paymentData): void { try { $this->paymentService->charge($paymentData); $this->success = true; } catch (InsufficientFundsException $e) { $this->error = 'Insufficient funds. Please try a different payment method.'; } catch (CardDeclinedException $e) { $this->error = 'Your card was declined. Please contact your bank.'; } catch (PaymentGatewayException $e) { $this->error = 'Payment processing is temporarily unavailable. Please try again later.'; $this->logger->error('Payment gateway error', ['exception' => $e]); } } } ``` **❌ Bad**: Technical error messages ```php catch (\Exception $e) { $this->error = $e->getMessage(); // "SQLSTATE[HY000]..." } ``` ### Handle Network Failures Gracefully **✅ Good**: Retry with backoff ```javascript window.addEventListener('livecomponent:request-failed', async (event) => { const { componentId, requestId, error } = event.detail; if (error.status === 0) { // Network error - retry with exponential backoff let delay = 1000; for (let i = 0; i < 3; i++) { await new Promise(resolve => setTimeout(resolve, delay)); try { await retryRequest(componentId, requestId); break; // Success } catch (e) { delay *= 2; } } } }); ``` ### Validate Input Early **✅ Good**: Client-side + server-side validation ```html
``` ```php #[LiveAction] #[Validated(rules: [ 'email' => ['required', 'email', 'unique:users'], 'password' => ['required', 'min:8'], ])] public function register(string $email, string $password): void { // Server-side validation happens first $this->authService->register($email, $password); } ``` --- ## Accessibility ### Use Semantic HTML **✅ Good**: Semantic markup ```html ``` **❌ Bad**: Divs everywhere ```html
Home
About
``` ### Provide ARIA Labels **✅ Good**: Accessible labels ```html
Loading...
``` ### Announce Dynamic Updates **✅ Good**: Screen reader announcements ```html
{{ successMessage }}
{{ errorMessage }}
``` ### Keyboard Navigation **✅ Good**: Keyboard accessible ```html
``` --- ## Production Deployment ### Environment Configuration **✅ Production `.env`**: ```env # LiveComponents Configuration LIVECOMPONENT_ENABLED=true LIVECOMPONENT_CSRF_PROTECTION=true LIVECOMPONENT_RATE_LIMIT=60 LIVECOMPONENT_DEVTOOLS_ENABLED=false # Security LIVECOMPONENT_ENCRYPTION_KEY=your-256-bit-encryption-key-here CSRF_TOKEN_LENGTH=32 # Performance LIVECOMPONENT_BATCH_SIZE=10 LIVECOMPONENT_BATCH_DEBOUNCE=50 LIVECOMPONENT_CACHE_ENABLED=true # SSE LIVECOMPONENT_SSE_ENABLED=true LIVECOMPONENT_SSE_HEARTBEAT_INTERVAL=30 ``` ### Production Checklist **Before Deploying**: - [ ] Generate new `LIVECOMPONENT_ENCRYPTION_KEY` - [ ] Disable DevTools (`LIVECOMPONENT_DEVTOOLS_ENABLED=false`) - [ ] Enable CSRF protection - [ ] Configure rate limits for all public actions - [ ] Test all critical user flows - [ ] Run security audit - [ ] Test with real-world data volumes - [ ] Configure caching - [ ] Set up error monitoring - [ ] Test SSE connections (HTTPS required) - [ ] Verify fragment updates work correctly - [ ] Test file uploads with large files - [ ] Test error handling and recovery - [ ] Validate accessibility (WCAG 2.1 AA) ### Monitor Performance **✅ Good**: Track metrics ```javascript window.addEventListener('livecomponent:action-executed', (event) => { const { action, duration } = event.detail; // Send to analytics analytics.track('livecomponent_performance', { action: action, duration: duration, p95: calculateP95(action, duration), p99: calculateP99(action, duration) }); // Alert on slow actions if (duration > 1000) { errorReporter.captureMessage(`Slow action: ${action} (${duration}ms)`); } }); ``` ### Error Tracking **✅ Good**: Comprehensive error tracking ```javascript window.addEventListener('livecomponent:error', (event) => { const { componentId, error, context } = event.detail; errorReporter.captureException(error, { tags: { component: componentId, context: context }, extra: { props: LiveComponent.getComponent(componentId)?.props, user_agent: navigator.userAgent } }); }); ``` ### Performance Budgets **Set and Monitor**: - Action latency: p95 < 100ms, p99 < 250ms - Fragment updates: < 50ms - Network efficiency: > 80% (via batching) - Memory usage: < 2MB per component - Bundle size: < 50KB (gzipped) --- ## Summary: The Golden Rules 1. **Keep components focused** - Single responsibility 2. **Minimize state** - Compute derived values 3. **Use fragments** - For large components (>5KB) 4. **Debounce expensive actions** - Search, API calls 5. **Cache when possible** - Expensive queries 6. **Rate limit everything** - Public endpoints 7. **Authorize sensitive actions** - Admin operations 8. **Validate all input** - Client + server 9. **Encrypt sensitive data** - PII, financial 10. **Test thoroughly** - Unit, integration, E2E 11. **Handle errors gracefully** - User-friendly messages 12. **Make it accessible** - WCAG 2.1 AA 13. **Monitor production** - Performance + errors 14. **Use optimistic updates** - Instant feedback 15. **Document your code** - Future you will thank you --- For more details, see: - [Performance Guide](performance-guide.md) - [Security Guide](security-guide.md) - [Testing Guide](../testing/README.md) - [API Reference](api-reference.md)