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-scheme for 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_id ensure 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

  1. 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
  2. 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
  3. 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:

  1. Detect Connection Status

    navigator.onLine; // true/false
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
  2. Queue Mutations Locally

    • Store form submissions in IndexedDB when offline
    • Include timestamp, action type, and payload
    • Example: { type: 'CREATE_POST', timestamp: 1234567890, data: {...} }
  3. Optimistic UI Updates

    • Show success immediately (optimistic assumption)
    • Assign temporary IDs to new records
    • Roll back if sync fails (with error notification)
  4. 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
  5. 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)
  6. 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_id prevent 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_key in client bundles
  • Use anon_key for public operations (sign up, public reads)
  • Use service_role_key only in server code (API routes, webhooks)
  • Implement rate limiting on login attempts (use Supabase Auth built-in)

Multi-Tenant SaaS Pattern

  • Add tenant_id or org_id column 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 ANALYZE to 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 build to generate .next directory
  • 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