Skip to main content
Technology & EngineeringDeployment Hosting Services355 lines

Netlify Deployment

Netlify platform expertise — static/SSR deployment, serverless functions, forms, identity, build plugins, redirects, and split testing

Quick Summary18 lines
Netlify pioneered the Jamstack architecture: pre-render as much as possible, serve from a CDN, and enhance with serverless functions. The platform treats deployments as atomic and immutable — every deploy is a complete snapshot that can be instantly rolled back. Build plugins extend the pipeline, forms work without a backend, and identity provides authentication out of the box. The platform rewards a "pre-render first, enhance second" mindset.

## Key Points

- **Use `_redirects` or `netlify.toml` for routing** — define redirects declaratively rather than in application code for CDN-level performance.
- **Leverage deploy previews for every PR** — each pull request gets a unique URL for testing; add Lighthouse CI and visual regression checks.
- **Use background functions for long tasks** — name the file with `-background` suffix to get 15-minute execution instead of 10 seconds.
- **Cache aggressively in build plugins** — restore and save heavy dependencies (Prisma, sharp) between builds to cut build times.
- **Set context-specific config** — use `[context.production]` and `[context.deploy-preview]` blocks to vary build commands and environment variables.
- **Enable form spam filtering** — use the honeypot attribute (`netlify-honeypot`) and enable Akismet in the Netlify dashboard.
- **Use instant rollbacks** — if a production deploy breaks, roll back to any previous deploy in seconds from the dashboard.
- **Pin Node.js version** — set `NODE_VERSION` in `netlify.toml` to avoid surprises from default version changes.
- **Putting all logic in a single function** — each function file becomes its own endpoint. Split concerns into separate function files for independent scaling and debugging.
- **Ignoring redirect order** — redirects are matched top-to-bottom. Placing the SPA catch-all (`/* /index.html 200`) before specific redirects will swallow them.
- **Expecting persistent filesystem** — functions run in ephemeral containers. Never write to disk expecting persistence; use a database or Netlify Blobs.
- **Overusing edge functions for heavy computation** — edge functions have limited CPU time and memory. Keep them lightweight; offload heavy work to background functions.
skilldb get deployment-hosting-services-skills/Netlify DeploymentFull skill: 355 lines
Paste into your CLAUDE.md or agent config

Netlify Deployment

Core Philosophy

Netlify pioneered the Jamstack architecture: pre-render as much as possible, serve from a CDN, and enhance with serverless functions. The platform treats deployments as atomic and immutable — every deploy is a complete snapshot that can be instantly rolled back. Build plugins extend the pipeline, forms work without a backend, and identity provides authentication out of the box. The platform rewards a "pre-render first, enhance second" mindset.

Setup

Project Configuration

// netlify.toml — primary configuration file at repo root
// [build]
//   command = "npm run build"
//   publish = "dist"
//   functions = "netlify/functions"
//
// [build.environment]
//   NODE_VERSION = "20"
//   NPM_FLAGS = "--legacy-peer-deps"
//
// [[redirects]]
//   from = "/api/*"
//   to = "/.netlify/functions/:splat"
//   status = 200
//
// [[headers]]
//   for = "/*"
//   [headers.values]
//     X-Frame-Options = "DENY"
//     X-Content-Type-Options = "nosniff"

// netlify.toml context overrides
// [context.production]
//   command = "npm run build:prod"
//   environment = { API_URL = "https://api.example.com" }
//
// [context.deploy-preview]
//   command = "npm run build:preview"
//   environment = { API_URL = "https://staging-api.example.com" }

CLI Setup

// Install CLI and link site
// $ npm i -g netlify-cli
// $ netlify login
// $ netlify link
// $ netlify dev  — local development server with functions

// Deploy draft
// $ netlify deploy

// Deploy to production
// $ netlify deploy --prod

Key Techniques

Serverless Functions

// netlify/functions/users.ts
import type { Handler, HandlerEvent, HandlerContext } from "@netlify/functions";

interface User {
  id: string;
  name: string;
  email: string;
}

const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
  const { httpMethod, queryStringParameters, body } = event;

  if (httpMethod === "GET") {
    const page = parseInt(queryStringParameters?.page ?? "1", 10);
    const users = await fetchUsers(page);

    return {
      statusCode: 200,
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(users),
    };
  }

  if (httpMethod === "POST") {
    const data: Partial<User> = JSON.parse(body ?? "{}");
    const user = await createUser(data);

    return {
      statusCode: 201,
      body: JSON.stringify(user),
    };
  }

  return { statusCode: 405, body: "Method Not Allowed" };
};

export { handler };

Background Functions

// netlify/functions/process-upload-background.ts
// Suffix "-background" gives 15 minutes instead of 10 seconds
import type { Handler } from "@netlify/functions";

const handler: Handler = async (event) => {
  const { fileId, userId } = JSON.parse(event.body ?? "{}");

  // Long-running process — up to 15 minutes
  await downloadFile(fileId);
  await processFile(fileId);
  await notifyUser(userId, fileId);

  return { statusCode: 200, body: "Processing complete" };
};

export { handler };

Scheduled Functions

// netlify/functions/daily-report.ts
import type { Handler } from "@netlify/functions";
import { schedule } from "@netlify/functions";

const handler: Handler = async (event) => {
  const report = await generateDailyReport();
  await sendReportEmail(report);

  return { statusCode: 200, body: "Report sent" };
};

// Runs every day at 8am UTC
export default schedule("0 8 * * *", handler);

Edge Functions

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

export default async (request: Request, context: Context) => {
  const { country, city, latitude, longitude } = context.geo;

  // Rewrite to country-specific page
  if (country?.code === "DE") {
    return context.rewrite("/de" + new URL(request.url).pathname);
  }

  // Or modify the response
  const response = await context.next();
  response.headers.set("x-geo-country", country?.code ?? "unknown");
  return response;
};

// Configure path matching in netlify.toml:
// [[edge_functions]]
//   path = "/*"
//   function = "geolocation"

Forms

// HTML form with Netlify detection — no backend needed
// <form name="contact" method="POST" data-netlify="true" netlify-honeypot="bot-field">
//   <input type="hidden" name="form-name" value="contact" />
//   <p class="hidden"><input name="bot-field" /></p>
//   <input type="text" name="name" required />
//   <input type="email" name="email" required />
//   <textarea name="message" required></textarea>
//   <button type="submit">Send</button>
// </form>

// React form submission
async function submitForm(data: { name: string; email: string; message: string }) {
  const response = await fetch("/", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      "form-name": "contact",
      ...data,
    }).toString(),
  });

  if (!response.ok) throw new Error("Form submission failed");
  return response;
}

Build Plugins

// plugins/cache-prisma/index.ts — custom build plugin
import type { NetlifyPlugin } from "@netlify/build";

const plugin: NetlifyPlugin = {
  onPreBuild: async ({ utils }) => {
    // Restore cached Prisma engine
    const success = await utils.cache.restore("node_modules/.prisma");
    if (success) {
      console.log("Prisma cache restored");
    } else {
      console.log("No Prisma cache found, will generate");
    }
  },

  onPostBuild: async ({ utils }) => {
    // Cache Prisma engine for next build
    await utils.cache.save("node_modules/.prisma");
    console.log("Prisma cache saved");
  },

  onError: ({ utils }) => {
    utils.build.failPlugin("Build plugin encountered an error");
  },
};

export default plugin;

// Register in netlify.toml:
// [[plugins]]
//   package = "./plugins/cache-prisma"

Redirects and Rewrites

// netlify.toml — redirect rules (processed in order, first match wins)

// [[redirects]]
//   from = "/old-blog/*"
//   to = "/blog/:splat"
//   status = 301
//
// # Proxy to external API (rewrite, no redirect)
// [[redirects]]
//   from = "/api/v1/*"
//   to = "https://api.backend.com/v1/:splat"
//   status = 200
//   force = true
//   headers = {X-Custom-Header = "netlify-proxy"}
//
// # Country-based redirect
// [[redirects]]
//   from = "/*"
//   to = "/de/:splat"
//   status = 302
//   conditions = {Country = ["DE", "AT", "CH"]}
//
// # SPA fallback — must be last
// [[redirects]]
//   from = "/*"
//   to = "/index.html"
//   status = 200

Split Testing (A/B)

// Configure branch-based split testing via netlify.toml or UI
// Deploys from different branches are served to a percentage of users

// In your code, detect the branch via cookie
function getVariant(): "control" | "experiment" {
  const cookie = document.cookie
    .split("; ")
    .find((row) => row.startsWith("nf_ab="));

  if (cookie) {
    return cookie.split("=")[1] as "control" | "experiment";
  }
  return "control";
}

// Track conversion
async function trackConversion(variant: string) {
  await fetch("/api/analytics", {
    method: "POST",
    body: JSON.stringify({ variant, event: "signup" }),
  });
}

Identity (Authentication)

// Initialize Netlify Identity widget
// import netlifyIdentity from "netlify-identity-widget";

// netlifyIdentity.init();

// Sign up / log in
// netlifyIdentity.open("signup");
// netlifyIdentity.open("login");

// Listen for events
// netlifyIdentity.on("login", (user) => {
//   console.log("Logged in:", user.email);
//   const token = user.token.access_token;
// });

// Protect a function with identity
// netlify/functions/protected.ts
import type { Handler } from "@netlify/functions";

const handler: Handler = async (event, context) => {
  const { identity, user } = context.clientContext ?? {};

  if (!user) {
    return { statusCode: 401, body: JSON.stringify({ error: "Not authenticated" }) };
  }

  const userData = await fetchUserData(user.sub);
  return {
    statusCode: 200,
    body: JSON.stringify({ user: userData }),
  };
};

export { handler };

Best Practices

  • Use _redirects or netlify.toml for routing — define redirects declaratively rather than in application code for CDN-level performance.
  • Leverage deploy previews for every PR — each pull request gets a unique URL for testing; add Lighthouse CI and visual regression checks.
  • Use background functions for long tasks — name the file with -background suffix to get 15-minute execution instead of 10 seconds.
  • Cache aggressively in build plugins — restore and save heavy dependencies (Prisma, sharp) between builds to cut build times.
  • Set context-specific config — use [context.production] and [context.deploy-preview] blocks to vary build commands and environment variables.
  • Enable form spam filtering — use the honeypot attribute (netlify-honeypot) and enable Akismet in the Netlify dashboard.
  • Use instant rollbacks — if a production deploy breaks, roll back to any previous deploy in seconds from the dashboard.
  • Pin Node.js version — set NODE_VERSION in netlify.toml to avoid surprises from default version changes.

Anti-Patterns

  • Putting all logic in a single function — each function file becomes its own endpoint. Split concerns into separate function files for independent scaling and debugging.
  • Ignoring redirect order — redirects are matched top-to-bottom. Placing the SPA catch-all (/* /index.html 200) before specific redirects will swallow them.
  • Using environment variables at build time for secrets — build logs may expose them. Use function-level environment variables for secrets and never prefix with framework-specific public prefixes.
  • Expecting persistent filesystem — functions run in ephemeral containers. Never write to disk expecting persistence; use a database or Netlify Blobs.
  • Overusing edge functions for heavy computation — edge functions have limited CPU time and memory. Keep them lightweight; offload heavy work to background functions.
  • Skipping force = true on proxy rewrites — without force, Netlify will serve a static file if one matches the path, ignoring the rewrite rule.
  • Not testing locally with netlify dev — the CLI accurately simulates redirects, functions, and environment variables. Always test before pushing.

Install this skill directly: skilldb add deployment-hosting-services-skills

Get CLI access →

Related Skills

AWS Lightsail

AWS Lightsail provides a simplified way to launch virtual private servers (VPS), containers, databases, and more. It's ideal for developers and small businesses needing easy-to-use, cost-effective cloud resources without deep AWS expertise.

Deployment Hosting Services264L

Cloudflare Pages Deployment

Cloudflare Pages and Workers expertise — edge-first deployments, full-stack apps with Workers functions, KV/D1/R2 bindings, preview URLs, custom domains, and global CDN distribution

Deployment Hosting Services312L

Coolify Deployment

Coolify self-hosted PaaS expertise — Docker-based deployments, Git integration, automatic SSL, database provisioning, server management, and Heroku/Netlify alternative on your own hardware

Deployment Hosting Services227L

Digital Ocean App Platform

DigitalOcean App Platform is a fully managed Platform-as-a-Service (PaaS) that allows you to quickly build, deploy, and scale web applications, static sites, APIs, and background services. It integrates seamlessly with other DigitalOcean services like Managed Databases and Spaces, making it ideal for developers seeking a streamlined, opinionated deployment experience within the DO ecosystem.

Deployment Hosting Services248L

Fly.io Deployment

Fly.io platform expertise — container deployment, global edge distribution, Dockerfiles, volumes, secrets, scaling, PostgreSQL, and multi-region patterns

Deployment Hosting Services338L

Google Cloud Run

Google Cloud Run is a fully managed serverless platform for containerized applications. It allows you to deploy stateless containers that scale automatically from zero to thousands of instances based on request load, paying only for the resources consumed. Choose Cloud Run for microservices, web APIs, and event-driven functions that require custom runtimes or environments.

Deployment Hosting Services223L