ericrisco/rsc-harness/netlify

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Use when deploying or operating a site on Netlify — writing or fixing netlify.toml, authoring Functions or Edge Functions, redirects, rewrites and headers, env vars per deploy context, and shipping via the Netlify CLI. NOT deploying to Vercel (that is `vercel`).

Install

npx vskill@latest install ericrisco/rsc-harness/netlify

SKILL.md

Netlify — netlify.toml, Functions, Edge Functions, redirects & the CLI

The platform-mechanics layer for Netlify: config, function handlers, routing, env
contexts, and the deploy CLI. Not framework code, not generic CI.

What this owns

You own the Netlify adapter/config surface — netlify.toml, netlify/functions/, netlify/edge-functions/, _redirects, _headers, and netlify <cmd>. You do not own the app's routing or data layer (that is ../nextjs/SKILL.md / a React skill), DNS records (../domains-dns/SKILL.md), or another platform's config — Vercel is ../vercel/SKILL.md, Cloudflare is ../cloudflare/SKILL.md. Their config files and function models differ; never cross-apply.

The 5-minute happy path

bash
netlify link # link the cwd to a site (or `netlify init` to create one)
# write/edit netlify.toml (see skeleton below)
netlify dev # local emulation: build, functions, redirects, env injection
netlify deploy --prod # build + push straight to production

netlify deploy with no flag creates a draft deploy (preview URL, not live). --prod publishes. Test with netlify dev first — it is the only local runtime that emulates redirects and function routing together.

Where does each piece of config live?

Two rules decide everything below: `_redirects`/`_headers` files are processed before the `netlify.toml` equivalents, and a function's own config export beats dashboard guesses.

ConcernPut it inWhy
Redirects / rewrites / SPA fallback[[redirects]] in toml or _redirects fileOne source. File rules run first; pick one and stay consistent.
Response headers (CSP, caching)[[headers]] in toml or _headers fileSame precedence; keep security headers in version control, not the UI.
Build command / publish dir / functions dir[build] in tomlSingle declarative source the build picks up; survives UI drift.
A function's URL pathexport const config = { path } in the functionCo-located with the handler; deterministic, no dashboard mapping.
Edge function path + ordering[[edge_functions]] in tomlDeclaration order in toml is deterministic (inline config is not).
Secrets / API keys / per-context varsdashboard or netlify env:setNever commit secrets — build-time secrets scanning fails the build if it finds one.

netlify.toml skeleton

toml
[build]
command = "npm run build"
publish = "dist" # the directory you deploy; relative to base
functions = "netlify/functions"
[build.environment]
NODE_VERSION = "22"
[functions]
node_bundler = "esbuild"
# included_files = ["data/**"] # bundle extra files a function reads at runtime
# SPA fallback — note status 200, NOT 301 (see Redirects)
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
Content-Security-Policy = "default-src 'self'"
# per-context override: deploy previews build differently
[context.deploy-preview]
command = "npm run build:preview"
[context.deploy-preview.environment]
SHOW_PREVIEW_BANNER = "true"

Contexts are production, deploy-preview, branch-deploy, and branch."name". Each can override command, publish, environment, etc. The full key-by-key reference (every [build]/[functions]/[[plugins]] option, all header/redirect fields) lives in references/netlify-toml.md — link there instead of inlining it.

Functions (Node runtime)

Default directory is netlify/functions/. TypeScript files are .mts, JS is .mjs (ES modules). Use the modern Web-platform handler — Request in, Response out — and route with a config export, not a dashboard mapping.

typescript
// netlify/functions/hello.mts
import type { Config, Context } from "@netlify/functions";
export default async (req: Request, context: Context) => {
const name = new URL(req.url).searchParams.get("name") ?? "world";
return Response.json({ hello: name });
};
export const config: Config = { path: "/api/hello" };

Read secrets from the environment; never hardcode them:

typescript
const key = Netlify.env.get("RESEND_API_KEY") ?? process.env.RESEND_API_KEY;

Do not revive the legacy AWS-Lambda signature in new functions:

javascript
// Bad — legacy handler/event/statusCode shape
exports.handler = async (event) => ({ statusCode: 200, body: "ok" });
typescript
// Good — Web-API handler + Config.path
export default async (req: Request) => new Response("ok");
export const config: Config = { path: "/ok" };

Scheduled (config.schedule cron), background (-background suffix), and streaming responses are covered in references/functions.md.

Edge Functions (Deno runtime)

Live in netlify/edge-functions/, run on Deno at the edge. Declare them in toml so ordering is explicit:

toml
[[edge_functions]]
function = "geo-rewrite"
path = "/*"
# excludedPath = "/assets/*"

When multiple edge functions match one path, they run top-to-bottom in declaration order — declaring in netlify.toml is deterministic; relying on each function's inline config for ordering is not. Pick edge functions for latency-sensitive rewrites, geolocation, and A/B routing. Avoid them for heavy npm dependencies or Node-only APIs — use a regular Function there.

Redirects & rewrites

text
# _redirects (or the [[redirects]] equivalent)
/api/* https://api.example.com/:splat 200 # proxy/rewrite: keep the URL, fetch remote
/old/* /new/:splat 301 # permanent redirect (default status)
/* /index.html 200 # SPA fallback

Three things people get wrong:

  • SPA fallback must be `status = 200`, not 301. 200 serves index.html at the original

URL so the client router can read the path; a 301 changes the URL and breaks deep links.

  • Proxy/rewrite = `status = 200` to a remote URL (add force = true to override an

existing file at that path).

  • Processing order: Edge Functions run first, then _redirects rules, then netlify.toml

redirects; within the set the first matching rule wins (top-to-bottom). Put specific rules above the /* catch-all.

Env vars & deploy contexts

bash
netlify env:set RESEND_API_KEY "xxxx" --context production
netlify env:set SHOW_BANNER "true" --context deploy-preview
netlify env:list --context deploy-preview
netlify env:import .env # bulk import (do not commit .env)

Variables are scoped to deploy contexts, so a key can differ between production and previews. Build-time secrets scanning inspects build output; if a value you marked secret leaks into the bundle, the build fails — fix the leak, don't disable the scan blindly.

Deploy & local dev

CommandUse it for
netlify devLocal: build + functions + redirects + env injection. The truth before deploy.
netlify deployDraft deploy → preview URL, not live.
netlify deploy --prodBuild + publish to production.
netlify deploy --prod --no-buildPublish an already-built dir; skips the build command.
netlify deploy --skip-functions-cacheForce re-bundle functions when a stale cache bites.

--no-build is the classic foot-gun: if you didn't actually build (or built into the wrong publish dir), you ship a stale or empty site. Only use it when the artifact is fresh.

Anti-patterns

Anti-patternWhy it breaksDo instead
SPA fallback as status = 301URL rewrites; deep-link refresh 404s or loopsstatus = 200 to /index.html
Hardcoding API keys in a functionLeaks in bundle; secrets scan fails the buildNetlify.env.get(...) / process.env, set via netlify env:set
Legacy exports.handler = (event) => ({statusCode})Old Lambda shape, mismatched runtime expectationsWeb-API export default (req) => Response + config.path
Relying on inline edge config for run orderOrdering is non-deterministic across functionsDeclare order in [[edge_functions]] in toml
netlify deploy --prod --no-build without buildingShips stale/empty publish dirBuild first, or drop --no-build
Pasting Vercel/Cloudflare config into NetlifyDifferent files & function models; nothing wires upUse netlify.toml + netlify/functions; see ../vercel/SKILL.md for Vercel
functions dir in toml ≠ actual folder on diskFunctions silently not bundled → 404Make [build].functions match netlify/functions/ exactly
/* redirect above a specific ruleCatch-all wins first; specific rule never matchesOrder specific rules before the /* fallback

Verify

Run scripts/verify.sh [target-dir] (default cwd). It confirms a netlify.toml exists, parses, that any SPA fallback is status = 200, and that every [[redirects]] has both from and to. Read-only; exits 0 on a clean or empty target.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
141
Repository forks
11
7-day trend score
30

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/ericrisco/rsc-harness/netlify/badge)](https://verified-skill.com/skills/ericrisco/rsc-harness/netlify)

Something wrong with this skill or its page? Tell us.