- 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.
1015 lines
23 KiB
Markdown
1015 lines
23 KiB
Markdown
# 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
|
|
<div data-lc-component="Dashboard">
|
|
<include template="components/header" />
|
|
|
|
<div class="grid">
|
|
<include template="components/user-stats" data-lc-component="UserStats" />
|
|
<include template="components/activity-feed" data-lc-component="ActivityFeed" />
|
|
<include template="components/notifications" data-lc-component="Notifications" />
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**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
|
|
<!-- Use fragments for independent sections -->
|
|
<div data-lc-component="ProductList">
|
|
<div data-lc-fragment="filters">
|
|
<!-- Filter UI -->
|
|
</div>
|
|
|
|
<div data-lc-fragment="products">
|
|
<!-- Only this updates when products change -->
|
|
<for items="products" as="product">
|
|
<include template="components/product-card" data="{{ product }}" />
|
|
</for>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**❌ Bad**: Monolithic template
|
|
```html
|
|
<!-- Everything re-renders on any change -->
|
|
<div data-lc-component="ProductList">
|
|
<!-- 10KB of HTML -->
|
|
<div><!-- Filters --></div>
|
|
<div><!-- Products --></div>
|
|
<div><!-- Sidebar --></div>
|
|
<div><!-- Footer --></div>
|
|
</div>
|
|
```
|
|
|
|
### 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
|
|
<form data-lc-action="register">
|
|
<input
|
|
type="email"
|
|
name="email"
|
|
required
|
|
pattern="[^@]+@[^@]+\.[^@]+"
|
|
data-lc-model="email"
|
|
/>
|
|
|
|
<input
|
|
type="password"
|
|
name="password"
|
|
required
|
|
minlength="8"
|
|
data-lc-model="password"
|
|
/>
|
|
|
|
<button type="submit">Register</button>
|
|
</form>
|
|
```
|
|
|
|
```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
|
|
<nav aria-label="Main navigation" data-lc-component="Navigation">
|
|
<ul role="list">
|
|
<for items="menuItems" as="item">
|
|
<li>
|
|
<a
|
|
href="{{ item.url }}"
|
|
aria-current="{{ item.isActive ? 'page' : null }}"
|
|
>
|
|
{{ item.label }}
|
|
</a>
|
|
</li>
|
|
</for>
|
|
</ul>
|
|
</nav>
|
|
```
|
|
|
|
**❌ Bad**: Divs everywhere
|
|
```html
|
|
<div data-lc-component="Navigation">
|
|
<div onclick="navigate()">Home</div>
|
|
<div onclick="navigate()">About</div>
|
|
</div>
|
|
```
|
|
|
|
### Provide ARIA Labels
|
|
|
|
**✅ Good**: Accessible labels
|
|
```html
|
|
<button
|
|
data-lc-action="deleteItem"
|
|
data-lc-params='{"id": "{{ item.id }}"}'
|
|
aria-label="Delete {{ item.name }}"
|
|
>
|
|
✕
|
|
</button>
|
|
|
|
<div
|
|
data-lc-fragment="loading"
|
|
role="status"
|
|
aria-live="polite"
|
|
aria-busy="{{ isLoading }}"
|
|
>
|
|
<if condition="isLoading">
|
|
Loading...
|
|
</if>
|
|
</div>
|
|
```
|
|
|
|
### Announce Dynamic Updates
|
|
|
|
**✅ Good**: Screen reader announcements
|
|
```html
|
|
<div role="status" aria-live="polite" aria-atomic="true">
|
|
<if condition="successMessage">
|
|
{{ successMessage }}
|
|
</if>
|
|
</div>
|
|
|
|
<div role="alert" aria-live="assertive">
|
|
<if condition="errorMessage">
|
|
{{ errorMessage }}
|
|
</if>
|
|
</div>
|
|
```
|
|
|
|
### Keyboard Navigation
|
|
|
|
**✅ Good**: Keyboard accessible
|
|
```html
|
|
<div
|
|
data-lc-component="Dropdown"
|
|
role="combobox"
|
|
aria-expanded="{{ isOpen }}"
|
|
aria-controls="dropdown-list"
|
|
>
|
|
<button
|
|
data-lc-action="toggle"
|
|
aria-haspopup="listbox"
|
|
onkeydown="handleKeyDown(event)"
|
|
>
|
|
{{ selectedOption }}
|
|
</button>
|
|
|
|
<ul
|
|
id="dropdown-list"
|
|
role="listbox"
|
|
aria-label="Options"
|
|
style="display: {{ isOpen ? 'block' : 'none' }}"
|
|
>
|
|
<for items="options" as="option">
|
|
<li
|
|
role="option"
|
|
tabindex="0"
|
|
data-lc-action="selectOption"
|
|
data-lc-params='{"value": "{{ option.value }}"}'
|
|
onkeydown="handleOptionKeyDown(event)"
|
|
>
|
|
{{ option.label }}
|
|
</li>
|
|
</for>
|
|
</ul>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 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)
|