From netlify-skills
Guides writing Netlify Edge Functions for middleware, geolocation logic, request/response manipulation, auth checks, and A/B testing. Covers Deno runtime, config, path scoping, and edge vs serverless tradeoffs.
How this skill is triggered — by the user, by Claude, or both
Slash command
/netlify-skills:netlify-edge-functionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Edge functions run on Netlify's globally distributed edge network (Deno runtime), providing low-latency responses close to users.
Edge functions run on Netlify's globally distributed edge network (Deno runtime), providing low-latency responses close to users.
For framework projects, check the framework reference (the netlify-frameworks skill) before hand-writing an edge function — framework adapters emit their own edge middleware, so the behavior you need may already be generated. A custom edge function that duplicates adapter-generated middleware causes conflicts.
import type { Config, Context } from "@netlify/edge-functions";
export default async (req: Request, context: Context) => {
return new Response("Hello from the edge!");
};
export const config: Config = {
path: "/hello",
};
Place files in netlify/edge-functions/. Uses .ts, .js, .tsx, or .jsx extensions.
export const config: Config = {
path: "/api/*", // URLPattern path(s)
excludedPath: "/api/public/*", // Exclusions
method: ["GET", "POST"], // HTTP methods
onError: "bypass", // "fail" (default), "bypass", or "/error-page"
cache: "manual", // Enable response caching
};
Scope path narrowly — path: "/*" intercepts every request, including static assets. A /* match runs the edge function on every CSS, JS, image, and font request, not just your HTML pages — adding latency to each asset and billing an edge invocation for it. Match only the routes you need (e.g. path: "/", path: "/app/*"), or keep a broad path but exclude static assets with excludedPath (e.g. excludedPath: ["/*.css", "/*.js", "/*.png", "/*.woff2"]).
Cache headers on an edge response do nothing without cache: "manual". Setting Cache-Control (or any cache header) on the Response an edge function returns has no effect unless the function also opts in with config.cache = "manual". It's both or neither: without the flag the response is never cached, no matter what headers you set.
An edge function runs only if it is bound to a path. Bind it either with an inline export const config = { path: ... } in the function file (shown above), or with an [[edge_functions]] entry in netlify.toml that names the file:
[[edge_functions]]
path = "/admin/*"
function = "auth" # runs netlify/edge-functions/auth.ts
A file in netlify/edge-functions/ with no path binding still deploys, but silently never runs. There is no build error and no warning — nothing routes a request to it, so it is never invoked. If an edge function "isn't doing anything," first confirm it declares a path inline or has a matching [[edge_functions]] entry.
When several edge functions match the same path, they run as a chain in this order:
netlify.toml run first, in the order they appear in the file (top to bottom).export const config) run next, in alphabetical order by filename.cache: "manual") always run after non-caching ones.To guarantee a specific order (e.g. an auth gate that must run before a personalization rewrite), declare the functions in netlify.toml in the order you want — don't depend on inline config, whose order is alphabetical by filename and easy to get wrong. Declaring the same function both inline and in netlify.toml merges them into an inline declaration (inline config wins), which forfeits the deterministic netlify.toml ordering.
In Netlify's request chain, edge functions execute before redirect and rewrite rules ([[redirects]], _redirects). Two consequences bite often:
path to the URL the client actually requests — an edge function declared on the target of a rewrite will not fire for requests that only reach that target via the rewrite.Response, the request chain stops there and redirect rules for that path never run. Return context.next() (or undefined) if you want redirects to still apply.Use context.next() to invoke the next handler in the chain and optionally modify the response:
export default async (req: Request, context: Context) => {
// Before: modify request or short-circuit
if (!isAuthenticated(req)) {
return new Response("Unauthorized", { status: 401 });
}
// Continue to origin/next function
const response = await context.next();
// After: modify response
response.headers.set("x-custom-header", "value");
return response;
};
Return undefined to pass through without modification:
export default async (req: Request, context: Context) => {
if (!shouldHandle(req)) return; // continues to next handler
return new Response("Handled");
};
export default async (req: Request, context: Context) => {
const { city, country, subdivision, timezone } = context.geo;
const ip = context.ip;
if (country?.code === "DE") {
return Response.redirect(new URL("/de", req.url));
}
};
Local dev with mocked geo: netlify dev --geo=mock --country=US
Read and write cookies through the context.cookies helper instead of hand-parsing the Cookie header or building Set-Cookie strings:
export default async (req: Request, context: Context) => {
const bucket = context.cookies.get("bucket"); // read from the request
context.cookies.set({ name: "bucket", value: "a" }); // set on the response
context.cookies.delete("legacy_session"); // tell the client to delete it
return context.next();
};
cookies.get(name) — reads a named cookie from the incoming request.cookies.set(options) — sets a cookie on the outgoing response (same option shape as the web CookieStore.set standard).cookies.delete(name) — instructs the client to delete the cookie.Use Netlify.env (not process.env or Deno.env):
const secret = Netlify.env.get("API_SECRET");
import { randomBytes } from "node:crypto";import X from "https://esm.sh/package")For URL imports, use an import map:
// import_map.json
{ "imports": { "html-rewriter": "https://ghuc.cc/worker-tools/html-rewriter/index.ts" } }
# netlify.toml
[functions]
deno_import_map = "./import_map.json"
| Use Edge Functions for | Use Serverless Functions for |
|---|---|
| Low-latency responses | Long-running operations (up to 15 min) |
| Request/response manipulation | Complex Node.js dependencies |
| Geolocation-based logic | Database-heavy operations |
| Auth checks and redirects | Background/scheduled tasks |
| A/B testing, personalization | Tasks needing > 512 MB memory |
| Resource | Limit |
|---|---|
| CPU time | 50 ms per request |
| Memory | 512 MB per deployed set |
| Response header timeout | 40 seconds |
| Code size | 20 MB compressed |
claude plugin install netlify-skills@claude-plugins-officialBuilds and deploys Vercel Edge Functions for low-latency API routes, geolocation routing, A/B testing, and authentication at the edge. Includes middleware patterns.
Guides writing Netlify serverless functions with modern syntax, TypeScript, path routing, background/scheduled tasks, streaming, and method routing.
Edge middleware for EdgeOne Makers — request interception, redirects, rewrites, auth guards, A/B testing, and header injection at the edge (V8 runtime).