DocsArchitecture
App structure, patterns, and data flow
Architecture
Surflink is a Next.js 16 (React 19) App Router app in JavaScript (not TypeScript), with Supabase for database, auth, storage and realtime, deployed to Vercel from main. The computer-vision backend (SurfVision) is a separate service; the native apps are a separate repository.
Directory layout
surflink-app/
├── src/
│ ├── app/ # App Router pages and API routes
│ │ ├── layout.js # Root layout (fonts, theme, service worker)
│ │ ├── page.js # Landing page
│ │ ├── login/ signup/ request-access/ forgot-password/ reset-password/ verify-email/
│ │ ├── invite/[token]/ # Athlete invite + guardian page
│ │ ├── org/invite/[token]/ # Member invite acceptance
│ │ ├── dashboard/ # Coach Today (the home page)
│ │ ├── upload/ # Quick + batch upload, Frame.io import
│ │ ├── sessions/ # Video sessions list + viewer (overlays, insights, review mode, Send to TV)
│ │ ├── athletes/ # Roster, profile, CSV import
│ │ ├── coaches/ # Coach view + team calendar
│ │ ├── training-sessions/ # Schedule, detail, CSV import, batch upload (lessons/ redirects here)
│ │ ├── wearable/ # Watch sessions + trends
│ │ ├── drills/ compare/ analytics/ messages/ notifications/
│ │ ├── circuits/ strategy/ # Imported standings + the standings view (competitions module)
│ │ ├── athlete/ # The athlete portal
│ │ ├── org/settings/ # Workspace: members, roles, modules, billing
│ │ ├── billing/ pricing/ settings/ tv/
│ │ ├── admin/ # Super-admin console
│ │ ├── docs/ # This documentation site
│ │ └── api/ # Route handlers (see API Reference)
│ ├── components/ # React components (review/, wearable/, session/, admin/, ui/ …)
│ ├── hooks/ # useSessionData, useTVRemote, useContestImport, …
│ ├── lib/ # Shared code (see below)
│ ├── middleware.js # Auth + public routes + billing gating
│ └── __tests__/ # Vitest suite
├── public/ # Static assets, PWA manifest, service worker, the coach-guide PDF
├── supabase/migrations/ # The authoritative, ordered schema
├── docs-internal/ # The handbook, ADRs, design + voice guides
├── scripts/ # Handbook build, guide PDF, migration manifest, eval tooling
└── vercel.json # Cron schedule
Key patterns
- Client components + direct Supabase queries. Most pages are
'use client'and useuseAuth()(fromAuthProvider) for the user, coach profile, current org, permissions and enabled modules. Reads go through the browser Supabase client and are RLS-scoped. - DashboardLayout wraps every coach page: the grouped sidebar (
Sidebar.js, filtered by enabled modules) and a mobile navbar. The athlete portal usesAthleteNav. - Server route handlers for privileged work — the video pipeline, Stripe, orgs/RBAC, notifications, crons, admin — live under
src/app/api/*and use the server or admin Supabase client. - Three Supabase clients.
supabase-browser.js(anon, client),supabase-server.js(SSR, cookie-bound),supabase-admin.js(service role, server only). - Mobile BFF.
/api/mobile/*routes serve the iPhone with bearer auth (getRequestUser). New mobile routes must re-export a shared handler fromsrc/lib/rather than duplicating logic. - Labels and errors. UI strings go through
src/lib/labels.js; errors throughfriendlyError()insrc/lib/errorMessages.js. Design tokens only — a pre-commit gate enforces it.
Where the logic lives (src/lib/)
| Area | Files |
|---|---|
| Backend resolution + SSRF allow-list | modal.js |
| Measured insights, overlays, markers | sessionInsights.js, overlayMarkers.js |
| A/B loop rules | abLoop.js |
| Watch data | wearable.js, wearableFusion.js, wearableAutoSync.js, wearableBinding.js, wearableTrends.js, wearableHighlights.js |
| Progress numbers | progressMonthly.js |
| Permissions, roles, modules | permissions.js, roles.js, modules.js |
| Notifications | notificationTypes.js, notifications.js, notificationDelivery.js |
| Billing | stripe.js |
| Competition | strategy/, freshness.js, qualifications.js |
| Messaging rules | messagingRules.js |
| Age rules | age.js |
Video pipeline (overview)
- Upload raw clips to Supabase Storage (
raw-uploads) via resumable TUS. - Analyze —
POST /api/clips/analyzesigns the clip and forwards it to SurfVision. - Poll
GET {base}/status/{job_id}. - Store —
POST /api/clips/storedownloads the processed video and sidecars and caches them in Supabase Storage. - Stream through the HMAC-gated
/api/video/streamproxy.
See SurfVision Pipeline.
Theming and fonts
CSS custom properties in globals.css (dark/light) via ThemeProvider (next-themes). Three Google fonts via next/font: Outfit (display), Plus Jakarta Sans (body), JetBrains Mono (mono).