netlify-edge-functions
Write and configure Netlify Edge Functions — TypeScript/JavaScript handlers running in a Deno runtime at the network edge. Use when adding auth middleware or auth redirects, geolocation or localization logic, A/B testing or personalization, request/response transforms (rewrites/redirects), or edge S
- 0
- Installs
- —
- Rating
- —
- Success rate
- 1
- Files scanned
Security scan
Scan passedNo risky patterns were found in the scanned files.
Content sha256 569fe1058c830980… — run codexguild_scan_skills after installing to verify your local copy.
Static analysis is a first line of defense, not a guarantee. Read the source
SKILL.md
Netlify Edge Functions
Modern syntax (reach for this)
Export a default handler plus a config object. Import Config/Context types from @netlify/edge-functions; Request/Response/URL are global.
import type { Config, Context } from "@netlify/edge-functions";
export default async (request: Request, context: Context) => {
return new Response("Hello world");
};
export const config: Config = {
path: "/test",
};
Do not hand-write an edge function when your framework's adapter already generates middleware for the job — duplicating it causes conflicts. Check the framework adapter/reference first.
Edge vs serverless: use edge functions for low-latency request/response manipulation, geolocation logic, auth checks/redirects, and A/B personalization. Use serverless functions for long-running work (up to 15 min), heavy Node.js dependencies, database-heavy operations, background/scheduled tasks, or memory above 512 MB.
File location
- Default directory:
YOUR_BASE_DIRECTORY/netlify/edge-functions. Custom:edge_functionsunder[build]innetlify.toml(path relative to base directory). - Keep the directory outside your publish directory so source files aren't deployed.
- Extensions:
.js,.ts,.jsx,.tsx(.jsx/.tsxuseful for SSR). - Same-name conflict: if
my-function.tsandmy-function.jsboth exist, the TypeScript file is ignored and the JavaScript one is deployed.
Routing — required, or the function silently never runs
⚠️ An edge function without a route (no config export and no netlify.toml declaration) still deploys but never runs — no build error, no warning. When "my edge function does nothing", check the route first.
⚠️ Scope path narrowly. path: "/*" intercepts every request including static assets, adding latency and billing an edge invocation for each one.
Edge functions are not auto-assigned a URL route. Configure via inline config or netlify.toml.
path is a URLPattern expression, must start with /, single string or array:
export const config: Config = {
path: ["/", "/products/*"],
excludedPath: ["/*.css", "/*.js"],
};
Config properties: path, excludedPath, pattern (regex alternative to path), excludedPattern, method, header, onError, cache.
netlify.toml declaration
Use [[edge_functions]] to declare multiple functions on one path and control order:
[[edge_functions]]
path = "/admin"
function = "auth"
[[edge_functions]]
path = "/admin"
function = "injector"
cache = "manual"
[[edge_functions]]
pattern = "/products/(.*)"
excludedPattern = "/products/things/(.*)"
function = "highlight"
Properties: function, path, excludedPath, pattern, excludedPattern, header, cache.
Merge precedence: if the same function is declared both inline and in netlify.toml, configs merge and are treated as inline; inline wins duplicate fields.
Match by headers
header keys are HTTP header names (case-insensitive); values are true (present), false (absent), or a string regex on the value. Multiple same-name values match against the comma-joined list.
export const config: Config = {
header: { "x-required": true, "x-forbidden": false, "user-agent": "(iPhone|Android)" },
path: "/*",
};
Declaration processing order
Netlify runs the whole declaration order TWICE: the first pass runs only edge functions not configured for caching; the second pass runs the ones with caching configured. Within that:
- Framework-generated functions declared in a config file.
- Your
netlify.tomldeclarations (top-to-bottom order). - Framework/integration-generated functions with inline config.
- Your inline declarations (alphabetical by function file name).
To control order across multiple functions on a path, prefer netlify.toml declarations over inline.
After all functions run, Netlify evaluates redirect rules — unless a function returned a response and ended the chain. To customize order, use netlify.toml.
Order caveats:
- A returned response ends the chain; redirects for that path don't occur.
- An edge function on the target of a static rewrite does not execute for rewritten requests.
fetch()for internal requests or returning aURLstarts a new request chain and re-runs matching edge functions. Usecontext.next()to avoid re-running them.
Function signature & return values
Handler receives (request: Request, context: Context). Return one of:
- a
Response— delivered to the client; ends the request chain (declared redirects for that path don't run). - a
URL— rewrite to a same-site URL with 200 status; address bar unchanged. Same-site only — for other sites usefetch. undefined/ emptyreturn;— bypass this function, continue the chain.
Modify a response as middleware by awaiting context.next():
import type { Context } from "@netlify/edge-functions";
export default async (request: Request, context: Context) => {
const url = new URL(request.url);
if (url.searchParams.get("method") !== "transform") return;
const response = await context.next();
const text = await response.text();
return new Response(text.toUpperCase(), response);
};
Netlify does not add headers to edge function requests — use context for client request info.
Common patterns
Redirect by geo + cookie:
export default async (req: Request, { cookies, geo }: Context) => {
if (geo.city === "Paris" && cookies.get("promo-code") === "15-for-followers") {
return Response.redirect(new URL("/subscriber-sale", req.url));
}
};
Rewrite (same-site, 200) — return a standard URL object (see https://docs.netlify.com/build/edge-functions/api#return-a-rewrite):
export default async (request: Request, { geo }: Context) => {
if (geo.city === "Paris") return new URL("/subscriber-sale", request.url);
};
Read request body then continue — a body can only be read once, so pass a new Request with an unread body:
export default async (req: Request, context: Context) => {
const body = await req.json();
if (!isValid(body.access_token)) return new Response("forbidden", { status: 403 });
return context.next(new Request(req, { body: JSON.stringify(body) }));
};
Conditional request:
export default async (req: Request, { next }: Context) => {
const res = await next({ sendConditionalRequest: true });
if (res.status === 304) return res;
const text = await res.text();
return new Response(text.toUpperCase(), res);
};
SSR with React (.tsx):
import React from "https://esm.sh/react";
import { renderToReadableStream } from "https://esm.sh/react-dom/server";
import type { Config, Context } from "@netlify/edge-functions";
export default async function handler(req: Request, context: Context) {
const stream = await renderToReadableStream(
<html><body><h1>Hello {context.geo.country?.name}</h1></body></html>
);
return new Response(stream, { status: 200, headers: { "Content-Type": "text/html" } });
}
export const config: Config = { path: "/hello" };
Context object
geo—city,country.{code,name},subdivision.{code,name},latitude,longitude,timezone,postalCode.cookies—get(name),set(options)(CookieStore.set format),delete(name|options). Cross-subdomain cookies need a custom domain — impossible onnetlify.app(Public Suffix List).next(options?)/next(request, options?)— invoke the next item in the chain; returns aPromise<Response>you can modify.options.sendConditionalRequest: truefor conditional requests. Only callnextif you need the response body. Pass an explicitRequestwhen you've read the body.params— path params, e.g. path/pets/:name+ request/pets/winter→{name:"winter"}. Query string: userequest.url.ip— client IP string.requestId— Netlify request ID.account.id,site.{id,name,url},server.region,deploy.{context,id,published,skewProtectionToken}.waitUntil(promise)— extend execution past the response (analytics, logs) without blocking it. Still subject to the CPU limit.
Netlify global: Netlify.context (null outside the handler), Netlify.env.{get,has,set,delete,toObject}. Netlify.env.set/delete are invocation-scoped only — they do not persist env vars; use the Netlify env API endpoints.
Response caching
⚠️ Caching requires BOTH opting in AND setting headers — it's both or neither. Setting Cache-Control on the returned Response does nothing without cache: "manual" in config, and vice versa. Default (either missing): every request invokes the function.
- Opt in:
cache: "manual"(inline ornetlify.toml). - Set headers inline in the function code (not in
netlify.toml):
import type { Context, Config } from "@netlify/edge-functions";
export default async (req: Request, context: Context) => {
return new Response("Hello world", {
headers: { "cache-control": "public, s-maxage=3600" },
});
};
export const config: Config = { cache: "manual", path: "/hello" };
Supported cache headers: Cache-Control, CDN-Cache-Control, Netlify-CDN-Cache-Control, Expires (overridden by max-age/s-maxage), Vary, Netlify-Vary. See https://docs.netlify.com/build/caching/caching-overview
Atomic deploys void the cache: s-maxage/max-age/Expires are discarded by a new deploy in the same deploy context, even mid-lifetime.
When to cache: endpoint responses reusable across clients (e.g. identical SSR HTML). Do not cache middleware, routing/transform logic, or per-client personalization.
⚠️ Caching functions always shadow static files. A caching function on /* serves /cat.png instead of the static cat.png.
Error handling (onError, inline only)
fail(default) — serve a generic error page./YOUR_CUSTOM_PATH— rewrite to a same-site path (must start with/); served without invoking edge functions for that path.bypass— skip the erroring function, continue the chain.
export const config: Config = { path: "/hello", onError: "/unavailable" };
Fail closed for critical logic (auth); fail open (bypass) for progressive enhancement (nice-to-have localization).
Environment variables
- Set via UI/CLI/API; scope must include Functions to reach edge runtime.
- Env vars in
netlify.tomlare NOT available to edge functions. - Build-scope vars are NOT available at edge runtime — only during the build step. Embed their values at build time if needed.
- Changes require a new build and deploy; each deploy freezes values at deploy time.
- Access at runtime with
Netlify.env.get(key)/Netlify.env.toObject().
export default async (request: Request, context: Context) => {
const value = Netlify.env.get("MY_IMPORTANT_VARIABLE");
return new Response(`Value: ${value}`);
};
Next.js Middleware note: with Netlify Edge Functions for Middleware on Next.js, process.env also works.
Runtime & modules
Deno-based. Import modules by:
- Node built-ins:
import { randomBytes } from "node:crypto"; - Deno/URL imports:
import React from "https://esm.sh/react"; - npm packages (beta):
npm installthen import by name. ⚠️ Beta — packages using native binaries (Prisma) or runtime dynamic imports (cowsay) may fail. Report bugs to Netlify Support: https://www.netlify.com/support/
Import maps (module names instead of URLs) — use a separate import map file, declared in netlify.toml:
[functions]
deno_import_map = "./path/to/your/import_map.json"
Supported Web APIs include fetch/Request/Response/URL/File/Blob, console, atob/btoa, TextEncoder/TextDecoder (+ stream variants), Web Crypto (randomUUID, getRandomValues, SubtleCrypto), WebSocket, timers, Streams API, URLPattern, Performance.
Local dev & deploy
npm install netlify-cli -g
netlify dev # runs edge functions on local requests
# visit http://localhost:8888/test
- Debug:
netlify devwith--edge-inspector--edge-inspect-brk(see https://cli.netlify.com/commands/dev/). - Geo mocking:
--geo=mock(San Francisco) or--geo=mock --country=XX. - ⚠️ No local caching — cache headers are ignored in local testing.
- Manual deploys require Netlify CLI 12.2.8+ (older versions error).
- Deploys are atomic — old deploys keep old behavior until you publish a new production deploy.
Monitor: production logs at Netlify UI Cloud compute > Edge functions. Each console.* log includes the generating function name. Retention ≥ 24h (7 days on some plans). Log Drains on Enterprise.
Limits & feature gaps
- Code size: 20 MB compressed (bundle max).
- Memory: 512 MB per set of deployed edge functions.
- CPU time: 50 ms per request (excludes wait time;
waitUntilwork still counts). - Response header timeout: 40 s.
- Cached responses do not count toward invocations.
- Split Testing enabled → edge functions do not run.
- Custom Headers (incl. basic auth) do not apply to edge functions.
- Prerendering does not apply to edge-served paths.
- Rewrites are same-site only — use
fetchfor other/external sites. - Multiple framework plugins generating edge functions may collide.
- Not supported under HIPAA-compliant hosting.
See the overview at https://docs.netlify.com/build/edge-functions/overview.md and the full example library at https://edge-functions-examples.netlify.app/. For feedback or help, contact Netlify Support: https://www.netlify.com/support/
Netlify house rules (edge-functions)
These are org conventions and field-learned guardrails, not docs facts — they are merged into the rendered skill by ctx-gen and are never generated. Extracted from the previous hand-written netlify-edge-functions skill; owned by the skills maintainer.
- Check the framework's adapter/reference first: a custom edge function that duplicates adapter-generated middleware causes conflicts. Only hand-write an edge function when the framework doesn't already generate one for the job.
- Scope
pathnarrowly.path: "/*"intercepts every request — including static assets — adding latency to each one and billing an edge invocation for it. - An edge function without a route (no config export, no netlify.toml declaration) still deploys, but silently never runs: no build error, no warning. When "my edge function does nothing", check the route first.
- Choose edge vs serverless by workload shape: edge functions for low-latency request/response manipulation, geolocation logic, auth checks/redirects, and A/B personalization; serverless functions for long-running work (up to 15 min), heavy Node.js dependencies, database-heavy operations, background/scheduled tasks, or memory needs above 512 MB.
- Cache headers on an edge response do nothing without
cache: "manual"in config — it's both or neither. SettingCache-Controlon the returnedResponsehas no effect unless the function also opts in. - When explaining declaration processing order, state the two-pass loop, not just the ordering: Netlify runs the whole declaration order TWICE — first pass runs only edge functions not configured for caching, second pass runs the ones with caching configured. "Non-cached before cached" without the loop framing is an incomplete answer.
Files
1- SKILL.md
54bf0da18816.3 KB
Agent reviews
0No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.
More from netlify/context-and-tools8
Picks the right Netlify site-protection layer and disambiguates the three unrelated "auth" concepts users conflate — app-user login (Netlify Identity), site-load gating (Password Protection / project visibility), and dashboard SAML SSO. Use it when asked to password-protect a site or Deploy Preview,
Run AI agent tasks remotely on Netlify using Claude, Codex, or Gemini. Use when the user wants to run an AI agent on their site, get a second opinion from another model, or delegate development tasks to run remotely against their repo.
Use Netlify AI Gateway to call OpenAI, Anthropic Claude, Google Gemini, TypeSafe (Jev), or OpenRouter-hosted models (xAI/DeepSeek/Meta/Mistral/Qwen) from Netlify Functions or Edge Functions without managing provider accounts or API keys. Reach for this when adding an AI feature to a Netlify app — a
Store and retrieve unstructured objects, files, and cache-like state on Netlify with the @netlify/blobs module. Use when persisting user file uploads (images/documents), caching computed output from functions or Background Functions, serving downloadable assets, storing JSON blobs keyed by ID, or se
Cache dynamic and static responses on Netlify's CDN from Functions, Edge Functions, and proxies. Use when you add caching or cache-control headers to a function response, tune cache TTL or stale-while-revalidate, set up the durable cache, vary a cache key by query/header/cookie/country/language, pur
Configure Netlify builds and routing via netlify.toml, _redirects, and _headers. Use when setting a build command or publish directory, adding redirects or rewrites or proxies, adding an SPA fallback rewrite, setting custom response headers or basic auth, managing environment variables and secrets,
Zero-config Postgres for Netlify apps via @netlify/database — querying data from Functions/Edge Functions, writing schema migrations, setting up Drizzle ORM, local dev with netlify dev, database branches for deploy previews, and migrating an existing Postgres project onto Netlify. Use when adding a
Create, configure, and manage Netlify deploys from code — reach for this when setting up Git continuous deployment, running netlify deploy or netlify deploy --prod from the CLI, writing netlify.toml deploy contexts, adding a Deploy to Netlify button, wiring build hooks, configuring Deploy Previews o
Related security skillsscan passed
AI-powered codebase security scanner that reasons about code like a security researcher — tracing data flows, understanding component interactions, and catching vulnerabilities that pattern-matching tools miss. Use this skill when asked to scan code for security vulnerabilities, find bugs, check for
Repository-grounded threat modeling that enumerates trust boundaries, assets, attacker capabilities, abuse paths, and mitigations, and writes a concise Markdown threat model. Trigger only when the user explicitly asks to threat model a codebase or path, enumerate threats/abuse paths, or perform AppS
Turn ambiguous or high-impact product and engineering changes into scoped, verifiable acceptance criteria before or alongside implementation. Use when a user asks to clarify a feature, define acceptance criteria, de-risk a security/data/migration/integration change, prepare implementation requiremen
Security audit: supported static findings; qualified profiles add reproduction and repair candidates. (gstack)
Claude Security: scan the codebase (the whole repository or a scoped part of it), scan changes (this branch's or a pull request's diff, or one commit), or suggest patches (findings turned into targeted patch files, each verified by a panel of agents, that you apply when you choose). Use when the use
Implement JWT/cookie authentication and authorization in tRPC using createContext for user extraction, t.middleware with opts.next({ ctx }) for context narrowing to non-null user, protectedProcedure base pattern, client-side Authorization headers via httpBatchLink headers(), WebSocket connectionPara