14 KiB
Architecture: [Project Name]
Deployment Platform: Cloudflare Workers Frontend: Vite + React + Tailwind v4 + shadcn/ui Backend: Hono (API routes on same Worker) Database: Cloudflare D1 (SQLite) Storage: Cloudflare R2 (object storage) Auth: Clerk (JWT-based) Last Updated: [Date]
System Overview
┌──────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ React App (Vite build) │ │
│ │ - Components (shadcn/ui + custom) │ │
│ │ - State (TanStack Query + Zustand) │ │
│ │ - Forms (React Hook Form + Zod) │ │
│ └───────────┬────────────────────────────────────────────┘ │
└──────────────┼───────────────────────────────────────────────┘
│ HTTPS
↓
┌──────────────────────────────────────────────────────────────┐
│ Cloudflare Worker (Edge Runtime) │
│ ┌────────────────────────┐ ┌──────────────────────────┐ │
│ │ Static Assets │ │ API Routes (Hono) │ │
│ │ (Vite build output) │ │ /api/* │ │
│ │ Served directly │ │ │ │
│ │ │ │ Middleware: │ │
│ │ / → index.html │ │ - CORS │ │
│ │ /assets/* │ │ - Auth (JWT verify) │ │
│ │ │ │ - Error handling │ │
│ └────────────────────────┘ │ - Validation (Zod) │ │
│ └───────┬──────────────────┘ │
│ │ │
└────────────────────────────────────────┼─────────────────────┘
│
┌──────────────────────────────┼──────────────────────────┐
│ │ │
↓ ↓ ↓
┌───────────────────┐ ┌───────────────────┐ ┌──────────────────┐
│ Cloudflare D1 │ │ Cloudflare R2 │ │ Clerk Auth │
│ (Database) │ │ (File Storage) │ │ (External) │
│ │ │ │ │ │
│ - users │ │ - avatars/ │ │ - User accounts │
│ - [other tables] │ │ - uploads/ │ │ - JWT tokens │
│ │ │ │ │ - Social auth │
└───────────────────┘ └───────────────────┘ └──────────────────┘
Components Breakdown
Frontend (Browser)
Technology: React 19 + Vite + Tailwind v4
Responsibilities:
- Render UI components
- Handle user interactions
- Client-side validation (Zod)
- Optimistic updates
- State management
- Routing (React Router or TanStack Router)
Key Libraries:
@clerk/clerk-react- Authentication UI and hooks@tanstack/react-query- Server state management (caching, refetching)zustand- Client state management (UI state, preferences)react-hook-form- Form state and validationzod- Schema validationshadcn/ui- UI component library (Radix UI primitives)
State Architecture:
Server State (TanStack Query)
↓
Cached API responses, auto-refetch, background sync
Examples: user data, tasks, analytics
Client State (Zustand)
↓
UI state, form state, user preferences
Examples: sidebar open/closed, theme, filters
Backend (Cloudflare Worker)
Technology: Hono web framework on Cloudflare Workers
Responsibilities:
- Serve static assets (Vite build)
- API request routing
- Authentication/authorization
- Server-side validation
- Business logic
- Database operations
- Third-party API integration
Route Structure:
app.use('*', cors())
app.use('/api/*', authMiddleware)
// Static assets
app.get('/', serveStatic({ path: './dist/index.html' }))
app.get('/assets/*', serveStatic({ root: './dist' }))
// API routes
app.route('/api/auth', authRoutes)
app.route('/api/[resource]', [resource]Routes)
// ... more routes
app.onError(errorHandler)
Middleware Pipeline:
Request
↓
CORS Middleware (allow frontend origin)
↓
Auth Middleware (verify JWT for /api/* routes)
↓
Route Handler
↓
Validation Middleware (Zod schema)
↓
Business Logic
↓
Response
↓
Error Handler (catch unhandled errors)
Database (Cloudflare D1)
Technology: SQLite (via D1)
Access Pattern: SQL queries via Worker bindings
Schema: See DATABASE_SCHEMA.md for full details
Usage:
// In Worker
const result = await c.env.DB.prepare(
'SELECT * FROM users WHERE id = ?'
).bind(userId).first()
Migrations: Manual SQL files in migrations/
Backups: Export via npx wrangler d1 export
Storage (Cloudflare R2)
Technology: S3-compatible object storage
Use Cases:
- User avatars
- File uploads
- Generated assets
Access Pattern: Direct upload from Worker, signed URLs for browser access
Bucket Structure:
[bucket-name]/
├── avatars/
│ ├── user-1.jpg
│ └── user-2.png
├── uploads/
│ └── [user-id]/
│ └── document.pdf
└── generated/
└── export-123.csv
Authentication (Clerk)
Technology: Clerk (external SaaS)
Flow:
1. User clicks "Sign In"
2. Clerk modal opens (handled by @clerk/clerk-react)
3. User authenticates (email/password or social)
4. Clerk returns JWT
5. Frontend includes JWT in API requests (Authorization: Bearer ...)
6. Worker verifies JWT using Clerk secret key
7. Worker extracts user email/ID from JWT
8. Worker processes request with user context
JWT Custom Template (configured in Clerk):
{
"email": "{{user.primary_email_address}}",
"userId": "{{user.id}}",
"metadata": {
"displayName": "{{user.first_name}} {{user.last_name}}"
}
}
Verification (in Worker):
import { verifyToken } from '@clerk/backend'
const token = c.req.header('Authorization')?.replace('Bearer ', '')
const verified = await verifyToken(token, {
secretKey: c.env.CLERK_SECRET_KEY
})
Data Flow Patterns
User Authentication Flow
1. User loads app
→ React app served from Worker static assets
→ ClerkProvider wraps app
2. User not authenticated
→ Show sign-in button
→ User clicks sign-in
→ Clerk modal opens
3. User signs in
→ Clerk handles authentication
→ Returns JWT to browser
→ React stores JWT in memory (via ClerkProvider)
4. User makes API request
→ React includes JWT in Authorization header
→ Worker middleware verifies JWT
→ Extracts user info from JWT
→ Passes to route handler via context
CRUD Operation Flow (Example: Create Task)
1. User fills out form
↓
2. React Hook Form validates locally (Zod schema)
↓
3. Validation passes → Submit to API
↓
4. POST /api/tasks with JWT in header
↓
5. Worker receives request
↓
6. CORS middleware allows request
↓
7. Auth middleware verifies JWT → extracts userId
↓
8. Route handler receives request
↓
9. Validation middleware validates body (Zod schema)
↓
10. Business logic creates task in D1
INSERT INTO tasks (user_id, title, ...) VALUES (?, ?, ...)
↓
11. Return created task (201)
↓
12. TanStack Query updates cache
↓
13. React re-renders with new task
File Upload Flow
1. User selects file in browser
↓
2. React sends file to POST /api/upload
↓
3. Worker receives file (multipart/form-data)
↓
4. Worker validates file (size, type)
↓
5. Worker uploads to R2
await c.env.R2_BUCKET.put(`uploads/${userId}/${filename}`, file)
↓
6. Worker creates DB record with R2 key
INSERT INTO files (user_id, r2_key, filename, ...) VALUES (...)
↓
7. Return file metadata (200)
↓
8. Frontend shows uploaded file with signed URL
GET /api/files/:id/url → Worker generates R2 signed URL
Deployment Architecture
Development Environment
localhost:5173
↓
Vite dev server (HMR)
↓
@cloudflare/vite-plugin runs Worker alongside Vite
↓
Worker connects to local D1 database
↓
All requests proxied correctly (frontend ↔ API on same port)
Start dev:
npm run dev
# Vite serves frontend on :5173
# Worker API available at :5173/api
# Uses local D1 and R2 buckets
Production Environment
https://[app-name].[account].workers.dev (or custom domain)
↓
Cloudflare Worker (edge locations globally)
↓
Static assets cached at edge
API requests hit Worker
↓
D1 database (regional, auto-replicated)
R2 storage (global, low latency)
Deploy:
npm run build # Vite builds frontend → dist/
npx wrangler deploy # Uploads Worker + assets
Security Architecture
Authentication
- JWT verification on all protected routes
- Clerk handles password hashing, session management, social auth
- No passwords stored in our database
Authorization
- User ownership checks before mutations
- Example: Can't delete another user's task
const task = await getTask(id)
if (task.user_id !== c.get('userId')) {
return c.json({ error: 'Forbidden' }, 403)
}
Input Validation
- Client-side (Zod): Fast feedback, better UX
- Server-side (Zod): Security, trust boundary
- Same schemas used on both sides
CORS
- Restrict origins to production domain
- Allow credentials for JWT cookies (if used)
Secrets Management
- Environment variables for API keys
- Never committed to git
- Wrangler secrets for production
Rate Limiting
[Optional: Add if implemented]
- X requests per minute per IP
- Higher limits for authenticated users
Scaling Considerations
Current Architecture Scales to:
- Requests: Millions/day (Cloudflare Workers auto-scale)
- Users: 10k-100k (D1 suitable for this range)
- Data: Moderate (D1 max 10GB per database)
If You Need to Scale Beyond:
- Database: Consider Hyperdrive + external Postgres for >100k users
- Storage: R2 scales infinitely
- Real-time: Add Durable Objects for WebSocket connections
- Compute: Workers already global and auto-scaling
Monitoring and Observability
[Optional: Define monitoring strategy]
Metrics to Track:
- Request volume and latency
- Error rates (4xx, 5xx)
- Database query performance
- R2 upload/download volumes
Tools:
- Cloudflare Analytics (built-in)
- Workers Analytics Engine (custom metrics)
- Sentry or similar for error tracking
Development Workflow
- Local development:
npm run dev - Make changes: Edit code, hot reload
- Test locally: Manual testing or automated tests
- Commit:
git commit -m "feat: add feature" - Deploy:
npx wrangler deploy - Monitor: Check Cloudflare dashboard for errors
Technology Choices Rationale
Why Cloudflare Workers?
- Global edge deployment (low latency)
- Auto-scaling (no server management)
- Integrated services (D1, R2, KV)
- Cost-effective for moderate traffic
Why Vite?
- Fast dev server with HMR
- Excellent React support
- Simple config
@cloudflare/vite-pluginintegrates perfectly with Workers
Why Hono?
- Lightweight and fast
- Express-like API (familiar)
- Native TypeScript support
- Works on any runtime (including Workers)
Why Clerk?
- Handles complex auth flows
- Social auth out of the box
- Great DX
- No need to build/maintain auth system
Why Tailwind v4?
- Utility-first CSS
- shadcn/ui compatibility
- Dark mode support
- v4 Vite plugin (no PostCSS needed)
Future Architecture Enhancements
Potential additions:
- Durable Objects for real-time features (WebSockets)
- Workers AI for AI-powered features
- Queues for background jobs
- Hyperdrive for external database connections
- Vectorize for semantic search
Revision History
v1.0 ([Date]): Initial architecture design v1.1 ([Date]): [Changes made]