Architecture & Design Decisions
This document explains the architecture and design patterns used in PWASK, including offline-first synchronization, security considerations, and guidance for production hardening.
High-Level Stack
- Framework: Next.js App Router (server components + client components as needed)
- Database: Supabase (managed PostgreSQL with RLS, Auth, Realtime subscriptions, Storage)
- Frontend: React 19+, TypeScript, Tailwind CSS, SWR/React Query for data fetching
- Offline: IndexedDB for local caching, Service Worker for background sync
- PWA: Web App Manifest, Service Worker caching strategies, Web Push (optional)
- Internationalization: next-intl for EN/FR (extensible to more languages)
- Deployment: Vercel, Netlify, or any Node.js-compatible platform
Core Architecture Diagram
User Device
├── Browser
│ ├── React App (Next.js Client)
│ ├── Service Worker (caching + sync)
│ └── IndexedDB (local cache)
└── Network
└── [Online] ↔ Next.js Server
├── API Routes (auth, email, webhooks)
├── Server Components (SSR)
└── [Online] ↔ Supabase
├── PostgreSQL (RLS)
├── Auth (JWT)
├── Realtime (WebSocket)
└── Storage (signed URLs)
Key Components
Frontend Architecture
Server Components (default in Next.js App Router)
- Render on the server for SEO, security, and reduced client bundle size
- Fetch data directly from Supabase using server-side keys
- Suitable for: pages, static content, data-heavy components
Client Components ('use client')
- Handle interactivity, forms, real-time updates
- Use client-safe credentials (anon key) for Supabase
- Implement optimistic UI updates for offline-first experience
- Example: forms, filters, realtime subscription widgets
Data Fetching Patterns
- Server Components: direct
supabase.from('table').select()calls - Client Components: SWR or React Query with Supabase client
- Realtime subscriptions:
.on('*', (payload) => { ... })for live updates - Offline sync: queue mutations locally and replay when online
Styling & Themes
- Tailwind CSS with CSS variables for light/dark mode
prefers-color-schemefor system theme detection- Theme toggle stored in localStorage (persists across sessions)
Backend Architecture (Supabase)
Authentication
- JWT-based sessions (stored in
auth.users) - Methods: email/password, magic links, OAuth (Google, GitHub, etc.)
- Custom user profiles table linked via foreign key
- RLS policies enforce user isolation
Row-Level Security (RLS)
- Every table with user data must have RLS enabled
- Policies like:
auth.uid() = user_idensure users see only their data - Service role keys bypass RLS (use server-side only)
- Example RLS policy for profiles table:
CREATE POLICY "Users can view own profile" ON profiles FOR SELECT USING (auth.uid() = id);
Database Schema
auth.users— managed by Supabase Auth (email, passwords, sessions)public.profiles— user metadata (avatar, display_name, etc.)public.blog_articles— blog content with markdown/HTML- Custom tables: extend with your own business logic (orders, posts, etc.)
- Foreign keys link related data; cascading deletes clean up orphaned records
Realtime Subscriptions
- PostgreSQL LISTEN/NOTIFY triggers broadcast changes to connected clients
- Use
supabase.from('table').on('*', callback)for live updates - Useful for: collaborative features, notifications, live dashboards
Storage
- File uploads to
supabase.storage - Signed URLs for temporary or permanent access
- Row-level policies control who can read/write files
- Example: user avatars in
avatars/{user_id}folder
Service Worker & Caching
Caching Strategies
-
Cache-First (for static assets)
- Serve from cache if available; update in background
- Suitable for: JS bundles, CSS, images (versioned by hash)
- TTL: very long (service worker persists until next deploy)
- Pro: instant load; Con: cached code stays until manual update
-
Network-First (for API responses)
- Try network first; fallback to cache if offline
- Suitable for: blog articles, user data (non-critical)
- TTL: short (5–60 minutes)
- Pro: fresh data; Con: slow on poor connections
-
Stale-While-Revalidate
- Serve cached version immediately while fetching new version in background
- Suitable for: articles, feeds, search results
- Pro: instant + fresh; Con: UI updates after cache hits
- Requires: UI mechanism to show "newer version available"
Service Worker Updates
- New deploy → new service worker detected
- Users see update notification ("A new version is available")
- Optional auto-update or manual refresh button
- Avoid disruptive updates during critical user operations
Precaching Manifest
- Service Worker build step generates list of assets to precache
- Vercel/Netlify auto-generate this during build
- Include: app shell, offline page, critical routes, essential fonts
Offline-First Sync Pattern
PWASK implements a robust offline-first architecture that keeps the app functional without network:
-
Detect Connection Status
navigator.onLine; // true/false window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); -
Queue Mutations Locally
- Store form submissions in IndexedDB when offline
- Include timestamp, action type, and payload
- Example:
{ type: 'CREATE_POST', timestamp: 1234567890, data: {...} }
-
Optimistic UI Updates
- Show success immediately (optimistic assumption)
- Assign temporary IDs to new records
- Roll back if sync fails (with error notification)
-
Background Sync Replay
- When online, Service Worker retries all queued mutations
- Use exponential backoff: 1s → 2s → 4s → 8s → give up
- Delete from queue only after successful server response
-
Conflict Resolution
- Last-Write-Wins: server timestamp overrides local changes
- Vector Clocks: track causality for non-commutative operations
- Custom Logic: business rules define winner (e.g., highest bid wins)
-
Data Consistency
- Periodically refetch data to sync with server state
- Use ETags or version numbers to detect stale cache
- Notify user if local changes were overwritten
Security & Multi-Tenant Considerations
Row-Level Security (RLS)
- Must be enabled on every table with user/tenant data
- Policies like
auth.uid() = user_idprevent unauthorized reads - Test RLS with different user accounts before production
- Example:
ALTER TABLE posts ENABLE ROW LEVEL SECURITY; CREATE POLICY "users_see_own_posts" ON posts FOR SELECT USING (auth.uid() = author_id);
API Security
- Never embed
service_role_keyin client bundles - Use
anon_keyfor public operations (sign up, public reads) - Use
service_role_keyonly in server code (API routes, webhooks) - Implement rate limiting on login attempts (use Supabase Auth built-in)
Multi-Tenant SaaS Pattern
- Add
tenant_idororg_idcolumn to all tables - Filter queries by session metadata:
auth.jwt() ->> 'tenant_id' - Enforce quota per tenant (uploads, seats, API calls)
- Use custom claims in JWT for tenant ownership verification
CORS & CSRF
- CORS headers allow only your domain to access Supabase
- Use SameSite cookies for session protection
- Implement CSRF tokens if using traditional form submissions
Scalability & Performance
Database Optimization
- Add indexes on frequently filtered/sorted columns
- Use
EXPLAIN ANALYZEto find slow queries - Monitor query performance via Supabase dashboard
Read Replicas
- Supabase supports read-only replicas for high-traffic scenarios
- Route analytics/reporting queries to replicas
- Keep write operations on primary database
Connection Pooling
- Use PgBouncer (included with Supabase) for concurrent connections
- Prevents "too many connections" errors under load
Caching Strategy
- Leverage CDN for static content (Vercel/Netlify automatic)
- Cache API responses with appropriate headers
- Use HTTP caching:
Cache-Control: public, max-age=3600
Image Optimization
- Use Next.js
<Image>component for automatic optimization - Serve WebP with JPEG fallback (automatic via next/image)
- Implement lazy loading for below-the-fold images
Observability & Production Readiness
Error Tracking
- Integrate Sentry for JavaScript and server-side errors
- Set up alerts for error thresholds (e.g., 10+ errors/minute)
- Include release/commit info for better debugging
Monitoring
- Vercel Analytics for Core Web Vitals (LCP, FID, CLS)
- Supabase dashboard metrics (database size, connection count)
- Uptime monitoring (Pingdom, UptimeRobot) for critical endpoints
- Google Analytics for user behavior tracking
Logging
- Structured logs with timestamps, user ID, action type
- Log authentication events (sign-up, login, logout, role changes)
- Log data mutations (create, update, delete with before/after values)
- Use Supabase Logs or Vercel Logs for centralized access
Incident Response
- Document runbooks for common issues (database down, auth broken)
- Set up on-call rotation for production support
- Use automated backups and test restore procedures regularly
Deployment Patterns
Recommended Platforms
- Vercel: Best for Next.js (auto-scaling, edge functions, analytics included)
- Netlify: Good alternative with GitHub integration
- Cloudflare Pages: Emerging option with edge computing
- Self-hosted: Use Docker + Node.js on DigitalOcean / AWS / GCP
Build & Deploy
- Use
pnpm buildto generate.nextdirectory - Platform runs your build step automatically on git push
- Preview deployments for PRs (test before merging)
- Automatic rollback on failed deployments
Environment Management
- Separate dev, staging, and production Supabase projects
- Use environment-specific secrets in hosting provider
- Never use production secrets during development
- Document database migration steps for team
Where to Go Next
- Back to Introduction
- Environment & Deployment — Complete env variable reference and platform guides
- FAQ — Common questions about offline sync, security, and troubleshooting