Overview
Environment variables are key-value pairs that configure your application without hardcoding sensitive information. Sabo uses three types of environment files:.env.local- Local development (not committed to git).env.test- Testing environment (Playwright E2E tests)- Production environment - Set in your hosting platform (Vercel, Netlify, etc.)
Variable Naming Convention
Sabo follows Next.js environment variable conventions:NEXT_PUBLIC_*- Exposed to the browser (client-side code can access these)- Without
NEXT_PUBLIC_- Server-side only (API routes, server components)
process.env.STRIPE_SECRET_KEY in a client component, it will be undefined.Required Variables
These variables are essential for Sabo to function. Your app will not work without them.Core Application
- OAuth callback URLs (
src/app/(auth)/actions.ts) - Email verification links
- Stripe Customer Portal return URL (
src/app/api/customer_portal/route.ts)
Supabase (Authentication & Database)
- Browser client (
src/lib/supabase/client.ts) - Server client (
src/lib/supabase/server.ts) - Middleware (
src/lib/supabase/middleware.ts) - Test helpers (
tests/e2e/helpers/auth.ts)
- Browser client (
src/lib/supabase/client.ts) - Server client (
src/lib/supabase/server.ts) - Middleware (
src/lib/supabase/middleware.ts) - Test helpers (
tests/e2e/helpers/auth.ts)
anon public- Service client for bypassing RLS (
src/lib/supabase/server.ts-createServiceClient()) - Stripe webhook handler (
src/app/api/webhooks/stripe/route.ts) - Polar webhook handler (
src/app/api/webhooks/polar/route.ts)
service_role secretStripe (Payments)
- Stripe client initialization (
src/lib/payments/stripe.ts) - Creating checkout sessions (
src/app/api/checkout_sessions/route.ts) - Creating Customer Portal sessions (
src/app/api/customer_portal/route.ts) - Webhook verification (
src/app/api/webhooks/stripe/route.ts)
- Webhook signature verification (
src/app/api/webhooks/stripe/route.ts)
- Local Development
- Production
- Install Stripe CLI:
brew install stripe/stripe-cli/stripe - Login:
stripe login - Forward webhooks:
stripe listen --forward-to localhost:3000/api/webhooks/stripe - Copy the
whsec_...secret from the terminal output
- Client-side Stripe initialization
- Stripe Checkout redirects
Polar (Payments)
- Checkout redirect handler (
src/app/api/checkout/route.ts) - Customer portal handler (
src/app/api/portal/route.ts) - Webhook handler + invoice helper (
src/app/api/webhooks/polar/route.ts)
- Polar Dashboard → Settings → Webhooks → Add endpoint.
- Point to
/api/webhooks/polar(ngrok URL in dev, production domain in prod). - Copy the generated secret.
src/app/api/webhooks/polar/route.tsOptional Variables
These variables enable additional features but are not required for basic functionality.Stripe (Advanced)
- Plan configuration (
src/lib/payments/plans.ts) - Pricing page for displaying correct checkout button
- Create a product in Stripe Dashboard → Products
- Add a monthly recurring price
- Copy the Price ID (starts with
price_)
src/lib/payments/plans.ts is used as fallback. You can hardcode Price IDs in that file instead of using environment variables.- Plan configuration (
src/lib/payments/plans.ts) - Pricing page billing cycle toggle
- Customer Portal session creation (
src/app/api/customer_portal/route.ts)
- Go to Stripe Dashboard → Settings → Billing → Customer portal
- Create a new configuration or use the default
- Copy the configuration ID
- Which payment methods customers can add/remove
- Whether customers can cancel subscriptions
- Custom branding and messaging
Polar (Advanced)
/api/checkout.Format:src/lib/payments/plans.ts, src/components/marketing/pricing.tsx)src/lib/payments/plans.ts, pricing toggle, webhook plan lookup helper.https://sandbox-api.polar.sh vs https://api.polar.sh).- Webhook invoice helper (
src/app/api/webhooks/polar/route.ts) — determines API base URL - Checkout handler — when modified to use environment-driven server selection
- Customer portal (
src/app/api/portal/route.ts) — usesNODE_ENVby default, but can be updated to respectPOLAR_SANDBOX
server: "sandbox" hardcoded in /api/checkout for safety. To make it environment-driven, update the handler to use process.env.POLAR_SANDBOX === "true" ? "sandbox" : "production". See the Payments with Polar guide for details.PostHog (Analytics)
- Client-side PostHog initialization (
src/components/posthog-provider.tsx) - Server-side PostHog client (
src/lib/posthog/server.ts)
NEXT_PUBLIC_POSTHOG_KEY and NEXT_PUBLIC_POSTHOG_HOST must be set for PostHog to activate.- Client-side PostHog initialization (
src/components/posthog-provider.tsx) - Server-side PostHog client (
src/lib/posthog/server.ts)
Testing (Playwright)
- Authentication helper (
tests/e2e/helpers/auth.ts) - Sign-in tests (
tests/e2e/auth/sign-in.spec.ts) - Password reset tests (
tests/e2e/auth/password-reset.spec.ts)
- Create a test user in your Supabase database (manually or via Supabase Dashboard)
- Use a dedicated test email (not a real user account)
- Add email to
.env.testfile
- Authentication helper (
tests/e2e/helpers/auth.ts) - Sign-in tests (
tests/e2e/auth/sign-in.spec.ts)
System Variables
development- Local dev server (pnpm dev)production- Production build (pnpm build && pnpm start)test- Testing environment (set by testing frameworks)
- Auth callback redirects (
src/app/auth/callback/route.ts) - PostHog debug mode (
src/components/posthog-provider.tsx) - Conditional behavior based on environment
- Playwright configuration (
playwright.config.ts)- Enables
forbidOnly(fails iftest.only()is left in code) - Sets
retries: 2(retries flaky tests twice) - Sets
workers: 1(sequential test execution) - Disables
reuseExistingServer(always starts fresh server)
- Enables
Setup Guide
Local Development Setup
Create .env.local file
pnpm dev) after creating or modifying .env.local.Add .env.local to .gitignore
.env.local is in your .gitignore file:Create .env.test for testing
.env.test:Production Setup (Vercel)
Deploy to Vercel
- Push your code to GitHub
- Import repository in Vercel
- Vercel will detect Next.js automatically
Add environment variables
Update Supabase URLs
- Site URL:
https://yourdomain.com - Redirect URLs:
https://yourdomain.com/auth/callbackhttps://*.vercel.app/auth/callback(for preview deployments)
Set up production Stripe webhook
- Go to Stripe Dashboard → Developers → Webhooks
- Click “Add endpoint”
- Endpoint URL:
https://yourdomain.com/api/webhooks/stripe - Select events:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.paidinvoice.payment_failed
- Copy signing secret and add to Vercel as
STRIPE_WEBHOOK_SECRET
Set up production Polar webhook
- Polar Dashboard → Settings → Webhooks → Add endpoint.
- Endpoint URL:
https://yourdomain.com/api/webhooks/polar. - Subscribe to subscription + order events.
- Copy the signing secret into Vercel as
POLAR_WEBHOOK_SECRET. - Ensure
POLAR_SANDBOX=falseso/api/checkoutand the webhook use live APIs.
Redeploy
Troubleshooting
Environment variables not working
Environment variables not working
undefined in code, features not working.Causes:- Server not restarted after changing
.env.local - Variable name typo
- Trying to access server-only variable in client code
.env.localfile in wrong directory
- Restart dev server: Stop (
Ctrl+C) and restart (pnpm dev) - Check spelling: Variable names are case-sensitive
- Check client/server: Only
NEXT_PUBLIC_*variables work in browser - Verify location:
.env.localmust be in project root (same directory aspackage.json)
'NEXT_PUBLIC_SUPABASE_URL is not defined' error
'NEXT_PUBLIC_SUPABASE_URL is not defined' error
- Check
.env.localhas bothNEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY - Restart dev server:
pnpm dev - Verify no typos in variable names (common:
ANON_KEYvsPUBLISHABLE_KEY)
PUBLISHABLE_KEY not ANON_KEY:Stripe webhook signature verification failed
Stripe webhook signature verification failed
STRIPE_WEBHOOK_SECRET or webhook not from Stripe.Fix:Local development:- Ensure Stripe CLI is running:
stripe listen --forward-to localhost:3000/api/webhooks/stripe - Copy the
whsec_...secret from terminal output - Update
STRIPE_WEBHOOK_SECRETin.env.local - Restart server
- Verify webhook endpoint URL matches exactly:
https://yourdomain.com/api/webhooks/stripe - Check webhook signing secret in Stripe Dashboard → Webhooks → [Your endpoint] → Signing secret
- Update
STRIPE_WEBHOOK_SECRETin Vercel - Redeploy
Polar webhook signature verification failed
Polar webhook signature verification failed
POLAR_WEBHOOK_SECRET doesn’t match the endpoint configured in Polar Dashboard, or the webhook still points to an old URL (common when ngrok restarts).Fix:- Reconfirm the webhook endpoint URL in Polar Dashboard → Settings → Webhooks.
- Copy the latest signing secret and update
POLAR_WEBHOOK_SECRET(.env.localfor dev, hosting env for prod). - Restart the dev server or redeploy.
- Trigger a sandbox checkout to emit
subscription.created/order.paidevents and ensure the terminal logs show200 OK.
OAuth redirect not working
OAuth redirect not working
NEXT_PUBLIC_SITE_URL doesn’t match Supabase configuration.Fix:-
Check
NEXT_PUBLIC_SITE_URLin.env.local: -
Ensure it matches Supabase Dashboard → Authentication → URL Configuration:
- Site URL must match
NEXT_PUBLIC_SITE_URLexactly - Redirect URLs must include
{NEXT_PUBLIC_SITE_URL}/auth/callback
- Site URL must match
- No trailing slash in URL
http://localhost:3000/❌ (trailing slash)http://localhost:3000✅
PostHog not tracking events
PostHog not tracking events
-
Verify both variables are set:
-
Check PostHog key format starts with
phc_ - Restart dev server
-
Open browser console and look for
[PostHog]debug messages (in development mode) - Disable ad blockers (they often block PostHog)
NEXT_PUBLIC_POSTHOG_KEY and NEXT_PUBLIC_POSTHOG_HOST are set. If either is missing, PostHog is disabled.Playwright tests can't authenticate
Playwright tests can't authenticate
.env.test setup.Fix:-
Create
.env.testin project root: -
Create test user in Supabase:
- Go to Supabase Dashboard → Authentication → Users
- Add user manually or via SQL
- Verify test user exists and email is confirmed
-
Run tests:
pnpm test:e2e
Security Best Practices
1. Never commit secrets to version control
1. Never commit secrets to version control
- Add
.env.local,.env.test,.env*.localto.gitignore - Use environment variables, not hardcoded secrets
- Review commits before pushing to ensure no secrets leaked
- Rotate all compromised keys immediately
- Remove from git history:
git filter-branchor use BFG Repo-Cleaner - Force push to remote (if safe to do so)
2. Use different keys for development and production
2. Use different keys for development and production
- Stripe: Use test keys (
sk_test_,pk_test_) in development - Supabase: Use separate projects for dev and prod (recommended)
- PostHog: Use separate projects or test mode
3. Restrict server-only variables
3. Restrict server-only variables
SUPABASE_SECRET_KEY- Bypasses Row Level SecuritySTRIPE_SECRET_KEY- Full Stripe account accessSTRIPE_WEBHOOK_SECRET- Webhook verificationPOLAR_ACCESS_TOKEN- Full Polar API accessPOLAR_WEBHOOK_SECRET- Polar webhook verification
NEXT_PUBLIC_* variables in browser DevTools.Check your code:4. Rotate keys regularly
4. Rotate keys regularly
- Production secrets: Every 90 days
- After team member departure: Immediately
- After suspected breach: Immediately
- Generate new secret key in Supabase Dashboard → Project Settings → API
- Update
SUPABASE_SECRET_KEYin hosting platform - Redeploy
- Revoke old key
- Create new secret key in Stripe Dashboard → Developers → API keys
- Update
STRIPE_SECRET_KEYin hosting platform - Redeploy
- Delete old key from Stripe Dashboard
5. Use environment-specific URLs
5. Use environment-specific URLs
Quick Reference
All Variables Summary
| Variable | Access | Required? | Where Used |
|---|---|---|---|
NEXT_PUBLIC_SITE_URL | Public | Yes | OAuth redirects, email links, Stripe portal |
NEXT_PUBLIC_SUPABASE_URL | Public | Yes | All Supabase clients |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | Public | Yes | All Supabase clients |
SUPABASE_SECRET_KEY | Secret | Yes | Service client, webhooks |
STRIPE_SECRET_KEY | Secret | Yes | Stripe API, checkout, portal |
STRIPE_WEBHOOK_SECRET | Secret | Yes | Webhook verification |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | Public | Yes | Client-side Stripe |
NEXT_PUBLIC_STRIPE_PRICE_ID_PRO_MONTHLY | Public | Optional | Pricing page |
NEXT_PUBLIC_STRIPE_PRICE_ID_PRO_YEARLY | Public | Optional | Pricing page |
STRIPE_CUSTOMER_PORTAL_CONFIG_ID | Secret | Optional | Customer portal customization |
POLAR_ACCESS_TOKEN | Secret | Yes | Polar checkout, portal, webhooks |
POLAR_WEBHOOK_SECRET | Secret | Yes | Polar webhook verification |
NEXT_PUBLIC_POLAR_PRODUCT_ID_PRO_MONTHLY | Public | Optional | Polar plan lookup + checkout |
NEXT_PUBLIC_POLAR_PRODUCT_ID_PRO_YEARLY | Public | Optional | Polar plan lookup + checkout |
POLAR_SANDBOX | Secret | Optional | Toggle sandbox vs production Polar API |
NEXT_PUBLIC_POSTHOG_KEY | Public | Optional | PostHog analytics |
NEXT_PUBLIC_POSTHOG_HOST | Public | Optional | PostHog analytics |
TEST_USER_EMAIL | Secret | Optional | Playwright tests |
TEST_USER_PASSWORD | Secret | Optional | Playwright tests |
NODE_ENV | System | Auto | Environment detection |
CI | System | Auto | CI/CD behavior |