The /health route returns JSON status for Docker Swarm to verify container health during deployment. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
592 lines
15 KiB
Markdown
592 lines
15 KiB
Markdown
# Cloudflare Workers Roadmap for Mylder
|
|
|
|
## Overview
|
|
Edge computing strategy for Mylder platform using Cloudflare Workers, R2, D1, and KV.
|
|
|
|
**Philosophy:** Move compute closer to users, reduce VPS load, improve UX with sub-100ms responses.
|
|
|
|
## Current Architecture vs Edge-First
|
|
|
|
### Current (VPS-Centric)
|
|
```
|
|
User → Cloudflare CDN → VPS (149.102.155.84)
|
|
├── Next.js (Frontend)
|
|
├── Supabase (Auth, DB)
|
|
├── n8n (Automation)
|
|
└── Dokploy (Orchestration)
|
|
```
|
|
|
|
### Future (Edge-First)
|
|
```
|
|
User → Cloudflare Workers (Edge Logic)
|
|
├── Static Assets (CDN)
|
|
├── Edge Auth (Workers + KV)
|
|
├── Edge API (Workers + D1)
|
|
└── Origin (VPS for heavy compute)
|
|
├── Supabase (Primary DB)
|
|
└── n8n (Automation)
|
|
```
|
|
|
|
## Workers Use Cases
|
|
|
|
### 1. Edge Authentication (Priority: High)
|
|
|
|
**Problem:** Every auth check hits VPS → Supabase (100-300ms)
|
|
**Solution:** Cache JWT validation at edge
|
|
|
|
```typescript
|
|
// workers/auth-edge.ts
|
|
export default {
|
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
const token = request.headers.get('Authorization')?.split('Bearer ')[1];
|
|
if (!token) return new Response('Unauthorized', { status: 401 });
|
|
|
|
// Check KV cache for user session
|
|
const cached = await env.SESSIONS.get(token);
|
|
if (cached) {
|
|
return new Response(cached, {
|
|
headers: { 'X-Auth-Source': 'edge-cache' }
|
|
});
|
|
}
|
|
|
|
// Fallback to Supabase origin
|
|
const response = await fetch('https://supabase.mylder.io/auth/v1/user', {
|
|
headers: { Authorization: `Bearer ${token}` }
|
|
});
|
|
|
|
if (response.ok) {
|
|
const user = await response.text();
|
|
await env.SESSIONS.put(token, user, { expirationTtl: 3600 }); // 1 hour
|
|
return new Response(user, {
|
|
headers: { 'X-Auth-Source': 'origin' }
|
|
});
|
|
}
|
|
|
|
return response;
|
|
}
|
|
};
|
|
```
|
|
|
|
**Benefits:**
|
|
- Auth checks: 300ms → 10ms (30x faster)
|
|
- Reduced Supabase load
|
|
- Better UX for logged-in users
|
|
|
|
**Trade-offs:**
|
|
- Session invalidation complexity
|
|
- KV storage costs ($0.50/1M reads)
|
|
|
|
### 2. API Rate Limiting (Priority: High)
|
|
|
|
**Problem:** DDoS/abuse protection on VPS is resource-intensive
|
|
**Solution:** Rate limit at edge before hitting origin
|
|
|
|
```typescript
|
|
// workers/rate-limiter.ts
|
|
import { RateLimiter } from '@cloudflare/workers-rate-limiter';
|
|
|
|
export default {
|
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
const limiter = new RateLimiter({
|
|
namespace: env.RATE_LIMIT,
|
|
// 100 requests per 60 seconds per IP
|
|
limit: 100,
|
|
period: 60,
|
|
});
|
|
|
|
const ip = request.headers.get('CF-Connecting-IP') || 'unknown';
|
|
const { success } = await limiter.limit({ key: ip });
|
|
|
|
if (!success) {
|
|
return new Response('Rate limit exceeded', {
|
|
status: 429,
|
|
headers: { 'Retry-After': '60' }
|
|
});
|
|
}
|
|
|
|
// Forward to origin
|
|
return fetch(request);
|
|
}
|
|
};
|
|
```
|
|
|
|
**Benefits:**
|
|
- Block abuse before it hits VPS
|
|
- Per-IP, per-user, per-endpoint limits
|
|
- 0ms latency overhead
|
|
|
|
**Use Cases:**
|
|
- `/api/*` endpoints
|
|
- Login attempts
|
|
- Form submissions
|
|
- Search queries
|
|
|
|
### 3. A/B Testing at Edge (Priority: Medium)
|
|
|
|
**Problem:** Client-side A/B testing causes layout shift (poor CLS)
|
|
**Solution:** Server-side rendering with edge variants
|
|
|
|
```typescript
|
|
// workers/ab-test.ts
|
|
export default {
|
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
const url = new URL(request.url);
|
|
|
|
// Check existing variant
|
|
let variant = request.headers.get('Cookie')?.match(/variant=([AB])/)?.[1];
|
|
|
|
if (!variant) {
|
|
// Assign 50/50 split
|
|
variant = Math.random() < 0.5 ? 'A' : 'B';
|
|
}
|
|
|
|
// Rewrite request to origin with variant
|
|
const originUrl = `${url.origin}${url.pathname}?variant=${variant}`;
|
|
const response = await fetch(originUrl, request);
|
|
|
|
// Set variant cookie
|
|
const headers = new Headers(response.headers);
|
|
headers.set('Set-Cookie', `variant=${variant}; Path=/; Max-Age=2592000`);
|
|
|
|
return new Response(response.body, {
|
|
status: response.status,
|
|
headers
|
|
});
|
|
}
|
|
};
|
|
```
|
|
|
|
**Benefits:**
|
|
- No layout shift (CLS = 0)
|
|
- Instant variant assignment
|
|
- Analytics at edge
|
|
|
|
**Use Cases:**
|
|
- Landing page variants
|
|
- Pricing page tests
|
|
- Feature flags
|
|
|
|
### 4. Edge Personalization (Priority: Medium)
|
|
|
|
**Problem:** Generic content for all users (low engagement)
|
|
**Solution:** Geo/device-specific content at edge
|
|
|
|
```typescript
|
|
// workers/personalize.ts
|
|
export default {
|
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
const country = request.cf?.country as string;
|
|
const device = request.headers.get('User-Agent')?.includes('Mobile')
|
|
? 'mobile'
|
|
: 'desktop';
|
|
|
|
// Fetch base HTML from origin
|
|
const response = await fetch(request);
|
|
const html = await response.text();
|
|
|
|
// Inject personalized content
|
|
const personalized = html
|
|
.replace('{{COUNTRY}}', country)
|
|
.replace('{{DEVICE}}', device)
|
|
.replace('{{CURRENCY}}', getCurrency(country));
|
|
|
|
return new Response(personalized, {
|
|
headers: {
|
|
'Content-Type': 'text/html',
|
|
'Cache-Control': 'public, max-age=60, s-maxage=3600',
|
|
'Vary': 'CF-IPCountry, User-Agent'
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
function getCurrency(country: string): string {
|
|
const map: Record<string, string> = {
|
|
US: 'USD', GB: 'GBP', EU: 'EUR', DK: 'DKK'
|
|
};
|
|
return map[country] || 'USD';
|
|
}
|
|
```
|
|
|
|
**Benefits:**
|
|
- Geo-specific content (currency, language)
|
|
- Device-optimized markup
|
|
- Sub-50ms personalization
|
|
|
|
**Use Cases:**
|
|
- Pricing display
|
|
- Content localization
|
|
- Feature availability
|
|
|
|
### 5. Image Optimization & Resizing (Priority: Low)
|
|
|
|
**Problem:** Next.js Image Optimization on VPS is CPU-intensive
|
|
**Solution:** Cloudflare Images or Workers + R2
|
|
|
|
```typescript
|
|
// workers/image-optimize.ts
|
|
export default {
|
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
const url = new URL(request.url);
|
|
const imagePath = url.pathname.replace('/img/', '');
|
|
|
|
// Parse resize params
|
|
const width = url.searchParams.get('w') || '1920';
|
|
const quality = url.searchParams.get('q') || '80';
|
|
const format = url.searchParams.get('f') || 'webp';
|
|
|
|
// Fetch from R2
|
|
const object = await env.IMAGES.get(imagePath);
|
|
if (!object) return new Response('Not Found', { status: 404 });
|
|
|
|
// Resize at edge (requires paid Workers plan)
|
|
const resized = await fetch(`https://mylder.io/cdn-cgi/image/width=${width},quality=${quality},format=${format}/${imagePath}`);
|
|
|
|
return new Response(resized.body, {
|
|
headers: {
|
|
'Content-Type': `image/${format}`,
|
|
'Cache-Control': 'public, max-age=31536000, immutable'
|
|
}
|
|
});
|
|
}
|
|
};
|
|
```
|
|
|
|
**Benefits:**
|
|
- Offload VPS CPU
|
|
- Auto WebP/AVIF conversion
|
|
- Responsive images on-demand
|
|
|
|
**Trade-offs:**
|
|
- Requires Cloudflare Images ($5/month + $1/100k)
|
|
- Or R2 storage ($0.015/GB/month)
|
|
|
|
### 6. Edge API Caching (Priority: Medium)
|
|
|
|
**Problem:** Repeated API calls for same data
|
|
**Solution:** Smart caching with stale-while-revalidate
|
|
|
|
```typescript
|
|
// workers/api-cache.ts
|
|
export default {
|
|
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
const cache = caches.default;
|
|
const cacheKey = new Request(request.url, request);
|
|
|
|
// Check cache
|
|
let response = await cache.match(cacheKey);
|
|
if (response) {
|
|
const age = Date.now() - new Date(response.headers.get('Date')!).getTime();
|
|
|
|
// If stale (>60s), revalidate in background
|
|
if (age > 60000) {
|
|
ctx.waitUntil(revalidate(request, cache, cacheKey));
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
// Fetch from origin
|
|
response = await fetch(request);
|
|
|
|
// Cache for 5 minutes
|
|
const cached = new Response(response.body, response);
|
|
cached.headers.set('Cache-Control', 'public, max-age=300, stale-while-revalidate=3600');
|
|
ctx.waitUntil(cache.put(cacheKey, cached));
|
|
|
|
return response;
|
|
}
|
|
};
|
|
|
|
async function revalidate(request: Request, cache: Cache, key: Request) {
|
|
const fresh = await fetch(request);
|
|
await cache.put(key, fresh.clone());
|
|
}
|
|
```
|
|
|
|
**Benefits:**
|
|
- API responses: 200ms → 10ms
|
|
- Stale content served instantly
|
|
- Background revalidation
|
|
|
|
**Use Cases:**
|
|
- Public API endpoints
|
|
- User profiles
|
|
- Content feeds
|
|
|
|
## Cloudflare R2 (Object Storage)
|
|
|
|
### Use Cases for R2
|
|
|
|
**What:** S3-compatible object storage at edge (no egress fees)
|
|
|
|
#### 1. User Uploads
|
|
```typescript
|
|
// Store user avatars, documents
|
|
await env.UPLOADS.put(`users/${userId}/avatar.jpg`, file);
|
|
```
|
|
|
|
**Benefits:**
|
|
- $0.015/GB/month (10x cheaper than S3)
|
|
- No egress fees (free downloads)
|
|
- Global CDN distribution
|
|
|
|
**When to Use:**
|
|
- User-generated content (avatars, documents)
|
|
- Static assets (fonts, icons)
|
|
- Media files (videos, audio)
|
|
|
|
**When NOT to Use:**
|
|
- Frequent small writes (use D1 instead)
|
|
- Sub-10MB total storage (use KV instead)
|
|
|
|
#### 2. Static Asset Hosting
|
|
```typescript
|
|
// Move _next/static/* to R2
|
|
const asset = await env.STATIC_ASSETS.get(path);
|
|
return new Response(asset.body, {
|
|
headers: { 'Cache-Control': 'public, max-age=31536000, immutable' }
|
|
});
|
|
```
|
|
|
|
**Benefits:**
|
|
- Reduce VPS storage needs
|
|
- Faster global distribution
|
|
- Immutable content = perfect cache
|
|
|
|
#### 3. Backup Storage
|
|
```typescript
|
|
// Daily Supabase backups to R2
|
|
await env.BACKUPS.put(`supabase-${date}.sql.gz`, backup);
|
|
```
|
|
|
|
## Cloudflare D1 (Edge Database)
|
|
|
|
### Use Cases for D1
|
|
|
|
**What:** SQLite at edge (sub-10ms queries globally)
|
|
|
|
#### 1. Edge Metadata
|
|
```typescript
|
|
// Store user preferences, feature flags
|
|
const db = env.DB;
|
|
const result = await db.prepare(
|
|
'SELECT theme, locale FROM user_prefs WHERE user_id = ?'
|
|
).bind(userId).first();
|
|
```
|
|
|
|
**Benefits:**
|
|
- Read latency: 100ms (Supabase) → 5ms (D1)
|
|
- No VPS load for reads
|
|
- SQL interface (familiar)
|
|
|
|
**When to Use:**
|
|
- Read-heavy data (user settings, configs)
|
|
- Small datasets (<1GB)
|
|
- Geo-distributed reads
|
|
|
|
**When NOT to Use:**
|
|
- Write-heavy workloads (D1 eventual consistency)
|
|
- Complex joins (use Supabase instead)
|
|
- Primary data storage (Supabase is source of truth)
|
|
|
|
#### 2. Analytics/Metrics
|
|
```typescript
|
|
// Store page views, click events
|
|
await db.prepare(
|
|
'INSERT INTO analytics (page, user_id, timestamp) VALUES (?, ?, ?)'
|
|
).bind(page, userId, Date.now()).run();
|
|
```
|
|
|
|
**Benefits:**
|
|
- High-volume writes
|
|
- Real-time aggregations
|
|
- No Supabase quota impact
|
|
|
|
#### 3. Feature Flags
|
|
```typescript
|
|
// Enable/disable features per user
|
|
const flags = await db.prepare(
|
|
'SELECT * FROM feature_flags WHERE active = 1'
|
|
).all();
|
|
```
|
|
|
|
### D1 vs Supabase Decision Matrix
|
|
|
|
| Feature | D1 (Edge) | Supabase (VPS) |
|
|
|---------|-----------|----------------|
|
|
| Read Latency | 5-10ms | 100-300ms |
|
|
| Write Latency | 50-100ms | 100-300ms |
|
|
| Consistency | Eventual | Strong |
|
|
| Storage Limit | 1GB | 8GB+ |
|
|
| Cost | Free (5M reads/day) | Included in VPS |
|
|
| Use Case | Cached metadata | Primary data |
|
|
|
|
**Strategy:** D1 = edge cache, Supabase = source of truth
|
|
|
|
## Cloudflare KV (Key-Value Store)
|
|
|
|
### Use Cases for KV
|
|
|
|
**What:** Global key-value store (sub-1ms reads)
|
|
|
|
#### 1. Session Storage
|
|
```typescript
|
|
await env.SESSIONS.put(sessionId, userData, { expirationTtl: 3600 });
|
|
```
|
|
|
|
**Benefits:**
|
|
- Fastest storage option (<1ms reads)
|
|
- Auto-expiration (TTL)
|
|
- Global replication
|
|
|
|
**Cost:** $0.50/1M reads, $5/1M writes (generous free tier)
|
|
|
|
#### 2. Configuration/Secrets
|
|
```typescript
|
|
const apiKey = await env.CONFIG.get('stripe_public_key');
|
|
```
|
|
|
|
#### 3. Rate Limit Counters
|
|
```typescript
|
|
const count = await env.RATE_LIMIT.get(ip);
|
|
await env.RATE_LIMIT.put(ip, count + 1, { expirationTtl: 60 });
|
|
```
|
|
|
|
## Implementation Roadmap
|
|
|
|
### Phase 1: Foundation (Month 1)
|
|
**Goal:** Basic edge infrastructure
|
|
|
|
- [ ] Set up Cloudflare Workers project
|
|
- [ ] Deploy edge auth validator (Worker + KV)
|
|
- [ ] Implement rate limiting for `/api/*`
|
|
- [ ] Migrate static assets to R2
|
|
- [ ] Monitor performance impact
|
|
|
|
**Success Metrics:**
|
|
- Auth latency: <20ms (p95)
|
|
- Static asset cache hit rate: >95%
|
|
- VPS CPU load: -20%
|
|
|
|
### Phase 2: Optimization (Month 2-3)
|
|
**Goal:** Performance wins
|
|
|
|
- [ ] Deploy A/B testing worker
|
|
- [ ] Implement edge personalization
|
|
- [ ] Set up D1 for user preferences
|
|
- [ ] Create stale-while-revalidate API cache
|
|
- [ ] Image optimization via R2 + Workers
|
|
|
|
**Success Metrics:**
|
|
- LCP: <1.5s (p75)
|
|
- API latency: -50% for cached endpoints
|
|
- Global availability: 99.9%
|
|
|
|
### Phase 3: Advanced (Month 4+)
|
|
**Goal:** Edge-first architecture
|
|
|
|
- [ ] Move session management to edge (D1 + KV)
|
|
- [ ] Implement edge analytics (D1)
|
|
- [ ] Deploy feature flags system (D1)
|
|
- [ ] Create edge API gateway
|
|
- [ ] Multi-region failover
|
|
|
|
**Success Metrics:**
|
|
- Edge request ratio: >70%
|
|
- Origin load: -60%
|
|
- Global latency: <100ms (p95)
|
|
|
|
## Cost Analysis
|
|
|
|
### Free Tier Limits
|
|
- **Workers:** 100k requests/day
|
|
- **KV:** 100k reads/day, 1k writes/day
|
|
- **R2:** 10GB storage, 1M reads/month
|
|
- **D1:** 5M reads/day, 100k writes/day
|
|
|
|
### Paid Costs (Estimated for 1M users/month)
|
|
|
|
| Service | Usage | Cost/Month |
|
|
|---------|-------|------------|
|
|
| Workers | 100M requests | $5 (bundled) |
|
|
| KV | 50M reads | $25 |
|
|
| R2 | 100GB storage + 500M reads | $1.50 |
|
|
| D1 | Included in Workers | $0 |
|
|
| **Total** | | **~$30/month** |
|
|
|
|
**ROI:** Potential to downgrade VPS ($30/month savings) → break even
|
|
|
|
## Anti-Patterns to Avoid
|
|
|
|
### 1. Over-Caching
|
|
**Bad:** Cache everything at edge for 24 hours
|
|
**Good:** Cache static assets long, dynamic content short
|
|
|
|
### 2. Edge as Primary DB
|
|
**Bad:** Store all user data in D1
|
|
**Good:** D1 = cache, Supabase = source of truth
|
|
|
|
### 3. Synchronous Edge-Origin Calls
|
|
**Bad:** Worker → fetch origin → wait → respond
|
|
**Good:** Serve stale, revalidate in background
|
|
|
|
### 4. Ignoring Cold Starts
|
|
**Bad:** Assume Workers are instant
|
|
**Good:** Optimize bundle size (<1MB), minimize dependencies
|
|
|
|
### 5. Complex Business Logic at Edge
|
|
**Bad:** Move entire Next.js app to Workers
|
|
**Good:** Keep heavy compute on VPS, edge for lightweight tasks
|
|
|
|
## Monitoring & Debugging
|
|
|
|
### Workers Analytics
|
|
**Dashboard:** Workers & Pages → Analytics
|
|
|
|
- Request volume
|
|
- Success rate (2xx/3xx/4xx/5xx)
|
|
- CPU time (p50, p95, p99)
|
|
- Error logs
|
|
|
|
### Tail Workers (Real-Time Logs)
|
|
```bash
|
|
wrangler tail mylder-auth-edge
|
|
```
|
|
|
|
### Custom Metrics
|
|
```typescript
|
|
// workers/metrics.ts
|
|
export default {
|
|
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
const start = Date.now();
|
|
const response = await fetch(request);
|
|
const duration = Date.now() - start;
|
|
|
|
// Log to D1 analytics
|
|
ctx.waitUntil(
|
|
env.ANALYTICS.prepare(
|
|
'INSERT INTO metrics (endpoint, duration, status) VALUES (?, ?, ?)'
|
|
).bind(request.url, duration, response.status).run()
|
|
);
|
|
|
|
return response;
|
|
}
|
|
};
|
|
```
|
|
|
|
## Next Steps
|
|
|
|
1. **Prototype:** Deploy simple edge auth worker (1 day)
|
|
2. **Measure:** Compare edge vs origin performance (1 week)
|
|
3. **Iterate:** Expand to rate limiting, A/B testing (1 month)
|
|
4. **Optimize:** Move 70%+ traffic to edge (3 months)
|
|
|
|
## References
|
|
|
|
- [Cloudflare Workers Docs](https://developers.cloudflare.com/workers/)
|
|
- [R2 Storage](https://developers.cloudflare.com/r2/)
|
|
- [D1 Database](https://developers.cloudflare.com/d1/)
|
|
- [Workers Examples](https://developers.cloudflare.com/workers/examples/)
|
|
- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/)
|