ShipVeryFastShipVeryFast
Documentation

Deploy to Netlify

ShipVeryFast ships a typed deployment-config generator that produces a ready-to-commit netlify.toml, plus a helper script for one-shot CLI deploys. This page covers the generated config, the build and runtime settings, how to wire up environment variables, and the App Router gotchas to watch for on Netlify.

The generated netlify.toml

The Netlify config is generated from a Zod schema in libs/deploymentConfig.ts, not hand-written. The NetlifyConfigSchema extends a shared BaseDeploymentConfigSchema with Netlify-specific fields (publish, functions, headers, redirects), and generateNetlifyConfig() renders it to TOML. The defaults come from getDefaultNetlifyConfig(projectName) in the same file.

You can drive this from the interactive UI component components/ui/deployment/NetlifyDeploymentConfig.tsx, which lets you tweak the Node version, build command, publish directory, headers, and redirects, then copy or download the resulting netlify.toml. The default output looks like this:

[build]
  command = "npm run build"
  publish = ".next"

[build.environment]
  NODE_VERSION = "18.x"

[build.processing]
  skip_processing = false

[build.processing.css]
  bundle = true
  minify = true

[build.processing.js]
  bundle = true
  minify = true

[build.processing.html]
  pretty_urls = true

[build.processing.images]
  compress = true

[[headers]]
  for = "/*"
  [headers.values]
    Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload"
    X-Content-Type-Options = "nosniff"
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"

[[redirects]]
  from = "/api/*"
  to = "/.netlify/functions/:splat"
  status = 200
  force = false

Commit this file as netlify.toml at the repo root. The security headers above mirror the production headers in next.config.js, and the /api/* redirect maps your API routes onto Netlify Functions.

Build command and Next.js runtime settings

The generated config keeps the build conventional: command = "npm run build" (which runs next build) and a publish directory of .next. The Node runtime is pinned through NODE_VERSION under [build.environment], the default is 18.x, drawn from the NodeVersion enum in libs/deploymentConfig.ts (which also offers 14.x, 16.x, and 20.x).

Next.js App Router on Netlify needs the official @netlify/plugin-nextjs to handle SSR, route handlers, and image optimization. Netlify auto-installs it when it detects a Next.js site, so a publish dir of .next works, but you should not treat this as a plain static export.

For a quick CLI-driven deploy without committing a workflow, the repo includes a helper at deploy-to-netlify.js. It installs the Netlify CLI if missing and runs a deploy:

# helper script bundled with the boilerplate
node deploy-to-netlify.js

# what it does under the hood:
#   which netlify || npm install -g netlify-cli
#   netlify deploy

Run netlify deploy --prod directly when you are ready to push to your production site.

Configuring env vars and secrets in Netlify

Every required variable validated by libs/config.ts must be present in Netlify, or the app throws at startup (the schema is parsed at import time via envSchema.parse(process.env)). Set these under Site configuration → Environment variablesin the Netlify dashboard, or with the CLI. The generator's getDefaultNextJsEnvVars() marks the sensitive ones with isSecret, which keeps their values out of the committed netlify.toml, set those secrets in the dashboard, never in the TOML.

# set env vars via the Netlify CLI
netlify env:set NEXTAUTH_URL "https://your-app.netlify.app"
netlify env:set NEXTAUTH_SECRET "$(openssl rand -base64 32)"
netlify env:set SUPABASE_URL "https://xxxx.supabase.co"
netlify env:set SUPABASE_ANON_KEY "..."
netlify env:set SUPABASE_SERVICE_ROLE_KEY "..."
netlify env:set STRIPE_SECRET_KEY "sk_live_..."
netlify env:set STRIPE_WEBHOOK_SECRET "whsec_..."
netlify env:set MAILGUN_API_KEY "..."

The variables required by the Zod schema in libs/config.ts:

VariableSecret?Notes
NEXTAUTH_URLNoYour live Netlify URL (or custom domain). Validated as a URL.
NEXTAUTH_SECRETYesGenerate with openssl rand -base64 32.
SUPABASE_URL / SUPABASE_ANON_KEY / SUPABASE_SERVICE_ROLE_KEYService role: yesDatabase and auth. SUPABASE_URL is URL-validated.
STRIPE_SECRET_KEY / STRIPE_PUBLIC_KEY / STRIPE_WEBHOOK_SECRETSecret key + webhook: yesUse live keys in production.
STRIPE_PRICE_BASIC / STRIPE_PRICE_PRONoRequired price IDs. STRIPE_PRICE_ENTERPRISE is optional.
MAILGUN_API_KEY / MAILGUN_DOMAIN / MAILGUN_FROM_EMAILAPI key: yesTransactional email. MAILGUN_FROM_EMAIL is email-validated.
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETSecret: yesGoogle OAuth provider for NextAuth.
RATE_LIMIT_MAGICLINK_MAX / RATE_LIMIT_MAGICLINK_DURATIONNoCoerced to numbers. Required by the schema.
ANTHROPIC_API_KEY / OPENAI_API_KEYYesOptional, the AI assistant degrades gracefully when unset.

See Environment variables for the full reference, including which values are URL- or email-validated.

Webhook endpoint and OAuth redirect updates

Once your site is live on Netlify, point your external services at the new hostname:

  • Stripe webhook: the handler lives at app/api/webhooks/stripe/route.ts, so register https://your-app.netlify.app/api/webhooks/stripe in the Stripe dashboard and copy the resulting signing secret into STRIPE_WEBHOOK_SECRET.
  • OAuth redirect: NextAuth is mounted at app/api/auth/[...nextauth]/route.ts. In the Google Cloud console, add https://your-app.netlify.app/api/auth/callback/google as an authorized redirect URI, and make sure NEXTAUTH_URL matches your live origin.
# the endpoints to register, using your live Netlify origin
Stripe webhook:  https://your-app.netlify.app/api/webhooks/stripe
Google OAuth:    https://your-app.netlify.app/api/auth/callback/google

If you map a custom domain, repeat both updates with the final domain and update NEXTAUTH_URL accordingly, a stale NEXTAUTH_URL is the most common cause of broken sign-in after a domain change.

Common Netlify + Next.js App Router gotchas

  • Treat it as SSR, not a static export. The App Router uses server route handlers (app/api/*) and dynamic rendering, so the Next.js runtime plugin must be active. Do not set a static-only publish target.
  • The /api/* redirect already routes to functions. The default netlify.toml maps /api/* to /.netlify/functions/:splat. Avoid adding competing redirects that swallow your API routes or the OAuth callback path.
  • Headers are defined in two places. Production security headers are set in next.config.js (including the CSP), and the generated netlify.toml adds its own [[headers]] block. Keep them consistent so you do not double up or contradict the Content-Security-Policy.
  • Pin the Node version. Leave NODE_VERSION at 18.x (or set 20.x) so Netlify's build matches your local toolchain, mismatches surface as build-only failures.
  • Env vars are read at import time. Because libs/config.ts calls envSchema.parse(process.env) eagerly, a missing required variable fails the build rather than a single request. Set every required key before your first deploy.

Next steps