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. 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.

    text
    cp .env.example .env.local
  2. 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. 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.

    text
    pnpm 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. 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. 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. 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. 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.

    text
    pnpm run doctor
    pnpm run quality
    pnpm run build
Minimum local .env.local starter
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.com

Core 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.

VariableScopeWhat to configure
NEXT_PUBLIC_SUPABASE_URLPublicThe Supabase project URL from Project Settings → API.
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEYPublicThe browser-safe Supabase publishable key from Project Settings → API.
SUPABASE_SECRET_KEYServer-onlyThe trusted server key for service-role operations; never prefix it with NEXT_PUBLIC_.
NEXT_PUBLIC_GOOGLE_CLIENT_IDPublicThe Web OAuth client ID used by Google Identity Services; configure its origins and the same client ID in Supabase Auth.
APP_URLServer/runtimehttp://localhost:3000 locally; the final HTTPS origin in preview or production.
APP_ENVServer/runtimedevelopment locally, preview for a preview deployment, and production for the live app.
NEXT_PUBLIC_SITE_NAMEPublicYour product name used by safe identity and metadata defaults.
NEXT_PUBLIC_SUPPORT_EMAILPublicThe customer-facing support address used in public copy and fallback messaging.
SUPER_ADMIN_EMAILServer/runtimeThe first owner address used by admin bootstrap and notification defaults.

Add values when you enable a capability

CapabilityVariables or settingSafety rule
Protected scheduled jobsCRON_SECRETUse a unique bearer secret for cron routes; do not reuse the health-check secret.
Readiness monitoringHEALTHCHECK_SECRETUse a separate secret and send it as an Authorization header from the monitor.
Production proxyTRUST_PROXY_HEADERS=trueEnable only when the hosting or reverse-proxy layer forwards trusted client headers.
Production static checksSTATIC_DATA_MODE=requiredKeep production credentials and static data configuration explicit; do not hide missing setup with placeholders.
Resend application emailRESEND_API_KEY, RESEND_SENDER_EMAIL, RESEND_REPLY_TO_EMAIL, RESEND_WEBHOOK_SECRETKeep all four server-only; verify the domain and webhook signature before sending real mail.
Stripe billingSTRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, BILLING_CATALOG_JSONUse test-mode resources first and keep provider IDs aligned with the active catalog.
Lemon Squeezy billingLEMONSQUEEZY_API_KEY, LEMONSQUEEZY_STORE_ID, LEMONSQUEEZY_WEBHOOK_SECRET, BILLING_CATALOG_JSONConfigure only when Lemon Squeezy is the active provider.

Choose the correct Supabase migration path

  1. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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.

    text
    RESEND_API_KEY=re_...
    RESEND_SENDER_EMAIL=no-reply@updates.example.com
    RESEND_REPLY_TO_EMAIL=support@example.com
  4. 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.

HostInitial configurationFollow-up before launch
VercelImport 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.
NetlifyConnect 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.
RailwayCreate 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.