# 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