Initial Project Setup
Connect the first environment, Supabase database, authentication, email, and hosting before building your differentiating SaaS workflow.
On this page
First infrastructure pass
- 1
Copy the canonical environment template
The Product checkout keeps the environment inventory in .env.example. Copy it to .env.local, fill the boot values below, and keep every secret out of git. On PowerShell, use Copy-Item .env.example .env.local instead of cp.
textcp .env.example .env.local - 2
Create an isolated Supabase project
Create a development project in Supabase, or start the committed local stack with Docker. Use separate projects for development, preview, and production so a reset or test flow cannot damage customer data.
- Hosted development project
- Local Docker stack for migration and RLS work
- Dedicated production project
- 3
Run the migration baseline
Authenticate the Supabase CLI, link the checkout to the intended non-production project, preview the migration plan, apply it, and regenerate the linked types. pnpm dlx is the preferred one-off runner; npx supabase is an equivalent alternative.
textpnpm dlx supabase login pnpm dlx supabase link --project-ref <development-project-ref> pnpm dlx supabase db push --linked --dry-run pnpm dlx supabase db push --linked pnpm run supabase:types:generate:linked pnpm run supabase:types - 4
Configure authentication before inviting users
Set the Supabase Site URL and exact redirect allow list, then configure password, recovery, MFA, and Google sign-in behavior. The app callback is /auth/callback; never accept an arbitrary redirect_to value from a user.
- 5
Connect application email
Use Supabase Auth for account and security email. Use Resend for application notifications, support replies, billing messages, and operational alerts. Verify a sending domain before testing customer delivery.
- 6
Choose a hosting path
Vercel is the default CI-verified path. Netlify and Railway are supported compatibility targets; configure their environment scope, start behavior, proxy headers, public domain, and scheduled-job strategy explicitly.
- 7
Verify the baseline before product work
Confirm the app boots, the database is ready, auth redirects correctly, and the production build can be created before adding your differentiating workflow.
textpnpm run doctor pnpm run quality pnpm run build
NEXT_PUBLIC_SUPABASE_URL=https://<project-ref>.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=<publishable-key>
SUPABASE_SECRET_KEY=<server-only-secret-key>
NEXT_PUBLIC_GOOGLE_CLIENT_ID=<google-web-client-id>
APP_URL=http://localhost:3000
APP_ENV=development
NEXT_PUBLIC_SITE_NAME=Your Product
NEXT_PUBLIC_SUPPORT_EMAIL=support@example.com
SUPER_ADMIN_EMAIL=owner@example.comCore values for the first app boot
These are the values a new customer-owned checkout should understand first. Public values may be available to browser code; secret values must stay server-only.
| Variable | Scope | What to configure |
|---|---|---|
| NEXT_PUBLIC_SUPABASE_URL | Public | The Supabase project URL from Project Settings → API. |
| NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | Public | The browser-safe Supabase publishable key from Project Settings → API. |
| SUPABASE_SECRET_KEY | Server-only | The trusted server key for service-role operations; never prefix it with NEXT_PUBLIC_. |
| NEXT_PUBLIC_GOOGLE_CLIENT_ID | Public | The Web OAuth client ID used by Google Identity Services; configure its origins and the same client ID in Supabase Auth. |
| APP_URL | Server/runtime | http://localhost:3000 locally; the final HTTPS origin in preview or production. |
| APP_ENV | Server/runtime | development locally, preview for a preview deployment, and production for the live app. |
| NEXT_PUBLIC_SITE_NAME | Public | Your product name used by safe identity and metadata defaults. |
| NEXT_PUBLIC_SUPPORT_EMAIL | Public | The customer-facing support address used in public copy and fallback messaging. |
| SUPER_ADMIN_EMAIL | Server/runtime | The first owner address used by admin bootstrap and notification defaults. |
Add values when you enable a capability
| Capability | Variables or setting | Safety rule |
|---|---|---|
| Protected scheduled jobs | CRON_SECRET | Use a unique bearer secret for cron routes; do not reuse the health-check secret. |
| Readiness monitoring | HEALTHCHECK_SECRET | Use a separate secret and send it as an Authorization header from the monitor. |
| Production proxy | TRUST_PROXY_HEADERS=true | Enable only when the hosting or reverse-proxy layer forwards trusted client headers. |
| Production static checks | STATIC_DATA_MODE=required | Keep production credentials and static data configuration explicit; do not hide missing setup with placeholders. |
| Resend application email | RESEND_API_KEY, RESEND_SENDER_EMAIL, RESEND_REPLY_TO_EMAIL, RESEND_WEBHOOK_SECRET | Keep all four server-only; verify the domain and webhook signature before sending real mail. |
| Stripe billing | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, BILLING_CATALOG_JSON | Use test-mode resources first and keep provider IDs aligned with the active catalog. |
| Lemon Squeezy billing | LEMONSQUEEZY_API_KEY, LEMONSQUEEZY_STORE_ID, LEMONSQUEEZY_WEBHOOK_SECRET, BILLING_CATALOG_JSON | Configure only when Lemon Squeezy is the active provider. |
Choose the correct Supabase migration path
- 1
Fresh hosted development project
Link the project, run db push --dry-run, apply the committed migrations with db push --linked, then generate linked types.
- 2
Existing remote with schema changes
Stop before pushing. Run db pull, review the generated baseline migration, reset a disposable local database, and commit only the reviewed schema history.
- 3
Full local database work
Run supabase start, then supabase db reset --local --no-seed, database lint, pgTAP, and generated type checks. Docker is required for this path.
- 4
Production release
Use a trusted release environment, preview db push, apply only pending migrations, regenerate or validate types, and never run db reset --linked against production.
Google Identity Services
- 1
Set up Google sign-in in Google Cloud
In Google Cloud Console → Google Auth Platform, complete the consent-screen branding with your product name, homepage, privacy policy, terms, and verified domain. Create a Web application OAuth client and add http://localhost:3000 plus the final HTTPS origin as authorized JavaScript origins. The browser GIS flow returns an ID token directly, so it does not use a Supabase callback redirect.
- Save the client ID and client secret outside source control.
- Use the actual product domain as an authorized domain and keep the Supabase project host out of the browser origin list.
- Keep the auth routes' same-origin-allow-popups response policy so the GIS popup can complete sign-in.
- 2
Enable Google in Supabase Auth
In Supabase Dashboard → Authentication → Providers → Google, enable the provider and paste the same Google client ID and secret. Supabase validates the Google ID token here; the app calls signInWithIdToken() from the browser and then visits /auth/callback for profile and role reconciliation. Keep the app callback URLs in Supabase's allow list for that final step and for email auth.
- 3
Configure local Google OAuth only when needed
For the local CLI stack, set SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_SECRET in the environment that runs Supabase, then enable the Google provider in supabase/config.toml with the client ID, env-backed secret, and the CLI version's nonce setting. Keep the local secret file ignored.
text$env:SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_SECRET = "<google-client-secret>" pnpm dlx supabase start - 4
Test the complete auth loop
Test password sign-up, recovery, Google ID-token sign-in, callback continuation, sign-out, and MFA if enabled. Confirm that the Google consent and account-security surfaces show the product's configured name or domain, and that a user cannot change their own role or profile identity from the browser.
Application email
- 1
Configure Supabase Auth email
For production, configure custom SMTP in Supabase Authentication settings, set a verified auth sender, review confirmation/recovery/invite templates, and keep Auth email separate from application notifications.
- 2
Verify a Resend sending domain
In Resend, add a dedicated sending domain or subdomain, publish the supplied SPF and DKIM records, and add DMARC after legitimate senders are authenticated. Wait for the domain to be verified before using its address.
- 3
Add Resend server values
Create a least-privilege sending API key and set the sender and reply-to addresses in the deployment environment. Never put RESEND_API_KEY in a NEXT_PUBLIC_ variable.
textRESEND_API_KEY=re_... RESEND_SENDER_EMAIL=no-reply@updates.example.com RESEND_REPLY_TO_EMAIL=support@example.com - 4
Register and verify the Resend webhook
After deployment, create a Resend webhook at /api/notifications/webhooks/resend, subscribe to the delivery events the app uses, store its signing secret as RESEND_WEBHOOK_SECRET, and replay one event to confirm idempotent local delivery state.
Choose a hosting provider
All three hosts can serve the Next.js application. The host changes deployment controls, environment scope, port handling, and scheduler options—not the Product's Supabase or provider contracts.
| Host | Initial configuration | Follow-up before launch |
|---|---|---|
| Vercel | Import the private repository, use pnpm install --frozen-lockfile and pnpm run build, then add variables separately for Development, Preview, and Production. | Set APP_URL to the custom HTTPS domain, configure Supabase redirects and webhooks, and choose how protected cron routes will be scheduled. |
| Netlify | Connect the repository with the Next.js preset and add the same server/public variables in Site configuration. Netlify's Next.js adapter handles App Router routes. | Set the canonical APP_URL, review forwarded-header trust, configure auth and provider callbacks, and use an external scheduler for protected jobs unless a platform schedule is explicitly configured. |
| Railway | Create a service from the private repository, set the build command to pnpm run build and the start command to pnpm run start -- --port $PORT, then add variables in the service environment. | Generate or attach a custom domain, set APP_URL, verify the service listens on the injected PORT, and configure external scheduling and readiness monitoring. |
Baseline handoff is complete when
- The app boots from .env.local without production credentials.
- Supabase migration history is applied to the intended development project and generated types are checked.
- Supabase Site URL and redirect allow list contain only deliberate local, preview, and production URLs.
- Google sign-in, password recovery, and MFA behavior are tested for the environment being launched.
- Resend sender, reply-to, webhook signature, and delivery replay are verified.
- The hosting provider has the correct build, start, port, environment scope, APP_URL, and scheduler plan.
- Only after this checklist passes should you begin the first differentiating SaaS workflow.