mcpField
N
netlify-edge-functions

netlify-edge-functions

github.com/netlify开发473.2k 次安装6 天前更新
代码仓库

关于 netlify-edge-functions

netlify-edge-functions 是面向开发者的 Netlify Edge Functions 开发技能,说明如何配置请求路由、处理中间件及访问请求上下文。内容还涵盖缓存、错误处理、环境变量和执行限制。

主要功能

  1. 01配置路径、模式和请求条件
  2. 02通过中间件修改源站响应
  3. 03使用重写和重定向
  4. 04读取 Cookie、地理位置和用户上下文
  5. 05配置响应缓存和错误处理

适用场景

  1. 01按路径或请求条件执行边缘逻辑
  2. 02修改博客页面的源站响应
  3. 03基于用户或地理信息限制访问
  4. 04为可复用响应配置缓存

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/netlify/context-and-tools --skill netlify-edge-functions

安装后在 Claude Code 中输入/netlify-edge-functions确认已加载。

详细介绍

Netlify Edge Functions

使用此表单

// 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" };
  • 一个文件对应一个函数:默认导出的处理程序,以及带有路由的 config 导出。也可以在 netlify.toml 中声明路由。
  • Request 和 Response 是全局对象。从 @netlify/edge-functions 导入 Context 和 Config 类型。
  • 使用 Netlify.env.get() 读取环境变量。process.env 仅适用于 Next.js 中间件。

请避免以下情况:

  • 文件没有 config.path 或 pattern,且没有 [[edge_functions]] 条目。它会部署,但永远不会运行。
  • 在 deno.json 中使用导入映射。这不受支持。请使用单独的文件,并设置 [functions] deno_import_map。
  • 在 netlify.toml 中设置变量。边缘函数无法读取这些变量。
  • 将 Netlify.env.set() 或 Netlify.env.delete() 用作持久化机制。它们只影响当前调用。
  • 使用 fetch() 请求当前路径所在的同一站点。请改用 context.next()。

⚠️ 易踩的坑

  • 没有路由 = 静默无操作。 边缘函数不会自动获得 URL。
  • path: "/*" 会处理每个请求,包括静态资源。请缩小匹配范围,或添加 excludedPath。
  • 缓存需要同时具备两部分: 配置中的 cache: "manual",以及返回的 Response 上的缓存标头。只设置一项不会缓存。
  • 缓存函数会遮蔽静态文件。 在 /* 上缓存的函数会返回 /cat.png,而不是该文件。
  • 返回 Response 会终止调用链。 该路径的重定向规则不会运行。
  • 同站点的 fetch() 和 URL 重写会启动新的请求链。 匹配的边缘函数会再次运行。要访问同一路径上的静态资源或无服务器函数,请使用 context.next()。
  • 请求正文只能读取一次。 执行 await req.json() 后,将 new Request(req, { body }) 传给 context.next()。
  • 同名的 foo.ts 和 foo.js: TS 文件会被忽略,JS 文件会部署。
  • 每个请求的 CPU 时间为 50 ms,包括 waitUntil 工作。
  • 启用 Split Testing 的站点不会执行边缘函数。
  • 自定义标头和基本身份验证标头不适用于边缘函数。
  • 不支持 HIPAA 合规托管。
  • netlify.app 位于 Public Suffix List 中。 跨子域 Cookie 需要自定义域名。

文件

内容位置
函数<base>/netlify/edge-functions/*.{js,ts,jsx,tsx}
自定义目录[build] edge_functions = "my-dir"(相对于基础目录;请将其放在发布目录之外)
toml 中的名称不含扩展名的文件名(auth.ts → function = "auth")
导入映射任意 JSON 文件,通过 [functions] deno_import_map = "./import_map.json" 引用

返回值

返回值效果
Response发送给客户端;调用链结束
URL同站点重写,状态码为 200,地址栏保持不变
undefined / return;跳过此函数;调用链继续
端点
export default async () =>
  new Response("Hello, World!", { headers: { "content-type": "text/html" } });
export const config = { path: "/api/hello" };
重定向 / 重写
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" };

重写目标必须是同一站点。要访问其他站点,请使用 fetch。

中间件(修改源站响应)
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/*" };

只有在需要响应正文时才调用 context.next()。如果只返回 undefined,调用链会自行继续。

条件请求
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);
};

默认情况下,next() 会移除条件标头,因此始终返回完整响应。

读取正文后继续
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 用户门禁(私有 / 受团队登录保护的项目)
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/*" };
响应后的工作
export default async (req: Request, context: Context) => {
  context.waitUntil(fetch("https://analytics.example.com/event", { method: "POST" }));
  return new Response("ok");
};

waitUntil 不会阻塞响应,但仍计入 50 ms 的 CPU 时间限制。

流式 SSR(.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

属性说明
cookies.get(name) / .set(options) / .delete(name | options)set 和 delete 使用 CookieStore 选项格式;它们作用于发出的响应
geocity、country.{code,name}、subdivision.{code,name}、latitude、longitude、timezone、postalCode(格式各异)
ip客户端 IP
next(options?) / next(request, options?)链中下一个项目返回的 Promise<Response>
paramspath 中 URLPattern 的命名组(/pets/:name → { name })。对于查询字符串,请使用 request.url
user{ id, email?, expiresAt } 或 undefined。这是 Netlify 账户,不是 Netlify Identity;对于 Identity,请使用 @netlify/identity 中的 getUser()
waitUntil(promise)在发送响应后运行
requestId、account.id、server.region—
site{ id, name, url }
deploy{ context, id, published, skewProtectionToken }

Netlify 不会向请求添加客户端信息标头。请改为从 context 读取客户端信息。

Netlify.context 提供相同的对象,但仅在处理程序内部可用。在处理程序外部,它为 null。

Netlify.env:get(name)、has(name)、toObject(),以及仅限调用期间有效的 set 和 delete。若要持久更改变量,请使用 env API。

Web API:

  • console、atob、btoa
  • fetch、Request、Response、URL、File、Blob
  • TextEncoder/TextDecoder(及其 Stream 变体)、Performance
  • Web Crypto:randomUUID、getRandomValues、SubtleCrypto
  • WebSocket
  • setTimeout、clearTimeout、setInterval
  • Streams:ReadableStream、WritableStream、TransformStream
  • URLPattern

模块:

  • Node 内置模块:使用 node: 前缀(import { randomBytes } from "node:crypto")。
  • Deno 模块:通过 URL 或 import map 导入。
  • npm 包(beta):按名称导入。包含原生二进制文件或运行时动态导入的包可能无法正常工作。

声明

Inline config
键值
pathURLPattern 字符串或数组;必须以 / 开头
excludedPathURLPattern 字符串或数组;必须以 / 开头
pattern / excludedPattern与 path / excludedPath 对应的正则表达式选项
methodHTTP 方法字符串或数组
header{ name: true | false | "regex" };名称不区分大小写;多值标头会与逗号拼接后的列表进行匹配
onError"fail"(默认值)、"/custom-path" 或 "bypass"
cache"manual"
export const config: Config = {
  path: "/*",
  excludedPath: ["/*.css", "/*.js", "/img/*"],
  header: { "user-agent": "(iPhone|Android)" },
};

netlify-agent-category 请求标头会将任意 user-agent 映射到客户端类别。

netlify.toml

toml 格式支持 function、path、excludedPath、pattern、excludedPattern、[edge_functions.header] 和 cache。请在 inline 中设置 onError。需要明确指定同一路径上多个函数的执行顺序时,请使用 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

对 /admin 的请求会依次运行 auth、common,然后是 injector。

顺序

Netlify 会针对请求路径按以下顺序遍历两次。第一轮仅运行未配置缓存的函数。第二轮仅运行已缓存的函数。

  1. 在配置文件中声明的框架生成函数。
  2. netlify.toml 中从上到下的声明。
  3. 框架和集成生成的 inline 声明。
  4. 按文件名的字母顺序排列的 inline 声明。

如果同一个函数同时在 toml 和 inline 中声明,配置会合并并按第 4 步处理;对于重复字段,以 inline 为准。

边缘函数执行完后,会运行重定向规则,除非某个函数已返回 Response。重定向到新路径后,会针对该路径重复整个流程。

声明在静态重写的目标路径上的边缘函数不会对重写后的请求运行。

响应缓存

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" };
  • 必须在函数代码中设置标头。支持的标头:
    • Cache-Control、CDN-Cache-Control、Netlify-CDN-Cache-Control
    • Expires(会被 max-age/s-maxage 覆盖)
    • Vary、Netlify-Vary
  • 请参阅缓存概述。
  • 同一部署上下文中的新部署会使缓存的响应失效。
  • 命中缓存时会跳过调用,且不会计入调用限制。
  • 缓存可重用的端点输出,例如 SSR。
  • 不要缓存路由或个性化中间件,例如基于 Cookie 的产品排序。
  • 本地开发没有缓存。本地会忽略缓存标头。

错误处理(onError)

值效果
"fail"(默认)链停止;显示通用错误页面
"/unavailable"重写到同一站点路径;不会调用该路径上的边缘函数
"bypass"跳过发生故障的函数;链继续执行

对于关键逻辑,应采用失败关闭:内部身份验证使用默认值,面向客户的身份验证使用自定义路径。对于本地化等锦上添花的功能,应采用失败开放(bypass)。

环境变量

  • 变量必须具有 Functions 作用域。
  • Build 作用域的变量仅在构建时存在;如有需要,请在构建期间嵌入其值。
  • 更改变量后需要重新部署。
  • 部署信息(站点、区域、部署 ID)来自 context,而不是环境变量。

本地开发 / 部署 / 日志

  • 运行 npm install netlify-cli -g,然后运行 netlify dev。访问 http://localhost:8888/<path>。
    • 更改会在下一个请求时生效。
    • 使用 --edge-inspect 或 --edge-inspect-brk 进行调试。
    • 使用 --geo=mock(旧金山)模拟位置,或使用 --geo=mock --country=DE。
    • 在本地,context.user 未定义。
  • 手动部署需要 CLI ≥ 12.2.8。部署是原子性的,Deploy Previews 和回滚也包含边缘函数。
  • 日志:在 Netlify UI 中,前往 Cloud compute > Edge functions(https://app.netlify.com/projects/{site_name}/edge-functions)。
    • 对于较早的部署:Deploys → 打开该部署 → Edge Functions。
    • console.log 的输出会附带函数名称标签。
    • 保留时间 ≥ 24 小时(部分套餐为 7 天)。
    • Enterprise 套餐提供 Log Drains。

限制

限制值
Bundle 大小压缩后 20 MB
内存(每组已部署函数)512 MB
每个请求的 CPU 时间50 ms(不包括 I/O 等待)
响应标头超时40 s
  • 预渲染不适用于由边缘函数提供服务的路径。
  • 多个会生成边缘函数的框架插件可能会发生冲突。

文档:API · Declarations · Optional configuration · Limits · Examples

Netlify house rules(edge-functions)

这些是组织约定和从实践中总结的注意事项,并非文档事实;ctx-gen 会将它们合并到渲染后的技能中,且不会自动生成。 内容摘自之前手工编写的 netlify-edge-functions 技能;由技能维护者负责维护。

  1. 先查阅框架的适配器/参考文档:自定义边缘函数如果重复实现适配器生成的中间件,会导致冲突。只有当框架不会为该任务生成边缘函数时,才手动编写。
  2. 将 path 的范围设得尽可能窄。path: "/*" 会拦截每个请求——包括静态资源——给每个请求增加延迟,并为其计费一次边缘调用。
  3. 没有关联路由的边缘函数(没有 config 导出,也没有 netlify.toml 声明)仍会部署,但会悄无声息地从不运行:既不会出现构建错误,也不会有警告。当“我的边缘函数什么也没做”时,先检查路由。
  4. 根据工作负载特征选择边缘函数还是无服务器函数:边缘函数适用于低延迟的请求/响应处理、地理位置逻辑、身份验证检查/重定向和 A/B 个性化;无服务器函数适用于长时间运行的任务(最长 15 分钟)、依赖较重的 Node.js、数据库密集型操作、后台/定时任务,或内存需求超过 512 MB 的任务。
  5. 如果 config 中没有设置 cache: "manual",边缘响应上的缓存标头不会生效——二者必须同时设置,或者都不设置。除非函数也选择启用缓存,否则在返回的 Response 上设置 Cache-Control 不会生效。
  6. 说明声明处理顺序时,要说明两轮循环,而不只是排序:Netlify 会完整执行两次声明顺序——第一轮只运行未配置缓存的边缘函数,第二轮运行已配置缓存的边缘函数。只说“非缓存函数先于缓存函数”,却不说明循环过程,答案并不完整。
  7. 对于私有项目或启用了团队登录保护的项目,context.user 是发出请求的 Netlify 用户(包含 id、email、expiresAt)。应使用它进行个人级检查和个性化处理,而不是另加一层登录;也绝不能从客户端发送的标头或 Cookie 中获取身份:Netlify 会在边缘函数运行前验证登录状态。所有示例都必须处理 context.user 为 undefined 的情况(公开项目、基本密码保护、netlify dev);email 也可能是 undefined,因此应依据 id 做判断。这是 Netlify 账户,不是 Netlify Identity 用户。

评论

支持 Markdown