StarterApp Docs
Deployment

Deploy to Netlify

Git-powered deployment with built-in forms and edge functions

Netlify combines deployment simplicity with powerful features like form handling and serverless functions. The platform provides automatic SSL, global CDN distribution, and deploy previews.

Pro Tip

Install the Essential Next.js plugin for image optimization, ISR support, and edge function distribution. Without this plugin, Next.js advanced features won't work correctly on Netlify.

Prerequisites

Git Repository

Push your StarterApp to GitHub, GitLab, or Bitbucket. Netlify supports all major Git providers with identical workflows.

Netlify Account

Create a free account at netlify.com. The Starter tier includes:

  • 300 build minutes/month
  • 100GB bandwidth/month
  • Automatic SSL certificates
  • Deploy previews on all branches

Node.js 20.18.1+

Verify your repository includes .nvmrc:

# .nvmrc
v20.18.1

Netlify reads this file to set the build environment Node version.

Deployment Walkthrough

Import Project

Navigate to app.netlify.com/start and select your Git provider.

Monorepo Setup Required

Netlify requires manual configuration for monorepos. You'll create separate sites for apps/marketing and apps/dashboard.

Authorize Netlify to access your repository and select the StarterApp project.

Configure Build Settings

# Base directory
apps/marketing

# Build command
cd ../.. && pnpm turbo run build --filter=marketing

# Publish directory
apps/marketing/.next

# Functions directory (optional)
apps/marketing/.netlify/functions

Build Command Context

The cd ../.. navigates to monorepo root, ensuring pnpm workspace resolution works correctly. Turbo's --filter targets only the marketing app.

# Base directory
apps/dashboard

# Build command
cd ../.. && pnpm turbo run build --filter=dashboard

# Publish directory
apps/dashboard/.next

# Functions directory (optional)
apps/dashboard/.netlify/functions

Set Environment Variables

Navigate to Site settings → Environment variables and add:

# Core Application (update after initial deploy)
APP_BASE_URL=https://your-site.netlify.app
DASHBOARD_BASE_URL=https://dashboard-your-site.netlify.app
NEXT_PUBLIC_DASHBOARD_BASE_URL=https://dashboard-your-site.netlify.app

# Authentication (BetterAuth)
BETTER_AUTH_SECRET=<generate: openssl rand -base64 32>
GOOGLE_CLIENT_ID=<from Google Cloud Console>
GOOGLE_CLIENT_SECRET=<from Google Cloud Console>

# Database (Convex)
NEXT_PUBLIC_CONVEX_URL=https://your-prod.convex.cloud
CONVEX_DEPLOYMENT=production
CONVEX_DEPLOY_KEY=<from Convex dashboard>

# Billing (UseAutumn)
AUTUMN_SECRET_KEY=am_sk_live_<your-production-key>

# Security
ALLOWED_WEB_ORIGINS=https://your-site.netlify.app
ENABLE_HSTS=1
HSTS_PRELOAD=0

# Platform Detection
NETLIFY=true

# Analytics (optional)
NEXT_PUBLIC_POSTHOG_KEY=phc_<your-key>
NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com

Netlify Environment Variable

NETLIFY=true enables platform-specific optimizations in your application code.

# Deploy previews use staging/test credentials
APP_BASE_URL=https://deploy-preview-${REVIEW_ID}--your-site.netlify.app
NEXT_PUBLIC_CONVEX_URL=https://your-preview.convex.cloud
CONVEX_DEPLOYMENT=preview

# Same auth secret, different billing key
BETTER_AUTH_SECRET=<same-as-production>
AUTUMN_SECRET_KEY=am_sk_test_<your-test-key>

# Relaxed security for previews
ENABLE_HSTS=0
HSTS_PRELOAD=0

Pro Tip

Netlify provides REVIEW_ID and other build variables automatically. Use them for dynamic preview URLs.

# Branch deploys for staging/development
APP_BASE_URL=https://${BRANCH}--your-site.netlify.app
NEXT_PUBLIC_CONVEX_URL=https://your-staging.convex.cloud
CONVEX_DEPLOYMENT=staging

# Development-friendly settings
ENABLE_HSTS=0

Variable Scopes in Netlify:

  • All deploys: Shared across production, previews, and branches
  • Production only: Main branch deploys
  • Deploy previews: Pull request deploys
  • Branch deploys: Non-main branch pushes

Install Essential Next.js Plugin

Navigate to Integrations and search for "Essential Next.js."

Click Install to enable:

  • Image optimization via next/image
  • Incremental Static Regeneration (ISR)
  • Middleware and API route support
  • Edge function deployment

Plugin Required

Without this plugin, Next.js advanced features like ISR and image optimization won't work correctly on Netlify.

Deploy

Click Deploy site to trigger the initial build. Netlify will:

  1. Clone repository
  2. Install pnpm via Node Corepack
  3. Run workspace installation
  4. Execute Turbo build
  5. Deploy to CDN
  6. Provision SSL certificate

Initial deploy time: ~4-6 minutes Subsequent deploys: ~2-3 minutes (with build cache)

Update Environment URLs

After deployment, Netlify assigns a URL:

https://<site-name>.netlify.app

Update environment variables:

APP_BASE_URL=https://<site-name>.netlify.app
ALLOWED_WEB_ORIGINS=https://<site-name>.netlify.app

Trigger a new deploy via Deploys → Trigger deploy → Deploy site.

Sync Convex Environment

Mirror credentials to Convex for hosted runtime execution:

# Set production deployment
npx convex deploy --prod

# Configure environment variables
npx convex env set APP_BASE_URL https://<site-name>.netlify.app --prod
npx convex env set DASHBOARD_BASE_URL https://dashboard-<site>.netlify.app --prod
npx convex env set ALLOWED_WEB_ORIGINS https://<site-name>.netlify.app --prod
npx convex env set BETTER_AUTH_SECRET <same-value-as-netlify> --prod
npx convex env set GOOGLE_CLIENT_ID <same-value-as-netlify> --prod
npx convex env set GOOGLE_CLIENT_SECRET <same-value-as-netlify> --prod
npx convex env set AUTUMN_SECRET_KEY <same-value-as-netlify> --prod

Environment Consistency

Rerun these commands whenever you rotate secrets or create new deployment environments.

Configure OAuth Callbacks

Update OAuth provider redirect URIs with Netlify domain:

Custom Domain Setup

Add Custom Domain

Navigate to Domain settings → Add custom domain and enter your domain:

your-domain.com

Netlify automatically suggests adding www subdomain as well.

Configure DNS

Recommended: Transfer DNS management to Netlify for automatic SSL and simplified setup.

Update nameservers at your domain registrar:

dns1.p01.nsone.net
dns2.p01.nsone.net
dns3.p01.nsone.net
dns4.p01.nsone.net

Netlify handles all DNS records automatically.

Keep your existing DNS provider and add records manually:

For apex domain (your-domain.com):

Type: A
Name: @
Value: 75.2.60.5
TTL: 3600

For www subdomain:

Type: CNAME
Name: www
Value: <site-name>.netlify.app
TTL: 3600

For subdomains (dashboard, app, etc.):

Type: CNAME
Name: dashboard
Value: <site-name>.netlify.app
TTL: 3600

Enable HTTPS

Netlify provisions SSL certificates automatically via Let's Encrypt. Wait for verification (~few minutes).

Enable Force HTTPS in Domain settings → HTTPS to redirect all HTTP traffic.

Update Environment Variables

After domain verification, update all environment URLs:

APP_BASE_URL=https://your-domain.com
DASHBOARD_BASE_URL=https://dashboard.your-domain.com
ALLOWED_WEB_ORIGINS=https://your-domain.com,https://www.your-domain.com

Update OAuth provider callbacks and trigger a new deploy.

Edge Functions for Global Performance

Netlify Edge Functions run on Deno Deploy with global distribution:

// netlify/edge-functions/auth.ts
import type { Context } from "@netlify/edge-functions";

export default async (request: Request, context: Context) => {
  // Runs at the edge, closest to users
  const response = await context.next();
  return response;
};

export const config = {
  path: "/api/auth/*",
};
// netlify/edge-functions/hello.ts
export default () => new Response("Hello from the edge!");

export const config = { path: "/api/hello" };

Deploy edge functions automatically with your site. Access at:

https://your-site.netlify.app/api/hello
// netlify/edge-functions/geo.ts
import type { Context } from "@netlify/edge-functions";

export default (request: Request, context: Context) => {
  const country = context.geo.country.code;
  const city = context.geo.city;

  return new Response(
    JSON.stringify({ country, city }),
    { headers: { "Content-Type": "application/json" } }
  );
};
// netlify/edge-functions/ab-test.ts
import type { Context } from "@netlify/edge-functions";

export default async (request: Request, context: Context) => {
  const variant = Math.random() > 0.5 ? "A" : "B";

  context.cookies.set({
    name: "ab_variant",
    value: variant,
    path: "/",
    secure: true,
    sameSite: "Lax",
  });

  return context.next();
};

Form Handling Without Servers

Netlify processes forms automatically with zero backend code:

// app/contact/page.tsx
export default function ContactPage() {
  return (
    <form
      name="contact"
      method="POST"
      data-netlify="true"
      netlify-honeypot="bot-field"
    >
      <input type="hidden" name="form-name" value="contact" />
      <input type="hidden" name="bot-field" />

      <label>
        Name:
        <input type="text" name="name" required />
      </label>

      <label>
        Email:
        <input type="email" name="email" required />
      </label>

      <label>
        Message:
        <textarea name="message" required />
      </label>

      <button type="submit">Send</button>
    </form>
  );
}

Deploy Previews for Confident Shipping

Every pull request receives a unique deployment URL for isolated testing.

Pro Tip

Deploy preview URLs follow the pattern: https://deploy-preview-<PR-number>--<site-name>.netlify.app

Configure Deploy Previews

# netlify.toml
[context.deploy-preview]
  command = "cd ../.. && pnpm turbo run build --filter=marketing"

[context.deploy-preview.environment]
  NEXT_PUBLIC_ENVIRONMENT = "preview"
  ENABLE_HSTS = "0"

Branch-Specific Configuration

# netlify.toml
[context.staging]
  command = "cd ../.. && pnpm turbo run build --filter=marketing"

[context.staging.environment]
  NEXT_PUBLIC_ENVIRONMENT = "staging"
  NEXT_PUBLIC_CONVEX_URL = "https://staging.convex.cloud"

Performance Optimization

Configure caching for optimal performance:

// next.config.ts
export default {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
    ];
  },
};

Netlify's CDN caches static assets globally.

Enable automatic asset optimization in Site settings → Build & deploy → Asset optimization:

  • CSS minification: Enabled
  • JS minification: Enabled (with source maps)
  • Image optimization: Enabled via Essential Next.js plugin
  • Pretty URLs: Enabled (optional)

Install performance-enhancing plugins:

# netlify.toml
[[plugins]]
  package = "@netlify/plugin-lighthouse"

[[plugins]]
  package = "netlify-plugin-cache-nextjs"

[[plugins]]
  package = "netlify-plugin-inline-critical-css"

Plugins run during build to optimize output.

Monitoring and Analytics

Troubleshooting

Advanced Configuration

[build]
  base = "apps/marketing"
  command = "cd ../.. && pnpm turbo run build --filter=marketing"
  publish = "apps/marketing/.next"

[build.environment]
  NODE_VERSION = "20.18.1"
  NPM_FLAGS = "--frozen-lockfile"

[[redirects]]
  from = "/old-path"
  to = "/new-path"
  status = 301

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-Content-Type-Options = "nosniff"
    Referrer-Policy = "strict-origin-when-cross-origin"

[functions]
  node_bundler = "esbuild"
# netlify.toml
[[redirects]]
  from = "https://www.your-domain.com/*"
  to = "https://your-domain.com/:splat"
  status = 301
  force = true

[[redirects]]
  from = "/api/*"
  to = "/.netlify/functions/:splat"
  status = 200
# netlify.toml
[[headers]]
  for = "/api/*"
  [headers.values]
    Access-Control-Allow-Origin = "*"
    Access-Control-Allow-Methods = "GET, POST, OPTIONS"

[[headers]]
  for = "/*.js"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"