netlify Best Practices
Netlify is the definitive platform for the Jamstack. Our workflow prioritizes pre-built, edge-first deployments, ensuring maximum performance, security, and developer experience. These guidelines enforce a consistent, high-quality approach to building for the modern web on Netlify.
1. Code Organization and Build Configuration
Always centralize your build logic and deployment settings in netlify.toml. This ensures consistency and reproducibility across environments and team members.
✅ GOOD: Standardized netlify.toml
# netlify.toml
[build]
command = "npm run build" # Your project's build command
publish = "dist" # Directory containing your built static assets
functions = "netlify/functions" # Directory for Netlify Edge/Serverless Functions
# Essential redirects for SPAs or SEO
[[redirects]]
from = "/old-path"
to = "/new-path"
status = 301
[[redirects]]
from = "/*"
to = "/index.html"
status = 200 # For Single Page Applications (SPAs)
# Security headers for all responses
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "no-referrer-when-downgrade"
Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';"
Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"
❌ BAD: Relying on UI settings or missing critical configurations
# netlify.toml (incomplete)
[build]
command = "npm run build"
publish = "build"
# Missing redirects, functions path, and crucial security headers.
# This leads to inconsistent deployments, SEO issues, and security vulnerabilities.
2. Environment Variables and Secrets Management
Never hardcode sensitive information. Use Netlify's encrypted environment variables for all secrets. Differentiate between build-time and runtime variables.
✅ GOOD: Secure Environment Variables
// netlify/functions/api.js
import fetch from 'node-fetch';
export async function handler(event, context) {
const API_KEY = process.env.MY_EXTERNAL_API_KEY; // Fetched securely at runtime
if (!API_KEY) {
return { statusCode: 500, body: 'API Key not configured.' };
}
// ... use API_KEY
}
# In Netlify UI or netlify.toml (for build-time only, generally prefer UI for secrets)
# netlify.toml
[build.environment]
# Only for variables needed during the build process, e.g., for a static site generator
GATSBY_API_URL = "https://api.example.com/public"
❌ BAD: Hardcoding secrets or exposing them in client-side bundles
// src/components/MyComponent.js
const API_KEY = "sk_YOUR_SECRET_KEY_HERE"; // NEVER DO THIS!
// This key will be bundled with your client-side code and exposed.
3. Performance Optimization
Leverage Netlify's edge network and build process for maximum speed.
✅ GOOD: Image Optimization with Build Plugins
Use Netlify Build Plugins for automated image optimization.
# netlify.toml
[[plugins]]
package = "@netlify/plugin-nextjs" # Example for Next.js, includes image optimization
# Or a dedicated image optimization plugin:
# package = "@netlify/plugin-image-optim"
❌ BAD: Serving unoptimized, large images
<!-- src/index.html -->
<img src="/images/hero-lg.jpg" alt="Hero" />
<!-- This image is likely too large, not responsive, and not optimized for web. -->
4. Edge Functions for Dynamic Logic
Move lightweight server-side logic to the edge with Netlify Edge Functions for faster response times and reduced latency.
✅ GOOD: Edge Function for A/B Testing or Geo-targeting
// netlify/edge-functions/geo-redirect.ts
import type { Context } from "https://edge.netlify.com/";
export default async (request: Request, context: Context) => {
const country = context.geo?.country?.name || "Unknown";
if (country === "Germany") {
return Response.redirect(new URL("/de", request.url));
}
// Continue to the original page
return context.next();
};
❌ BAD: Relying on traditional serverless functions for every request
// netlify/functions/slow-geo-check.js (traditional serverless function)
exports.handler = async (event, context) => {
// This will incur higher latency than an Edge Function for simple routing logic.
const ip = event.headers['x-forwarded-for'];
// ... complex IP-to-geo lookup (slow)
return { statusCode: 200, body: `Hello from ${country}` };
};
5. Testing and CI/CD Hygiene
Integrate automated testing and quality checks into your Netlify build pipeline.
✅ GOOD: Automated Linting, Type Checking, and E2E Tests
Ensure your package.json scripts support these, and Netlify's build command triggers them.
// package.json
{
"scripts": {
"build": "npm run lint && npm run typecheck && next build",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"typecheck": "tsc --noEmit",
"test:e2e": "cypress run"
}
}
# netlify.toml
[build]
command = "npm run build" # This will run lint and typecheck before the actual build
publish = "out"
[context.deploy-preview]
command = "npm run build && npm run test:e2e" # Run E2E tests on deploy previews
❌ BAD: Skipping quality checks in CI
# netlify.toml
[build]
command = "next build" # No linting, type checking, or tests before deployment.
publish = "out"
# This allows broken code to reach deploy previews or even production.
6. Accessibility
Accessibility is a non-negotiable part of modern web development. Integrate tools to enforce it.
✅ GOOD: Automated Accessibility Checks
Use tools like Lighthouse CI or Pa11y in your build process.
// package.json
{
"scripts": {
"build": "next build",
"audit:a11y": "lighthouse ci --config=./.lighthouseci.json"
}
}
# netlify.toml
[build]
command = "npm run build"
publish = "out"
[context.production]
command = "npm run build && npm run audit:a11y" # Run a11y audit on production builds
❌ BAD: Ignoring accessibility until manual review
<!-- src/components/ImageGallery.js -->
<img src="image.jpg" />
<!-- Missing `alt` attribute, leading to accessibility issues. -->
7. Decoupled Architecture and API-First Development
Embrace the Jamstack philosophy: frontend decoupled from backend. Interact with services via APIs.
✅ GOOD: Consuming external APIs
// src/utils/dataService.js
export async function fetchProducts() {
const response = await fetch('/.netlify/functions/getProducts'); // Call a Netlify Function acting as an API gateway
if (!response.ok) {
throw new Error('Failed to fetch products');
}
return response.json();
}
❌ BAD: Tightly coupled frontend and backend
// src/server.js (monolithic approach)
// This file would not run on Netlify's static hosting.
// Attempting to deploy a traditional server-side app directly to Netlify will fail.
const express = require('express');
const app = express();
// ... server-side logic