netlify/context-and-tools

netlify-edge-functions

Write and configure Netlify Edge Functions — TypeScript/JavaScript handlers running in a Deno runtime at the network edge.

Voir la source
Document Skill original

Rendu depuis le dépôt source en conservant titres, exemples, code, tableaux, liens et images.

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.

ts
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_functions under [build] in netlify.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/.tsx useful for SSR).
  • Same-name conflict: if my-function.ts and my-function.js both 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:

ts
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:

toml
[[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.

ts
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:

  1. Framework-generated functions declared in a config file.
  2. Your netlify.toml declarations (top-to-bottom order).
  3. Framework/integration-generated functions with inline config.
  4. 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 a URL starts a new request chain and re-runs matching edge functions. Use context.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 use fetch.
  • undefined / empty return; — bypass this function, continue the chain.

Modify a response as middleware by awaiting context.next():

ts
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:

ts
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):

ts
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:

ts
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:

ts
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`):

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 on netlify.app (Public Suffix List).
  • `next(options?)` / `next(request, options?)` — invoke the next item in the chain; returns a Promise<Response> you can modify. options.sendConditionalRequest: true for conditional requests. Only call next if you need the response body. Pass an explicit Request when you've read the body.
  • `params` — path params, e.g. path /pets/:name + request /pets/winter → {name:"winter"}. Query string: use request.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.

  1. Opt in: cache: "manual" (inline or netlify.toml).
  2. Set headers inline in the function code (not in netlify.toml):
ts
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.
ts
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.toml` are 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().
ts
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 install then 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:

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

bash
npm install netlify-cli -g
netlify dev        # runs edge functions on local requests
# visit http://localhost:8888/test
  • Debug: netlify dev with --edge-inspect or --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; waitUntil work 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 fetch for 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/

<!-- system: agent-context/edge-functions/system.md — human-owned, merged by ctx-gen; edit system.md, not this section -->

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.

  1. 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.

  1. Scope path narrowly. path: "/*" intercepts every request — including

static assets — adding latency to each one and billing an edge invocation for it.

  1. 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.

  1. 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.

  1. Cache headers on an edge response do nothing without cache: "manual" in

config — it's both or neither. Setting Cache-Control on the returned Response has no effect unless the function also opts in.

  1. 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.

du même dépôt

Autres Skills

Tous les Skills
netlify
Communauté

netlify-access-control

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, make a project private/public, restrict a site to your team, require SSO to view a site, set up company-wide app SSO, or invite users to a private project. Also use it for SSO-session symptoms on pro

installations
2
GitHub Stars
40
Mis à jour
12 oct.
netlify
Communauté

netlify-agent-runner

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.

installations
2
GitHub Stars
40
Mis à jour
12 oct.
netlify
Communauté

netlify-ai-gateway

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 chatbot, text summarizer, image generator, joke/content generator, form-submission routing or analysis, or any LLM call — or when wiring the OpenAI/Anthropic/Gemini/TypeSafe/OpenRouter SDK into a Netl

installations
2
GitHub Stars
40
Mis à jour
12 oct.
netlify
Communauté

netlify-blobs

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 seeding deploy-specific data. Reach for this for key/value or object storage from Functions, Edge Functions, or Build Plugins — not for per-user, transactional, or relational data (use Netlify DB for th

installations
2
GitHub Stars
40
Mis à jour
12 oct.