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:
- Clone repository
- Install pnpm via Node Corepack
- Run workspace installation
- Execute Turbo build
- Deploy to CDN
- 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.
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"