KernelRanch
返回文章列表

Pages Functions:用目录结构写后端

2026-11-10 #Cloudflare

读完这篇你能做到:在一个已经能部署的 Pages 项目里加一层请求处理,用它接表单、写 JSON 接口、做鉴权或 A/B 分流,并且知道每个文件会被哪条 URL 命中。全部逻辑放在 functions/ 目录,不需要额外配置路由表。

起点是先有一个能跑起来的 Pages 项目,没有的话从 67-pages-getting-started.md 开始。如果你更想用 Workers 那套 export default { fetch } 的写法,先看 54-workers-fetch-handler-tutorial.md,两者的 Request / Response 处理方式一致,差别只在路由来源。

1. 目录即路由

functions/ 的层级直接映射成 URL 路径,文件名去掉扩展名就是最后一段。

文件路径命中的 URL
functions/index.jsexample.com/
functions/helloworld.jsexample.com/helloworld
functions/fruits/index.jsexample.com/fruits
functions/fruits/apple.jsexample.com/fruits/apple

两条容易踩的规则:

尾斜杠可选,/foo 和 /foo/ 都会落到 functions/foo.js。如果 foo.js 和 foo/index.js 同时存在,/foo 和 /foo/ 都交给 foo/index.js。

没有 Function 命中时,请求回落到同名静态文件;静态文件也没有,才走 Pages 默认的 404 行为。

2. 动态段

单层参数用一层方括号,多层用两层。

// functions/users/[user].js
export function onRequest(context) {
  return new Response(context.params.user);
}

请求 /users/daniel 时,context.params.user 是字符串 daniel。

// functions/users/[[path]].js
export function onRequest(context) {
  return new Response(JSON.stringify(context.params.path));
}

/users/daniel/xyz/123 会得到数组 ["daniel","xyz","123"]。方括号数量决定了值是字符串还是数组,这一点在写 catch-all 路由时很容易搞混。

路由优先级按通配符数量排序,越具体的规则越先匹配:users/special.js 会先于 users/[user].js 命中 /users/special。

3. 处理器命名

一个文件可以导出多个处理函数,按 HTTP 方法分派:

// functions/api/feedback.js
export async function onRequestPost(context) {
  const body = await context.request.json();
  if (!body.message) {
    return new Response(JSON.stringify({ ok: false, error: 'message required' }), {
      status: 400,
      headers: { 'content-type': 'application/json' },
    });
  }
  return new Response(JSON.stringify({ ok: true, id: crypto.randomUUID() }), {
    status: 201,
    headers: { 'content-type': 'application/json' },
  });
}

export function onRequestGet() {
  return new Response('send a POST', { status: 405 });
}

onRequest 是所有方法的总入口。只写 onRequest 时,GET 和 POST 都走它;写了 onRequestGet,GET 就只走那个函数。没定义的方法返回 405。

4. context 上有哪些东西

字段用途
context.request标准 Request 对象,读 body、header、URL
context.env环境变量与绑定(KV、D1、R2)
context.params动态段取值
context.next()交给下一个处理器,中间件里用
context.data中间件之间传数据
context.waitUntil()响应返回后继续执行的任务,例如写日志

context.env 里读不到控制台没配的变量,值为 undefined 而不是报错,线上排查时容易看漏。变量与密钥在 Pages 项目的 Settings 里维护,生产与预览各一套,名字对不上时函数拿到的是空值。

5. 中间件

放在 functions/_middleware.js 的处理器会包住同目录及子目录下所有路由:

// functions/_middleware.js
export async function onRequest(context) {
  const response = await context.next();
  response.headers.set('x-frame-options', 'DENY');
  return response;
}

中间件按目录层级从外到内依次执行,context.next() 之后的代码在响应回来时执行,适合统一加安全头、记录耗时。多层中间件共享 context.data,可以拿来传递已解析的用户身份。

6. 计费从哪一刻开始

纯静态的 Pages 项目,请求不计入 Workers 额度。项目里出现 functions/ 目录之后,默认所有请求都会调用 Function,于是全部开始计数。

要保住静态请求的免费额度,用 _routes.json 限定调用范围:

{
  "version": 1,
  "include": ["/api/*"],
  "exclude": []
}

这份文件放在构建输出目录里,和产物一起上传,目录的关系见 70-pages-build-cache.md。用 Git 集成或 wrangler pages deploy 发布时,只要检测到 functions/,平台会自动生成一份默认规则;想自己控制就显式提供。include 之外的路径直接由静态资源返回,不进 Function。

免费额度上,Functions 的请求计入 Workers 免费档的每天 100,000 次,单次 CPU 时间上限 10 毫秒。中等流量的站点够用,接口里做大量计算或大文件转码就不合适了。各档位的完整对照在 56-free-tier-limits-cheatsheet.md。

7. 四个实战细节

判断资源不存在时返回 404 加一句说明,不要返回 200 加空 body。前者在前端和监控里都能立刻看出问题,后者会让人以为是数据为空。

跨域请求要自己处理。函数默认不带 CORS 头,前端从别的域调接口时需要在分支里回 OPTIONS,并补上 Access-Control-Allow-Origin。放在中间件里统一处理比每个文件写一遍省事。

大响应用流式返回。不要先把数据全部 await 拼成字符串再交给 Response,直接把 ReadableStream 塞进去,边生成边发,内存占用和首字节时间都会好一些。

日志用 console.log,在部署详情的 Functions 日志里看。实时性一般,需要长期留存的记录用 context.waitUntil() 往外部服务发一份,不阻塞响应返回。

常见问题

functions 目录该放仓库根目录还是构建输出目录。

放仓库根目录,和 package.json 同级。它不参与构建,平台单独识别。_routes.json、_headers、_redirects 则要放进构建输出目录,两者的位置不一样。

本地怎么调。

wrangler pages dev ./dist 会把静态产物和 functions/ 一起跑起来,改了函数代码会自动重载。绑定需要本地模拟时,用 --kv、--d1 之类的参数传入。

TypeScript 能用吗。

可以。装 @cloudflare/workers-types,处理器参数标注成 PagesFunction<Env>,context.env 就有类型了。类型只在编译期生效,运行时还是 JS。

小结

Pages Functions 的设计是把路由交给文件系统:建文件就是建接口,方括号就是参数,_middleware.js 就是横切逻辑。写起来比配路由表省事,代价是路由结构和目录结构得保持一致,重构时要连文件一起挪。

上线前记得处理 _routes.json。多一层 Function 调用换来的是灵活性,但如果静态页面也被卷进 Function,免费额度的消耗速度会和你预期差很远。

数据来源与检索时间

检索时间 2026-11-10。

来源URL对应数据
Pages Functions Routinghttps://developers.cloudflare.com/pages/functions/routing/文件到路由的映射、尾斜杠规则、[user] 与 [[path]] 取值类型、路由优先级、_routes.json 的作用与自动生成条件
Pages Limitshttps://developers.cloudflare.com/pages/platform/limits/Functions 请求计入 Workers 额度;_headers 最多 100 条规则、单条 2,000 字符

核验说明

  • 文档部分:目录到路由的映射表、动态段返回字符串还是数组、_routes.json 的自动生成逻辑,均来自 2026-11-10 抓取的官方 Routing 页面原文。
  • 未执行:本机 Token 无 Pages 权限,wrangler pages dev 与 wrangler pages deploy 都没有运行。示例代码是依据官方处理器签名写的,未在真实 Pages 项目里跑过请求。
  • 未执行:控制台里配置绑定与环境变量的步骤未操作,依据文档描述。
  • 本地验证(部分):用只有静态文件的目录跑 wrangler pages dev,日志出现 No Functions. Shimming...,即未检测到 functions/ 时的行为与文档一致。带 functions/ 目录的实际路由、context.params 取值、中间件串联都没有在本机运行过。
  • 额度数字(每天 100,000 次请求、CPU 10 毫秒)来自 Workers 免费档,Pages Functions 沿用同一套计数,未做实际压测。