Environment Variables
This is the complete, code-verified list of environment variables Surflink reads. Put them in .env.local for local development, or your hosting provider's project settings for deployment.
| Variable | Used for |
|---|
NEXT_PUBLIC_SUPABASE_URL | Supabase project URL (browser/server/admin clients, middleware) |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Supabase anon/public key |
SUPABASE_SERVICE_ROLE_KEY | Service-role key for the admin client, all cron jobs, notifications, and achievement seeding |
NEXT_PUBLIC_MODAL_API_URL | SurfVision AI backend base URL (cloud) |
STREAM_SIGNING_SECRET | HMAC secret for signed video stream URLs. Throws in production if unset. |
SUPABASE_SERVICE_ROLE_KEY is mandatory even though earlier versions of these docs omitted it.
| Variable | Used for |
|---|
GOOGLE_AI_API_KEY | Google Gemini API key for coaching feedback and progress analysis |
GEMINI_MODEL | Model name; defaults to gemini-3-flash-preview |
| Variable | Used for |
|---|
SURFVISION_PRIMARY_URL | Server-only. Primary self-hosted GPU backend (YOLO26 + SAM 3.1); preferred over Modal when reachable, with automatic failover |
SURFVISION_CLEAN_VIDEO | Server-only. When true, new analyses ship a clean re-encode and overlays render live in-browser (toggleable) instead of baked in. Defaults to baked video |
NEXT_PUBLIC_LOCAL_API_URL | Dev-only override: if set and reachable, a local SurfVision server is preferred over the Modal cloud URL |
| Variable | Used for |
|---|
STRIPE_SECRET_KEY | Stripe secret key |
STRIPE_WEBHOOK_SECRET | Verifies incoming Stripe webhooks |
Stripe Price IDs for platform plans are also read from env (per plan/seat/overage). See Plans, Seats & Billing and Integrations.
| Variable | Used for |
|---|
NEXT_PUBLIC_APP_URL | App origin for checkout/portal returns, invite links, and email templates |
NEXT_PUBLIC_SITE_URL | Origin used in signup confirmation + notifications |
VERCEL_URL | Fallback base URL for notifications |
| Variable | Used for |
|---|
CRON_SECRET | Bearer secret required by every cron route. Fails closed if unset. |
| Variable | Used for |
|---|
RESEND_API_KEY | Resend email; every send is guarded by this, so email is optional |
RESEND_FROM_EMAIL | Sender address (has an inline default) |
| Variable | Used for |
|---|
FRAMEIO_CLIENT_ID / FRAMEIO_CLIENT_SECRET | Adobe IMS OAuth credentials |
FRAMEIO_REDIRECT_URI | OAuth callback URL |
| Variable | Used for |
|---|
APNS_KEY_ID / APNS_TEAM_ID / APNS_PRIVATE_KEY / APNS_BUNDLE_ID / APNS_ENVIRONMENT | APNs JWT signing and delivery |
| Variable | Used for |
|---|
NODE_ENV | Standard; also gates an SSRF guard that only honors client-supplied API URLs outside production |
DEBUG / NEXT_PUBLIC_DEBUG | Verbose logging in the Modal helper and session data hook |
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
NEXT_PUBLIC_MODAL_API_URL=https://your-surfvision-endpoint.modal.run
STREAM_SIGNING_SECRET=generate-a-long-random-secret
# AI coaching
GOOGLE_AI_API_KEY=your-google-ai-key
GEMINI_MODEL=gemini-3-flash-preview
# App origin + cron
NEXT_PUBLIC_APP_URL=http://localhost:3000
CRON_SECRET=another-long-random-secret