Netlify Platform Skill
progressive_disclosure: entry_point: summary: "JAMstack deployment platform with serverless functions, forms, and identity" when_to_use:
- "When deploying static sites and SPAs"
- "When building JAMstack applications"
- "When needing serverless functions"
- "When requiring built-in forms and auth" quick_start:
- "npm install -g netlify-cli"
- "netlify login"
- "netlify init"
- "netlify deploy --prod" token_estimate: entry: 70-85 full: 3800-4800
Netlify Fundamentals
Core Concepts
-
Sites: Static sites deployed to Netlify's global CDN
-
Builds: Automated build process triggered by Git commits
-
Deploy Contexts: production, deploy-preview, branch-deploy
-
Atomic Deploys: All-or-nothing deployments with instant rollback
-
Instant Cache Invalidation: CDN cache cleared automatically
Platform Benefits
-
Global CDN: Built-in content delivery network
-
Continuous Deployment: Auto-deploy from Git
-
HTTPS by Default: Free SSL certificates
-
Deploy Previews: Preview every pull request
-
Serverless Functions: Backend logic without servers
-
Forms & Identity: Built-in features for common needs
Static Site Deployment
Supported Frameworks
React (Create React App, Vite)
Build command: npm run build Publish directory: build (CRA) or dist (Vite)
Next.js (Static Export)
Build command: npm run build && npm run export Publish directory: out
Vue.js
Build command: npm run build Publish directory: dist
Gatsby
Build command: gatsby build Publish directory: public
Hugo
Build command: hugo Publish directory: public
Svelte/SvelteKit
Build command: npm run build Publish directory: build
Manual Deployment
Install Netlify CLI
npm install -g netlify-cli
Login to Netlify
netlify login
Initialize site
netlify init
Deploy draft (preview URL)
netlify deploy
Deploy to production
netlify deploy --prod
Deploy with build
netlify deploy --build --prod
netlify.toml Configuration
Basic Configuration
netlify.toml
[build]
Build command
command = "npm run build"
Publish directory
publish = "dist"
Functions directory
functions = "netlify/functions"
Production context
[context.production] command = "npm run build:prod"
[context.production.environment] NODE_ENV = "production" API_URL = "https://api.example.com"
Deploy Preview context
[context.deploy-preview] command = "npm run build:preview"
Branch deploys
[context.branch-deploy] command = "npm run build"
Build Settings
[build] command = "npm run build" publish = "dist"
Base directory for monorepos
base = "packages/web"
Ignore builds on certain changes
ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF packages/web"
[build.environment] NODE_VERSION = "18" NPM_VERSION = "9" RUBY_VERSION = "3.1"
Advanced Build Configuration
[build] command = "npm run build" publish = "dist"
Build processing
[build.processing] skip_processing = false [build.processing.css] bundle = true minify = true [build.processing.js] bundle = true minify = true [build.processing.images] compress = true
Environment Variables
Setting Variables
Via CLI
netlify env:set API_KEY "secret-value" netlify env:set NODE_ENV "production"
List variables
netlify env:list
Import from .env file
netlify env:import .env
Variable Scopes
netlify.toml
[context.production.environment] API_URL = "https://api.production.com" ENABLE_ANALYTICS = "true"
[context.deploy-preview.environment] API_URL = "https://api.staging.com" ENABLE_ANALYTICS = "false"
[context.branch-deploy.environment] API_URL = "https://api.dev.com"
Accessing in Build
// During build const apiUrl = process.env.API_URL;
// Client-side (must be prefixed) const publicKey = process.env.REACT_APP_PUBLIC_KEY;
Serverless Functions
Function Structure
// netlify/functions/hello.js exports.handler = async (event, context) => { return { statusCode: 200, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message: 'Hello from Netlify Function', path: event.path, method: event.httpMethod, }), }; };
TypeScript Functions
// netlify/functions/api.ts import { Handler, HandlerEvent, HandlerContext } from '@netlify/functions';
interface RequestBody { name: string; email: string; }
export const handler: Handler = async ( event: HandlerEvent, context: HandlerContext ) => { if (event.httpMethod !== 'POST') { return { statusCode: 405, body: 'Method Not Allowed', }; }
const { name, email }: RequestBody = JSON.parse(event.body || '{}');
return {
statusCode: 200,
body: JSON.stringify({
message: Hello ${name},
email,
}),
};
};
Advanced Function Patterns
// netlify/functions/database.js const { MongoClient } = require('mongodb');
let cachedDb = null;
async function connectToDatabase() { if (cachedDb) return cachedDb;
const client = await MongoClient.connect(process.env.MONGODB_URI); cachedDb = client.db(); return cachedDb; }
exports.handler = async (event) => { const db = await connectToDatabase(); const users = await db.collection('users').find({}).toArray();
return { statusCode: 200, body: JSON.stringify(users), }; };
Scheduled Functions
// netlify/functions/scheduled.js const { schedule } = require('@netlify/functions');
const handler = async () => { console.log('Running scheduled task');
// Your scheduled logic await performBackup();
return { statusCode: 200, }; };
// Run every day at midnight exports.handler = schedule('0 0 * * *', handler);
Background Functions
// netlify/functions/background-task.js // Auto-runs in background if response is 200 within 10s exports.handler = async (event) => { // Long-running task await processLargeDataset();
return { statusCode: 200, }; };
// Invoke: POST to /.netlify/functions/background-task
Netlify Forms
HTML Form
<!-- Contact form --> <form name="contact" method="POST" data-netlify="true"> <input type="hidden" name="form-name" value="contact" />
<label>Name: <input type="text" name="name" required /></label> <label>Email: <input type="email" name="email" required /></label> <label>Message: <textarea name="message" required></textarea></label>
<button type="submit">Send</button> </form>
React Form
// ContactForm.jsx import { useState } from 'react';
export default function ContactForm() { const [formData, setFormData] = useState({ name: '', email: '', message: '', });
const handleSubmit = async (e) => { e.preventDefault();
const response = await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
'form-name': 'contact',
...formData,
}).toString(),
});
if (response.ok) {
alert('Thank you for your message!');
}
};
return ( <form name="contact" onSubmit={handleSubmit} data-netlify="true"> <input type="hidden" name="form-name" value="contact" /> {/* Form fields */} </form> ); }
Form Features
<!-- Spam filtering with honeypot --> <form name="contact" method="POST" data-netlify="true" data-netlify-honeypot="bot-field"> <input type="hidden" name="form-name" value="contact" /> <p class="hidden"> <label>Don't fill this out: <input name="bot-field" /></label> </p> <!-- Form fields --> </form>
<!-- reCAPTCHA v2 --> <form name="contact" method="POST" data-netlify="true" data-netlify-recaptcha="true"> <div data-netlify-recaptcha="true"></div> <button type="submit">Submit</button> </form>
<!-- File uploads --> <form name="file-upload" method="POST" data-netlify="true" enctype="multipart/form-data"> <input type="file" name="file" /> <button type="submit">Upload</button> </form>
Form Notifications
netlify.toml
[[plugins]] package = "@netlify/plugin-emails"
[plugins.inputs] formName = "contact" to = "admin@example.com" subject = "New contact form submission"
Netlify Identity
Enable Identity
// Add to HTML <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
// Initialize if (window.netlifyIdentity) { window.netlifyIdentity.on('init', user => { if (!user) { window.netlifyIdentity.on('login', () => { document.location.href = '/admin/'; }); } }); }
React Integration
// useNetlifyIdentity.js import { useEffect, useState } from 'react';
export function useNetlifyIdentity() { const [user, setUser] = useState(null);
useEffect(() => { const netlifyIdentity = window.netlifyIdentity;
netlifyIdentity.on('init', user => setUser(user));
netlifyIdentity.on('login', user => setUser(user));
netlifyIdentity.on('logout', () => setUser(null));
netlifyIdentity.init();
}, []);
return { user, login: () => window.netlifyIdentity.open('login'), logout: () => window.netlifyIdentity.logout(), signup: () => window.netlifyIdentity.open('signup'), }; }
Protected Functions
// netlify/functions/protected.js exports.handler = async (event, context) => { const { user } = context.clientContext;
if (!user) { return { statusCode: 401, body: 'Unauthorized', }; }
return { statusCode: 200, body: JSON.stringify({ message: 'Protected data', user: user.email, }), }; };
Redirects and Rewrites
_redirects File
_redirects file in publish directory
Redirect with status code
/old-path /new-path 301
Rewrite (proxy)
/api/* https://api.example.com/:splat 200
SPA fallback
/* /index.html 200
Force HTTPS
http://example.com/* https://example.com/:splat 301!
Conditional redirects
/news/* /blog/:splat 302 Country=us
Role-based redirects
/admin/* /admin/dashboard 200! Role=admin /admin/* /unauthorized 401
netlify.toml Redirects
[[redirects]] from = "/old-path" to = "/new-path" status = 301
[[redirects]] from = "/api/*" to = "https://api.example.com/:splat" status = 200 force = true
[[redirects]] from = "/*" to = "/index.html" status = 200
[[redirects]] from = "/admin/*" to = "/admin/dashboard" status = 200 conditions = {Role = ["admin"]}
Proxy with headers
[[redirects]] from = "/proxy/*" to = "https://backend.com/:splat" status = 200 force = true [redirects.headers] X-From = "Netlify"
Custom Headers
[[headers]] for = "/*" [headers.values] X-Frame-Options = "DENY" X-XSS-Protection = "1; mode=block" Content-Security-Policy = "default-src 'self'"
[[headers]] for = "/assets/*" [headers.values] Cache-Control = "public, max-age=31536000, immutable"
Deploy Previews
Automatic Preview URLs
netlify.toml
[context.deploy-preview] command = "npm run build:preview"
[context.deploy-preview.environment] NODE_ENV = "preview" API_URL = "https://api-staging.example.com"
Branch Deploys
Deploy specific branches
[context.staging] command = "npm run build:staging"
[context.staging.environment] API_URL = "https://api-staging.example.com"
Branch pattern matching
[context.branch-deploy] command = "npm run build"
Deploy Notifications
GitHub PR comments
Slack notifications
Email notifications
Configured in Netlify UI
Split Testing (A/B Testing)
Configuration
netlify.toml
[[redirects]] from = "/*" to = "/version-a/:splat" status = 200 conditions = {Cookie = ["ab_test=a"]} force = true
[[redirects]] from = "/*" to = "/version-b/:splat" status = 200 conditions = {Cookie = ["ab_test=b"]} force = true
50/50 split
[[redirects]] from = "/*" to = "/version-a/:splat" status = 200! percentage = 50
[[redirects]] from = "/*" to = "/version-b/:splat" status = 200!
Edge Functions
Deno Runtime
// netlify/edge-functions/hello.ts import type { Context } from "https://edge.netlify.com";
export default async (request: Request, context: Context) => { const url = new URL(request.url);
return new Response(Hello from ${url.pathname}, {
headers: { "content-type": "text/html" },
});
};
export const config = { path: "/hello" };
Geolocation
// netlify/edge-functions/geo.ts import type { Context } from "https://edge.netlify.com";
export default async (request: Request, context: Context) => { const { city, country } = context.geo;
return Response.json({
location: ${city}, ${country},
});
};
Transform Response
// netlify/edge-functions/transform.ts import type { Context } from "https://edge.netlify.com";
export default async (request: Request, context: Context) => { const response = await context.next(); const text = await response.text();
// Modify HTML const modified = text.replace( '</body>', '<script>console.log("Injected by edge");</script></body>' );
return new Response(modified, response); };
export const config = { path: "/*" };
Custom Domains and SSL
Add Custom Domain
Via CLI
netlify domains:add example.com
DNS Configuration
A record: 75.2.60.5
CNAME: <site-name>.netlify.app
Verify domain
netlify domains:verify example.com
SSL Certificates
Automatic HTTPS (default)
Free Let's Encrypt certificates
Auto-renewal
Force HTTPS redirect
[[redirects]] from = "http://example.com/*" to = "https://example.com/:splat" status = 301 force = true
Analytics
Netlify Analytics
<!-- Automatically injected, no code needed --> <!-- Server-side analytics, no client-side JS -->
Custom Analytics
// Track custom events function trackEvent(eventName, data) { fetch('/.netlify/functions/analytics', { method: 'POST', body: JSON.stringify({ event: eventName, ...data }), }); }
trackEvent('button_click', { button: 'signup' });
CLI Advanced Usage
Development Server
Run functions locally
netlify dev
Specific port
netlify dev --port 3000
Live session sharing
netlify dev --live
Functions only
netlify functions:serve
Site Management
Link existing site
netlify link
Create new site
netlify sites:create
List sites
netlify sites:list
Site info
netlify status
Open site in browser
netlify open
Deploy Management
List deploys
netlify deploy:list
Rollback to previous deploy
netlify rollback
Cancel deploy
netlify deploy:cancel <deploy-id>
Git Integration
Continuous Deployment
netlify.toml
[build] command = "npm run build" publish = "dist"
Auto-publish on Git push
Production: main/master branch
Previews: all pull requests
Branch deploys: configured branches
Deploy Hooks
Trigger builds via webhook
curl -X POST -d {} https://api.netlify.com/build_hooks/<hook-id>
Scheduled builds (use external cron + webhook)
Best Practices
Performance Optimization
Enable processing
[build.processing] skip_processing = false
[build.processing.css] bundle = true minify = true
[build.processing.js] bundle = true minify = true
[build.processing.images] compress = true
Asset optimization
[[headers]] for = "/assets/*" [headers.values] Cache-Control = "public, max-age=31536000, immutable"
Security Headers
[[headers]] for = "/*" [headers.values] X-Frame-Options = "DENY" X-Content-Type-Options = "nosniff" X-XSS-Protection = "1; mode=block" Referrer-Policy = "strict-origin-when-cross-origin" Permissions-Policy = "geolocation=(), microphone=(), camera=()" Content-Security-Policy = """ default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; """
Function Best Practices
// Keep functions lightweight // Use connection pooling // Cache external API responses // Set appropriate timeouts // Handle errors gracefully
exports.handler = async (event) => { try { // Set timeout const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 8000);
const response = await fetch(API_URL, {
signal: controller.signal,
});
clearTimeout(timeout);
return {
statusCode: 200,
body: JSON.stringify(await response.json()),
};
} catch (error) { console.error('Function error:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: 'Internal server error' }),
};
} };
Build Optimization
[build] command = "npm run build" publish = "dist"
Skip builds when not needed
ignore = """ git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF -- . ':(exclude)docs/' ':(exclude)*.md' """
Cache dependencies
[build.environment] NPM_FLAGS = "--legacy-peer-deps" NODE_OPTIONS = "--max-old-space-size=4096"
Monorepo Support
netlify.toml
[build] base = "packages/web" command = "npm run build" publish = "dist"
Only build when package changes
ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF packages/web"
Common Patterns
SPA with API Proxy
[build] command = "npm run build" publish = "build" functions = "netlify/functions"
[[redirects]] from = "/api/*" to = "/.netlify/functions/:splat" status = 200
[[redirects]] from = "/*" to = "/index.html" status = 200
Microsite with Subfolder
[[redirects]] from = "/blog/*" to = "https://blog.example.com/:splat" status = 200 force = true
Authentication Gateway
[[redirects]] from = "/app/*" to = "/app/dashboard" status = 200 conditions = {Role = ["user"]}
[[redirects]] from = "/app/*" to = "/login" status = 302
Summary: Netlify provides a complete JAMstack platform with static hosting, serverless functions, forms, and identity management. Key strengths include atomic deploys, instant cache invalidation, deploy previews, and built-in CDN. Configure via netlify.toml for builds, redirects, headers, and environment-specific settings. Leverage serverless functions for backend logic, forms for user input, and Edge Functions for edge computing. Best practices include performance optimization, security headers, and efficient build configurations.