netlify-deployment
Complete guide to deploying web applications on Netlify, covering build settings, deploy previews, serverless and edge functions, forms, identity, redirects and rewrites, split testing, and environment variable management for production workflows.
The primary configuration file lives at the project root: ## Key Points 1. Deploy your variant to a separate Git branch. 2. In Netlify dashboard: Site settings > Split testing. 3. Assign traffic percentages to branches. - **All** (default) - **Production** - **Deploy previews** - **Branch deploys** - **Local development** 1. **Not using `netlify.toml` for redirects**: Dashboard redirects are harder to version control and review. 2. **Forgetting `force = true` on proxied rewrites**: Without it, Netlify serves local files first. 3. **Using Netlify Identity for large user bases**: It's designed for small teams, not consumer auth. Use Auth0 or Clerk instead. 4. **Ignoring deploy context overrides**: Running production builds in preview wastes build minutes and exposes production secrets. ## Quick Example ``` https://deploy-preview-42--my-site.netlify.app ``` ``` /old-blog/* /blog/:splat 301 /api/* https://api.example.com/:splat 200 /* /index.html 200 ```
skilldb get deployment-patterns-skills/netlify-deploymentFull skill: 399 linesDeploying to Netlify
Build Settings and Configuration
netlify.toml
The primary configuration file lives at the project root:
[build]
command = "npm run build"
publish = "dist"
functions = "netlify/functions"
edge_functions = "netlify/edge-functions"
[build.environment]
NODE_VERSION = "20"
NPM_FLAGS = "--prefix=/dev/null"
# Production-specific overrides
[context.production]
command = "npm run build:production"
environment = { NODE_ENV = "production" }
# Deploy preview overrides
[context.deploy-preview]
command = "npm run build:preview"
# Branch-specific overrides
[context.staging]
command = "npm run build:staging"
Framework-Specific Settings
Netlify auto-detects frameworks, but be explicit:
# For Astro
[build]
command = "astro build"
publish = "dist"
# For SvelteKit
[build]
command = "vite build"
publish = "build"
# For Remix
[build]
command = "remix build"
publish = "public"
Anti-pattern: Relying solely on auto-detection. When Netlify misidentifies your framework, your build silently produces wrong output.
Deploy Previews
Every pull request gets an automatic deploy preview:
https://deploy-preview-42--my-site.netlify.app
Configuring Deploy Previews
[context.deploy-preview]
command = "npm run build:preview"
[context.deploy-preview.environment]
API_URL = "https://staging-api.example.com"
ENABLE_DEBUG = "true"
Deploy Notifications
[[plugins]]
package = "netlify-plugin-checklinks"
# Slack notification on deploy
[[notifications]]
type = "slack"
event = "deploy_succeeded"
channel = "#deploys"
Use the Netlify GitHub integration for PR comments with preview links and deploy status.
Serverless Functions
Basic Function
// netlify/functions/hello.js
export default async (req, context) => {
const name = new URL(req.url).searchParams.get('name') || 'World';
return new Response(JSON.stringify({ message: `Hello, ${name}` }), {
headers: { 'Content-Type': 'application/json' },
});
};
export const config = {
path: '/api/hello',
};
Scheduled Functions
// netlify/functions/daily-cleanup.js
export default async () => {
await cleanupExpiredSessions();
return new Response('Cleanup complete');
};
export const config = {
schedule: '@daily', // cron expressions also work: "0 0 * * *"
};
Background Functions
Long-running tasks (up to 15 minutes):
// netlify/functions/process-video-background.js
export default async (req) => {
const { videoId } = await req.json();
await processVideo(videoId); // Can take up to 15 min
return new Response('Processing complete');
};
export const config = {
path: '/api/process-video',
method: 'POST',
preferStatic: true,
};
Anti-pattern: Using synchronous functions for tasks over 10 seconds. Use background functions for heavy processing.
Edge Functions
Run at the CDN edge with Deno runtime:
// netlify/edge-functions/geolocation.ts
import type { Context } from '@netlify/edge-functions';
export default async (request: Request, context: Context) => {
const { country, city } = context.geo;
// Modify the response
const response = await context.next();
response.headers.set('x-country', country?.code || 'unknown');
return response;
};
export const config = {
path: '/api/geo',
};
Edge Function for A/B Testing
// netlify/edge-functions/ab-test.ts
import type { Context } from '@netlify/edge-functions';
export default async (request: Request, context: Context) => {
const cookie = request.headers.get('cookie') || '';
let variant = cookie.includes('ab_variant=b') ? 'b' : null;
if (!variant) {
variant = Math.random() < 0.5 ? 'a' : 'b';
}
const url = new URL(request.url);
if (variant === 'b') {
url.pathname = `/variants/b${url.pathname}`;
}
const response = await context.rewrite(url);
if (!cookie.includes('ab_variant=')) {
response.headers.append('set-cookie', `ab_variant=${variant}; path=/; max-age=86400`);
}
return response;
};
Forms
Netlify detects HTML forms automatically:
<form name="contact" method="POST" data-netlify="true" netlify-honeypot="bot-field">
<input type="hidden" name="form-name" value="contact" />
<p class="hidden"><label>Ignore: <input name="bot-field" /></label></p>
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
AJAX Form Submission
async function handleSubmit(event) {
event.preventDefault();
const form = event.target;
const data = new FormData(form);
const response = await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(data).toString(),
});
if (response.ok) {
window.location.href = '/thank-you';
}
}
Anti-pattern: Using Netlify Forms for high-volume submissions. The free tier limits to 100/month. Use a dedicated form service for heavy traffic.
Redirects and Rewrites
In netlify.toml
# Simple redirect
[[redirects]]
from = "/old-path"
to = "/new-path"
status = 301
# SPA fallback
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
# Proxy to external API (hides CORS issues)
[[redirects]]
from = "/api/*"
to = "https://api.example.com/:splat"
status = 200
force = true
# Country-based redirect
[[redirects]]
from = "/*"
to = "/uk/:splat"
status = 302
conditions = { Country = ["GB"] }
# Role-based redirect (with Netlify Identity)
[[redirects]]
from = "/admin/*"
to = "/admin/:splat"
status = 200
conditions = { Role = ["admin"] }
force = true
_redirects File
Place in your publish directory for simpler syntax:
/old-blog/* /blog/:splat 301
/api/* https://api.example.com/:splat 200
/* /index.html 200
Anti-pattern: Placing _redirects in the project root instead of the publish directory. It gets ignored silently.
Headers
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline'"
[[headers]]
for = "/fonts/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*.js"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
Split Testing
Split testing sends a percentage of traffic to different branches:
- Deploy your variant to a separate Git branch.
- In Netlify dashboard: Site settings > Split testing.
- Assign traffic percentages to branches.
main: 70%
redesign: 30%
Split tests use a cookie to keep users on their assigned branch. This is branch-level, not page-level.
Environment Variable Management
# Via Netlify CLI
netlify env:set DATABASE_URL "postgresql://..." --context production
netlify env:set DATABASE_URL "postgresql://..." --context deploy-preview
netlify env:list
netlify env:get DATABASE_URL
netlify env:unset DEPRECATED_VAR
# Pull to local
netlify env:pull .env
Scoping
Variables can be scoped to:
- All (default)
- Production
- Deploy previews
- Branch deploys
- Local development
Build Plugins for Environment Setup
[[plugins]]
package = "@netlify/plugin-nextjs"
[[plugins]]
package = "netlify-plugin-inline-critical-css"
Netlify CLI Essentials
# Install and login
npm install -g netlify-cli
netlify login
# Link to existing site
netlify link
# Local dev with Netlify features (functions, redirects, etc.)
netlify dev
# Manual deploy (useful for CI or testing)
netlify deploy --dir=dist # Draft deploy
netlify deploy --dir=dist --prod # Production deploy
# Trigger a new build
netlify build
Common Anti-Patterns
- Not using
netlify.tomlfor redirects: Dashboard redirects are harder to version control and review. - Forgetting
force = trueon proxied rewrites: Without it, Netlify serves local files first. - Using Netlify Identity for large user bases: It's designed for small teams, not consumer auth. Use Auth0 or Clerk instead.
- Ignoring deploy context overrides: Running production builds in preview wastes build minutes and exposes production secrets.
- Not pinning Node.js version: Netlify defaults can change. Always set
NODE_VERSIONinnetlify.toml.
Deployment Checklist
-
netlify.tomlcommitted with explicit build command and publish directory - Node.js version pinned in build environment
- Redirects and headers configured and tested
- Environment variables scoped to correct contexts
- Deploy previews verified for PR workflow
- Forms tested with honeypot spam protection
- Functions tested locally with
netlify dev - Custom domain configured with DNS verification
Install this skill directly: skilldb add deployment-patterns-skills
Related Skills
database-deployment
Comprehensive guide to database deployment for web applications, covering managed database services (PlanetScale, Neon, Supabase, Turso), migration strategies, connection pooling, backup and restore procedures, data seeding, and schema management best practices for production environments.
docker-deployment
Comprehensive guide to using Docker for production deployments, covering multi-stage builds, .dockerignore optimization, layer caching strategies, health checks, Docker Compose for local development, container registries, and security scanning best practices.
fly-io-deployment
Complete guide to deploying applications on Fly.io, covering flyctl CLI usage, Dockerfile-based deployments, fly.toml configuration, persistent volumes, horizontal and vertical scaling, multi-region deployments, managed Postgres and Redis, private networking, and auto-scaling strategies.
github-actions-cd
Comprehensive guide to implementing continuous deployment with GitHub Actions, covering deploy workflows, environment protection rules, secrets management, matrix builds, dependency caching, artifact management, and deploying to multiple targets including Vercel, Fly.io, AWS, and container registries.
monitoring-post-deploy
Comprehensive guide to post-deployment monitoring for web applications, covering uptime checks, error tracking with Sentry, application performance monitoring, log aggregation, alerting strategies, public status pages, and incident response procedures for production systems.
railway-deployment
Complete guide to deploying applications on Railway, covering project setup, environment variable management, services and databases (Postgres, Redis, MySQL), persistent volumes, monorepo support, private networking between services, and scheduled cron jobs.