К основному содержанию
скилл CX Codex

netlify-edge-functions — скилл для Claude Code: edge-функции Netlify

Помогает писать, роутить и отлаживать edge-функции Netlify на Deno: middleware, редиректы по гео, кэш, стриминг SSR.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add netlify/context-and-tools --skill netlify-edge-functions -a claude-code
Открыть на GitHub

Что делает netlify-edge-functions

Скилл описывает функции в netlify/edge-functions, которые выполняются на границе сети перед статикой и serverless-функциями. Один файл — одна функция: обработчик через default export плюс экспорт config с маршрутом, либо маршрут в netlify.toml. Типы Context и Config импортируются из @netlify/edge-functions, переменные окружения читаются через Netlify.env.get().

Внутри разобраны рабочие шаблоны: endpoint, редирект и rewrite по стране или cookie, middleware с изменением ответа origin, чтение тела запроса с продолжением цепочки, гейт по пользователю Netlify, работа после ответа через waitUntil и стриминг SSR в .tsx. Отдельный блок про грабли: функция без маршрута разворачивается, но не запускается; path: "/*" срабатывает на каждый запрос, включая ассеты; кэш требует и cache: "manual" в конфиге, и заголовков на возвращаемом Response.

Есть жёсткие рамки. На запрос даётся 50 ms CPU, и работа в waitUntil тоже в них входит. Функции не выполняются на сайтах с включённым Split Testing, не поддерживаются на HIPAA-хостинге, а заголовки Custom Headers и basic-auth к ним не применяются. Если у вас включён Split Testing, это повод проверить, не молчит ли ваша edge-функция просто потому, что её не запускают.

Какие задачи решает

  • поставить логин-гейт или auth middleware перед статикой
  • редиректить посетителей по стране или cookie
  • подставить геолокацию в страницу и персонализировать её
  • прогнать A/B-тест на edge без правки бандла
  • закэшировать ответ edge-функции и не отдать вместо этого картинку

Примеры запросов агенту

  • напиши edge-функцию в netlify/edge-functions/auth.ts, которая закрывает /admin/* для всех, кроме админов
  • сделай редирект на /subscriber-sale для посетителей из Парижа с cookie promo-code
  • почему моя edge-функция не кэшируется, хотя я задал cache: "manual"?

Как установить netlify-edge-functions

1Через Skills CLI (Vercel)

npx skills add netlify/context-and-tools --skill netlify-edge-functions -a claude-code

2Вручную: положить файлы

~/.claude/skills/netlify-edge-functions/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/netlify-edge-functions/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Из того же набора14

netlify-frameworks Скилл подсказывает агенту настройки сборки, SSR-адаптеры и переменные окружения для деплоя веб-фреймворков на Netlify. CX Codex +1 3 200установок netlify-functions Помогает агенту писать и настраивать серверless-функции Netlify на TypeScript, JavaScript или Go: маршруты, cron, лимиты, чтение секретов. CX Codex +1 3 200установок netlify-caching Скилл помогает настроить кеширование ответов на CDN Netlify для Functions и Edge Functions. Пригодится при работе с заголовками кеша, тегами и сбросом данных. CX Codex +1 3 100установок netlify-forms Скилл для работы с серверлес-формами на Netlify: разметка, AJAX-отправка, защита от спама, уведомления. Пригодится, когда форма молча не регистрируется. CX Codex +1 3 100установок netlify-mcp-servers Скилл помогает развернуть собственный MCP-сервер на бессерверной функции Netlify. Пригодится, когда нужно дать агенту доступ к API или данным проекта через HTTP. CX Codex +1 3 000установок netlify-identity Скилл для добавления входа, регистрации и OAuth на сайт Netlify через @netlify/identity. Пригодится, когда нужно закрыть контент по ролям. CX Codex +1 3 000установок netlify-ai-gateway Скилл учит агента вызывать OpenAI, Anthropic, Gemini, TypeSafe и OpenRouter из Netlify Functions без своих ключей и аккаунтов провайдеров. CX Codex +1 3 000установок netlify-image-cdn Помогает настроить трансформацию изображений на лету через эндпоинт Netlify Image CDN. Пригодится при сжатии картинок и генерации плейсхолдеров. CX Codex +1 3 000установок

Похожие

netlify-development Mindrally Скилл с правилами сборки и деплоя на Netlify: serverless- и edge-функции, Blobs, Image CDN, конфигурация сборки. Пригодится, когда агент пишет функции или правит netlify.toml. CX Codex +1 665установок netlify-official-cursorrules-prompt-file PatrickJS Правило для Cursor, которое задаёт агенту официальные соглашения Netlify: типы функций, работа с окружением и запрет лишних CORS-заголовков. ★ 41 011звёзд репо supabase-server supabase Подсказывает агенту, как писать Edge Functions, Hono-приложения и вебхуки на @supabase/server: режимы auth, клиенты, проверка входящих запросов. CX Codex +1 9 700установок vercel-development Mindrally Набор правил для ИИ-агента: как писать код под Next.js App Router, React Server Components и Vercel AI SDK. Пригодится, когда агент лепит клиентские компоненты там, где хватило бы серверных. CX Codex +1 712установок aws-lambda-durable-functions aws Скилл учит ИИ писать долгоживущие функции AWS Lambda с сохранением состояния до одного года. Пригодится при сборке серверлесс-пайплайнов и пошаговых сценариев для ИИ-агентов. CX Codex +1 5 800установок inngest-durable-functions inngest Скилл учит агента писать отказоустойчивые функции Inngest: шаги, мемоизация, ретраи, идемпотентность и отмена. CX Codex +1 3 800установок

Рядом в теме «DevOps и инфраструктура»

Всё по теме «DevOps и инфраструктура» →

Вопросы о netlify-edge-functions

Как установить netlify-edge-functions в Claude?

Выполните npx skills add netlify/context-and-tools --skill netlify-edge-functions -a claude-code. Скилл появится в ~/.claude/skills/netlify-edge-functions/SKILL.md.

Что делает netlify-edge-functions?

Даёт агенту шаблоны и правила для edge-функций Netlify на Deno: маршрутизация, редиректы и rewrite, middleware, кэш, стриминг SSR. Отдельно разбирает типичные ошибки вроде функции без маршрута.

Почему edge-функция не кэшируется?

Нужны оба условия: cache: "manual" в конфиге функции и заголовки кэша на возвращаемом Response. С одним из них кэша не будет.

Исходный текст

SKILL.md · MIT
---
name: netlify-edge-functions
description: Write, route, cache, and debug Netlify Edge Functions, the Deno-based TypeScript or JavaScript handlers in netlify/edge-functions that run at the network edge in front of static files and serverless functions. Activate this skill whenever someone wants to add auth middleware or a login gate, redirect or rewrite visitors by country or cookie, localize or personalize a page with geolocation, run A/B testing, transform or inject HTML into a response, stream server-side rendering at the edge, or cache an edge response. Also use it when an edge function silently does nothing, runs in the wrong order, ignores its Cache-Control headers, or cannot read an environment variable.
---

# Netlify Edge Functions

## Use this form

```ts
// netlify/edge-functions/hello.ts
import type { Config, Context } from "@netlify/edge-functions";

export default async (request: Request, context: Context) => {
  return new Response(`Hello from ${context.geo.country?.name ?? "somewhere"}`);
};

export const config: Config = { path: "/hello" };
```

- One file = one function: a default-export handler plus a `config` export with the route. Alternatively, declare the route in `netlify.toml`.
- `Request` and `Response` are global. Import `Context` and `Config` types from `@netlify/edge-functions`.
- Read env vars with `Netlify.env.get()`. `process.env` works only for Next.js middleware.

**Avoid these:**
- A file with no `config.path` or `pattern` and no `[[edge_functions]]` entry. It deploys but never runs.
- Import maps in `deno.json`. They are unsupported. Use a separate file with `[functions] deno_import_map`.
- Variables set in `netlify.toml`. They are invisible to edge functions.
- `Netlify.env.set()` or `Netlify.env.delete()` as persistence. They affect only the current invocation.
- `fetch()` to the same site for the current path. Use `context.next()` instead.

## ⚠️ Footguns

- **No route = silent no-op.** Edge functions get no automatic URL.
- **`path: "/*"` runs on every request**, including assets. Narrow it, or add `excludedPath`.
- **Caching needs both parts:** `cache: "manual"` in config AND cache headers on the returned `Response`. With only one, nothing is cached.
- **Cached functions shadow static files.** A cached function on `/*` serves `/cat.png` instead of the file.
- **Returning a `Response` ends the chain.** Redirect rules for that path never run.
- **Same-site `fetch()` and `URL` rewrites start a new request chain.** Matching edge functions run again. Use `context.next()` to reach the static asset or serverless function at the same path.
- **The request body can be read once.** After `await req.json()`, pass `new Request(req, { body })` to `context.next()`.
- **Same-named `foo.ts` and `foo.js`:** the TS file is ignored and the JS file deploys.
- **50 ms CPU per request**, including `waitUntil` work.
- **Not executed** on sites with Split Testing enabled.
- **Custom Headers and basic-auth headers do not apply** to edge functions.
- **Not supported** on HIPAA-compliant hosting.
- **`netlify.app` is on the Public Suffix List.** Cross-subdomain cookies need a custom domain.

## Files

| What | Where |
|---|---|
| Functions | `<base>/netlify/edge-functions/*.{js,ts,jsx,tsx}` |
| Custom dir | `[build] edge_functions = "my-dir"` (relative to base dir; keep it outside the publish dir) |
| Name in toml | file name without extension (`auth.ts` → `function = "auth"`) |
| Import map | any JSON file, referenced by `[functions] deno_import_map = "./import_map.json"` |

## Return values

| Return | Effect |
|---|---|
| `Response` | sent to client; chain ends |
| `URL` | same-site rewrite, 200, address bar unchanged |
| `undefined` / `return;` | skip this function; chain continues |

### Endpoint
```ts
export default async () =>
  new Response("Hello, World!", { headers: { "content-type": "text/html" } });
export const config = { path: "/api/hello" };
```

### Redirect / rewrite
```ts
import type { Context } from "@netlify/edge-functions";

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)); // redirect
    // or: return new URL("/subscriber-sale", req.url);             // rewrite (same-site only)
  }
};
export const config = { path: "/sale" };
```
Rewrites must target the same site. To reach other sites, use `fetch`.

### Middleware (modify origin response)
```ts
import type { Context } from "@netlify/edge-functions";

export default async (request: Request, context: Context) => {
  if (new URL(request.url).searchParams.get("method") !== "transform") return;
  const response = await context.next();
  return new Response((await response.text()).toUpperCase(), response);
};
export const config = { path: "/blog/*" };
```
Call `context.next()` only when you need the response body. If you just return `undefined`, the chain continues on its own.

### Conditional requests
```ts
export default async (req: Request, { next }: Context) => {
  const res = await next({ sendConditionalRequest: true });
  if (res.status === 304) return res;
  return new Response((await res.text()).toUpperCase(), res);
};
```
By default, `next()` strips conditional headers, so it always returns a full response.

### Read body, then continue
```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) }));
};
```

### Netlify user gate (private / team-login-protected projects)
```ts
import type { Config, Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const user = context.user; // undefined: public project, password protection, netlify dev
  if (!user) return new Response("Forbidden", { status: 403 });
  const allowed = (Netlify.env.get("ADMIN_USER_IDS") ?? "").split(",");
  if (!allowed.includes(user.id)) return new Response("Forbidden", { status: 403 });
  // user.email may be undefined, so use it for display only
};
export const config: Config = { path: "/admin/*" };
```

### Post-response work
```ts
export default async (req: Request, context: Context) => {
  context.waitUntil(fetch("https://analytics.example.com/event", { method: "POST" }));
  return new Response("ok");
};
```
`waitUntil` does not block the response, but it still counts against the 50 ms CPU limit.

### Streaming SSR (`.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 (req: Request, context: Context) => {
  const stream = await renderToReadableStream(
    <html><body><h1>Hello {context.geo.country?.name}</h1></body></html>
  );
  return new Response(stream, { headers: { "Content-Type": "text/html" } });
};
export const config: Config = { path: "/hello" };
```

## `Context`

| Property | Notes |
|---|---|
| `cookies.get(name)` / `.set(options)` / `.delete(name \| options)` | `set` and `delete` use `CookieStore` option format; they act on the outgoing response |
| `geo` | `city`, `country.{code,name}`, `subdivision.{code,name}`, `latitude`, `longitude`, `timezone`, `postalCode` (format varies) |
| `ip` | client IP |
| `next(options?)` / `next(request, options?)` | `Promise<Response>` from the next item in the chain |
| `params` | URLPattern named groups from `path` (`/pets/:name` → `{ name }`). For the query string, use `request.url` |
| `user` | `{ id, email?, expiresAt }` or `undefined`. It is a Netlify account, not Netlify Identity; for Identity, use `getUser()` from `@netlify/identity` |
| `waitUntil(promise)` | runs after the response is sent |
| `requestId`, `account.id`, `server.region` | — |
| `site` | `{ id, name, url }` |
| `deploy` | `{ context, id, published, skewProtectionToken }` |

Netlify adds no client-info headers to the request. Read client info from `context` instead.

`Netlify.context` gives the same object, but only inside the handler. Outside it, it is `null`.

`Netlify.env`: `get(name)`, `has(name)`, `toObject()`, and invocation-scoped `set` and `delete`. To change variables persistently, use the [env API](https://open-api.netlify.com/#tag/environmentVariables).

Web APIs:
- `console`, `atob`, `btoa`
- `fetch`, `Request`, `Response`, `URL`, `File`, `Blob`
- `TextEncoder`/`TextDecoder` (and their Stream variants), `Performance`
- Web Crypto: `randomUUID`, `getRandomValues`, `SubtleCrypto`
- WebSocket
- `setTimeout`, `clearTimeout`, `setInterval`
- Streams: `ReadableStream`, `WritableStream`, `TransformStream`
- URLPattern

Modules:
- Node built-ins: use the `node:` prefix (`import { randomBytes } from "node:crypto"`).
- Deno modules: import by URL, or through an import map.
- npm packages (beta): import by name. Packages with native binaries or runtime dynamic imports may break.

## Declarations

### Inline `config`
| Key | Value |
|---|---|
| `path` | URLPattern string or array; must start with `/` |
| `excludedPath` | URLPattern string or array; must start with `/` |
| `pattern` / `excludedPattern` | regex alternatives to `path` / `excludedPath` |
| `method` | HTTP method string or array |
| `header` | `{ name: true \| false \| "regex" }`; case-insensitive names; multi-value headers match the comma-joined list |
| `onError` | `"fail"` (default), `"/custom-path"`, or `"bypass"` |
| `cache` | `"manual"` |

```ts
export const config: Config = {
  path: "/*",
  excludedPath: ["/*.css", "/*.js", "/img/*"],
  header: { "user-agent": "(iPhone|Android)" },
};
```
The `netlify-agent-category` request header maps any user-agent to a [client category](https://docs.netlify.com/build/user-agent-categories).

### `netlify.toml`
The toml form supports `function`, `path`, `excludedPath`, `pattern`, `excludedPattern`, `[edge_functions.header]`, and `cache`. Set `onError` inline. Use toml when you need explicit ordering of several functions on one path.

```toml
[[edge_functions]]
  path = "/admin"
  function = "auth"

[[edge_functions]]
  path = "/admin"
  function = "injector"
  cache = "manual"

[[edge_functions]]
  pattern = "/products/(.*)"
  excludedPattern = "/products/things/(.*)"
  function = "highlight"

[[edge_functions]]
  path = "/*"
  excludedPath = "/img/*"
  function = "common"
  [edge_functions.header]
  x-required = true
  x-forbidden = false
```
A request to `/admin` runs `auth`, then `common`, then `injector`.

### Order
Netlify walks the following order **twice** for the request path. The first pass runs only functions without caching configured. The second pass runs only cached functions.

1. Framework-generated functions declared in a config file.
2. Your `netlify.toml` declarations, top to bottom.
3. Framework- and integration-generated inline declarations.
4. Your inline declarations, alphabetical by file name.

If the same function is declared both in toml and inline, the configs are merged and treated as step 4; inline wins for duplicate fields.

After the edge functions, redirect rules run, unless a function already returned a `Response`. A redirect to a new path repeats the whole process for that path.

An edge function declared on the *target* of a static rewrite does not run for rewritten requests.

## Response caching
```ts
import type { Config } from "@netlify/edge-functions";

export default async () =>
  new Response("Hello world", { headers: { "cache-control": "public, s-maxage=3600" } });

export const config: Config = { path: "/hello", cache: "manual" };
```
- Headers must be set in the function code. Supported headers:
  - `Cache-Control`, `CDN-Cache-Control`, `Netlify-CDN-Cache-Control`
  - `Expires` (overridden by `max-age`/`s-maxage`)
  - `Vary`, `Netlify-Vary`
- See [caching overview](https://docs.netlify.com/build/caching/caching-overview).
- A new deploy in the same deploy context invalidates cached responses.
- Cached hits skip invocation, and they do not count toward invocation limits.
- **Cache** reusable endpoint output, such as SSR.
- **Don't cache** routing or personalization middleware, such as cookie-based product ordering.
- Local dev has no cache. Cache headers are ignored locally.

## Error handling (`onError`)
| Value | Effect |
|---|---|
| `"fail"` (default) | chain stops; generic error page is shown |
| `"/unavailable"` | rewrite to that same-site path; edge functions on it are not invoked |
| `"bypass"` | skip the failing function; chain continues |

Fail closed for critical logic: use the default for internal auth, or a custom path for customer-facing auth. Fail open (`bypass`) for nice-to-haves such as localization.

## Environment variables
- Variables must have the **Functions** scope.
- **Build**-scoped variables exist only at build time; embed their values during the build if needed.
- Changing a variable requires a new deploy.
- Deploy info (site, region, deploy id) comes from `context`, not from env vars.

## Local dev / deploy / logs
- Run `npm install netlify-cli -g`, then `netlify dev`. Visit `http://localhost:8888/<path>`.
  - Changes apply on the next request.
  - Debug with `--edge-inspect` or `--edge-inspect-brk`.
  - Mock location with `--geo=mock` (San Francisco), or `--geo=mock --country=DE`.
  - `context.user` is undefined locally.
- Manual deploys require CLI **≥ 12.2.8**. Deploys are atomic, and Deploy Previews and rollbacks include edge functions.
- Logs: in the Netlify UI, go to **Cloud compute > Edge functions** (`https://app.netlify.com/projects/{site_name}/edge-functions`).
  - For older deploys: **Deploys** → open the deploy → **Edge Functions**.
  - `console.log` output is tagged with the function name.
  - Retention is ≥ 24 h (7 days on some plans).
  - [Log Drains](https://docs.netlify.com/manage/monitoring/log-drains) are available on Enterprise plans.

## Limits
| Limit | Value |
|---|---|
| Bundle size | 20 MB compressed |
| Memory (per set of deployed functions) | 512 MB |
| CPU time per request | 50 ms (excludes I/O wait) |
| Response header timeout | 40 s |

- Prerendering does not apply to paths served by an edge function.
- Multiple framework plugins that generate edge functions may collide.

Docs: [API](https://docs.netlify.com/build/edge-functions/api.md) · [Declarations](https://docs.netlify.com/build/edge-functions/declarations.md) · [Optional configuration](https://docs.netlify.com/build/edge-functions/optional-configuration.md) · [Limits](https://docs.netlify.com/build/edge-functions/limits.md) · [Examples](https://edge-functions-examples.netlify.app/)

<!-- Gaps: no legacy/deprecated handler form is documented; whether `method` works in netlify.toml is unstated. -->
<!-- Undocumented: context.params with regex `pattern` declarations; monthly invocation limits per plan. -->

<!-- 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.
2. Scope `path` narrowly. `path: "/*"` intercepts every request — including
   static assets — adding latency to each one and billing an edge invocation
   for it.
3. 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.
4. 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.
5. 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.
6. 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.
7. On a private project or one with team login protection, `context.user`
   is the Netlify user who made the request (`id`, `email`, `expiresAt`).
   Use it for per-person checks and personalization instead of adding a
   second login, and never take identity from client-sent headers or
   cookies: Netlify verifies the login before the edge function runs. Every
   example must handle `context.user` being `undefined` (public projects,
   basic password protection, `netlify dev`), and `email` can be
   `undefined` too, so key decisions on `id`. This is a Netlify account,
   not a Netlify Identity user.

© netlify · MIT · netlify/context-and-tools

Смотрите также

Спонсоры проекта

Компании, которые помогают держать аналитику открытой