Netlify Deploys skill for Claude Code
Builds, redirects and functions that behave per context. Claude moves Netlify configuration into netlify.toml with pinned Node versions, monorepo base directories and ignore commands, then scopes environment variables per deploy context. Redirects are ordered with shadowing and force understood, assets get immutable caching, functions and forms follow current limits, and every change is checked on a deploy preview with a one-step rollback ready.
- name
netlify-deploys - size265 lines · ≈2,750 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Configures and debugs Netlify sites - netlify.toml build settings, base directories for monorepos, ignore commands, deploy contexts for production, deploy previews and branch deploys, environment variables by context and scope, redirects and rewrites with shadowing and force, proxying, custom headers and caching, Netlify Functions (v2 handler, background and scheduled), Edge Functions, Forms with spam protection, instant rollback, and the Netlify CLI. Use when editing netlify.toml, _redirects or _headers, when a Netlify build fails or a deploy shows 404s, when adding functions or forms, or when setting up previews for a monorepo. Try asking
- Our SPA on Netlify returns 404 on refresh for any route except the homepage
- Set up Netlify for our monorepo so only the web app builds when it changes
- Add a contact form and a serverless function that emails submissions on Netlify
Use it when
- Editing netlify.toml, _redirects or _headers
- A Netlify build fails or a deploy serves the wrong pages
- Adding Netlify Functions, Edge Functions or Forms
- Configuring deploy previews, branch deploys and environment variables
Skip it when
- The site deploys to Vercel — use vercel-deploys
- The app runs on Cloudflare's edge platform — use cloudflare-workers
How to check the result
netlify build succeeds locally, the deploy preview passes curl -I checks for each redirect and header rule, a test form submission appears, and a previous deploy can be republished.
The common mistake it prevents
Defining runtime secrets in netlify.toml, which makes them available to the build but undefined inside functions at runtime.
A Netlify site is done when the build is defined in netlify.toml (not only the UI),
each deploy context gets the right variables, redirects are ordered and tested, static
assets are cached immutably, functions live in versioned code with known limits, and a
bad production deploy can be rolled back in one click or one command.
Ground rules
- Put build config, redirects and headers in
netlify.toml(or_redirects/_headersin the publish dir) in Git. UI settings drift silently. - Secrets go in Netlify environment variables (UI/CLI/API) scoped to the contexts and
scopes that need them; never commit them to
netlify.toml. - Production deploys (
netlify deploy --prod, publishing a deploy, rolling back) need explicit confirmation when run by hand. - Test redirects and functions locally with
netlify devand on a deploy preview before merging.
Workflow
- [ ] 1. Inspect: site link, build settings, env vars, recent deploy logs
- [ ] 2. Define build in netlify.toml (base, command, publish, Node version)
- [ ] 3. Contexts and environment variables
- [ ] 4. Redirects, rewrites, headers
- [ ] 5. Functions / Edge Functions / Forms as needed
- [ ] 6. Verify on a deploy preview; promote; know the rollback
1. Inspect
npm i -g netlify-cli # or npx netlify-cli
netlify login && netlify link # associate the folder with the site
netlify status
netlify env:list --context production
netlify build # run the build locally the way Netlify does
netlify dev # local server with redirects, functions, edge functions
Read the failed deploy log from the top: the first error usually names a missing env var, a wrong base directory, or a Node version mismatch.
2. Build settings
# netlify.toml (at repo root or at the package base for monorepos)
[build]
base = "apps/web"
command = "pnpm run build"
publish = "dist" # relative to base
ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF -- . ../../packages/ui"
[build.environment]
NODE_VERSION = "22"
PNPM_FLAGS = "--frozen-lockfile"
[context.production.environment]
PUBLIC_SITE_URL = "https://www.acme.com"
[context.deploy-preview]
command = "pnpm run build -- --mode preview"
[context.branch-deploy.environment]
PUBLIC_SITE_URL = "https://staging.acme.com"
ignoreexit code 0 skips the build, 1 builds. Use it in monorepos so unrelated packages do not rebuild.- Framework sites (Next.js, Astro, SvelteKit, Remix/React Router, Nuxt) get adapters automatically or via the framework’s Netlify adapter; do not hand-write redirects that the adapter generates.
- Pin the Node version (
NODE_VERSIONor.nvmrc) to match local and CI.
3. Environment variables
| Context | When it applies |
|---|---|
production | Deploys of the production branch |
deploy-preview | Pull/merge request previews |
branch-deploy | Other branches with branch deploys enabled |
dev | netlify dev locally |
Branch-specific (context.staging) | A named branch |
netlify env:set STRIPE_SECRET_KEY sk_live_... --context production --scope functions --secret
netlify env:set STRIPE_SECRET_KEY sk_test_... --context deploy-preview branch-deploy --scope functions --secret
netlify env:get STRIPE_SECRET_KEY --context production
- Variables in
netlify.tomlare available at build time only, not to functions at runtime. Runtime secrets must be set through the UI/CLI/API with the functions scope. - Anything prefixed for the client bundle (
VITE_,NEXT_PUBLIC_,PUBLIC_) ships to browsers; never put secrets there. - Netlify scans build output for secret values and fails the build when it finds one; fix the leak rather than disabling the scan (omit only known-safe keys).
4. Redirects, rewrites, headers
[[redirects]]
from = "/blog/*"
to = "/articles/:splat"
status = 301
[[redirects]]
from = "/api/*"
to = "https://api.acme.com/:splat"
status = 200 # proxy (rewrite), URL stays the same
force = true
headers = { X-From = "netlify" }
[[redirects]]
from = "/app/*"
to = "/app/index.html"
status = 200 # SPA fallback for a sub-app
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*"
[headers.values]
Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
Rules:
- First matching rule wins;
_redirectsrules are evaluated beforenetlify.tomlrules. Put specific rules before catch-alls. - Shadowing: a rule does not apply when a file exists at the
frompath, unlessforce = true. SPA fallbacks rely on this; forced catch-alls break static assets. status = 200is a rewrite/proxy;301permanent;302temporary;404custom not-found with a page.- Header rules apply to files served from the CDN, not to function responses or proxied responses; set those headers in the function or upstream.
- Cache HTML briefly (Netlify’s default for HTML is effectively revalidate on every request); cache fingerprinted assets for a year.
5. Functions, Edge Functions, Forms
Functions (v2 API), netlify/functions/orders.mts:
import type { Config, Context } from "@netlify/functions";
export default async (req: Request, context: Context) => {
if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });
const body = await req.json();
const key = Netlify.env.get("STRIPE_SECRET_KEY");
// ... do work
return Response.json({ ok: true, id: body.id });
};
export const config: Config = { path: "/api/orders" };
| Type | Use | Limits (check current docs) |
|---|---|---|
| Synchronous function | APIs, form handlers, webhooks | Execution limit around 60 s (older docs say 10-30 s); request/response size limits apply |
Background function (-background suffix) | Slow work; caller gets 202 immediately | Up to 15 minutes; retried on error |
Scheduled function (config.schedule = "@hourly" or cron) | Periodic jobs | Short limit (about 30 s); production deploys only |
Edge Function (netlify/edge-functions/, Deno) | Geolocation, rewrites, A/B, auth gates at the edge | Very short CPU budget; no Node APIs unless node: compat |
Edge Function example:
// netlify/edge-functions/geo-redirect.ts
import type { Config, Context } from "@netlify/edge-functions";
export default async (req: Request, context: Context) => {
if (context.geo?.country?.code === "DE" && !new URL(req.url).pathname.startsWith("/de")) {
return Response.redirect(new URL("/de" + new URL(req.url).pathname, req.url), 302);
}
return context.next();
};
export const config: Config = { path: "/*", excludedPath: ["/assets/*", "/api/*"] };
Forms:
<form name="contact" method="POST" data-netlify="true" netlify-honeypot="bot-field" action="/thanks">
<input type="hidden" name="form-name" value="contact" />
<p hidden><label>Skip <input name="bot-field" /></label></p>
<input name="email" type="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
- Netlify detects forms in deployed static HTML at build time. JS-rendered forms (React,
SSR frameworks) need a static HTML copy of the form in the publish dir and the hidden
form-namefield in the submitted payload. - Enable form detection in site settings if submissions never appear.
- AJAX submissions: POST
application/x-www-form-urlencodedto/(or the static form path) includingform-name. - Add the honeypot and, for public forms, reCAPTCHA (
data-netlify-recaptcha="true").
6. Previews, promotion, rollback
- Deploy previews build for every PR with the deploy-preview context; protect them with password or team auth if they hit real data.
- Manual deploys from CI:
netlify deploy --dir dist --message "PR 123" # draft deploy, prints a unique URL
netlify deploy --dir dist --prod --message "release 1.4" # production (confirm first)
- Rollback: publish a previous deploy from the Deploys list (instant, no rebuild), or lock auto-publishing during an incident so new merges do not deploy.
- Branch deploys + branch subdomains (
staging--site.netlify.app) give a stable staging URL.
Debugging
| Symptom | Cause | Fix |
|---|---|---|
| 404 on deep links in SPA | No fallback rewrite | /* /index.html 200 (or framework adapter) |
| Static assets return index.html | Forced catch-all | Remove force from SPA fallback |
| “Page not found” for whole site | Wrong publish relative to base | Check the build log’s publish path |
| Build works locally, fails on Netlify | Node/package manager version, missing env var in that context, case-sensitive paths | Pin versions; set env per context; fix import casing |
| Function env var undefined | Defined only in netlify.toml or wrong scope/context | Set via CLI/UI with functions scope |
| Function 502 / timeout | Exceeds execution limit, unhandled rejection | Move to background function or queue; check function logs |
| Form submissions missing | Form not in static HTML, missing form-name, detection off | Add static form skeleton; enable detection |
| Proxy to API loses cookies/CORS issues | Upstream sets domain-specific cookies | Proxy with 200 so same-origin; adjust upstream cookie domain |
netlify logs:function orders # stream function logs (CLI command names vary by version; see netlify help)
netlify open:admin
Gotchas
basechanges the working directory forcommand,publishandfunctions; paths innetlify.tomlunder a base are relative to that base.- Deploy previews use deploy-preview variables; a missing preview value silently falls back to nothing, not to production.
_redirectsmust be in the publish directory after the build (copy it via the build), whilenetlify.tomlsits at the base.- Large numbers of redirect rules slow matching; use splats and placeholders.
- Scheduled functions do not run on deploy previews or branch deploys.
- Newer accounts are on credit-based pricing where builds, bandwidth, requests and
function compute draw from one pool; preview-heavy monorepos should use
ignore.
Output
**Site:** acme-web (base apps/web, pnpm build, publish dist, Node 22)
**Contexts:** production / deploy-preview / branch-deploy (staging) with scoped env vars
**Routing:** /api/* proxied to api.acme.com; SPA fallback for /app/*; 301s for old blog URLs
**Headers:** immutable /assets/*; HSTS, nosniff, referrer policy
**Functions:** /api/orders (sync), report-background, cleanup (scheduled @daily)
**Verified:** deploy preview checked (redirects via curl -I, form submission received); rollback = publish deploy 65f... About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.