六维教程

Pages Functions 基础

纯静态站点能覆盖博客、文档、营销页这些场景,但一旦要做表单提交、接口代理、动态鉴权,纯静态就不够用了。Pages Functions 给静态站点加上动态能力,让你在同一个项目里既放静态文件,又写服务端接口。这一篇从它的目录约定讲起,覆盖端点编写和动态路由,把基础用法走通。

Pages Functions 是什么

Pages Functions 是 Pages 内置的 Serverless 函数能力。你在项目里建一个 functions 目录,目录里的每个文件就是一个接口端点,Pages 会按文件路径自动生成对应的访问路由。

它运行在 Cloudflare Workers(Cloudflare 的边缘计算函数服务)运行时上,代码在全球边缘节点执行,请求不用绕回中心机房。但不用单独学 Workers 那套开发流程,Pages 把目录约定和路由都封装好了,按文件放对位置就能用。

Functions 和静态文件的关系如下。

维度 静态文件 Pages Functions
存放位置 构建输出目录 functions 目录
执行时机 构建时生成 请求时执行
内容类型 固定的 HTML/CSS/JS 动态生成的响应
计费 静态请求免费不限量 按函数调用次数计费

一个 Pages 项目可以同时有静态文件和 Functions,互不冲突。

目录约定

Functions 的路由完全由 functions 目录的文件结构决定,这叫文件路由。目录在哪一层、文件叫什么名,访问路径就对应什么。

假设目录结构如下。

项目根目录/
├── functions/
│   ├── index.js
│   ├── helloworld.js
│   └── api/
│       ├── index.js
│       └── users.js
└── dist/   (静态文件输出目录)

对应的访问路由如下。

文件路径 访问路由
functions/index.js /
functions/helloworld.js /helloworld
functions/api/index.js /api
functions/api/users.js /api/users

两条规律。一是 index.js 代表它所在目录本身,二是文件名就是路径段。functions 目录必须放在项目根目录,不能放到 distsrc 里,否则 Pages 识别不到。

文件可以用 .js.ts,TypeScript 会在构建时自动编译。

编写第一个端点

每个端点文件导出一个 onRequest 函数,接收一个 context 对象,返回一个 Response。最简单的示例如下。

// functions/api/hello.js
export function onRequest(context) {
  return new Response("Hello from Pages Functions");
}

部署后访问 /api/hello 就能看到这段文字。context 对象包含请求处理需要的所有信息,常用的几个属性如下。

属性 作用
context.request 当前的 Request 对象,含请求头、方法、体
context.env 环境变量和绑定的资源
context.params 动态路由匹配到的参数
context.next() 调用下一个处理函数,中间件里用到
context.waitUntil 注册后台异步任务

返回 JSON 接口是更常见的场景,写法如下。

// functions/api/time.js
export async function onRequest(context) {
  const data = {
    now: new Date().toISOString(),
    region: context.request.cf?.colo || "unknown",
  };
  return new Response(JSON.stringify(data), {
    headers: { "Content-Type": "application/json" },
  });
}

context.request.cf 是 Cloudflare 注入的请求元信息,colo 是处理这次请求的边缘节点代码,能直观看到边缘计算的就近响应。

按方法分发

实际接口经常要区分 GET、POST、PUT、DELETE。除了用通用的 onRequest,Pages 还支持方法专属的导出名。

导出名 对应方法
onRequestGet GET
onRequestPost POST
onRequestPut PUT
onRequestDelete DELETE
onRequestPatch PATCH

一个文件里同时导出多个方法名,就能让同一个路径响应不同方法。

// functions/api/todos.js
export async function onRequestGet(context) {
  return new Response("返回待办列表");
}

export async function onRequestPost(context) {
  const body = await context.request.json();
  return new Response("创建待办 " + JSON.stringify(body), { status: 201 });
}

访问 /api/todos 时,GET 走 onRequestGet,POST 走 onRequestPost,互不干扰。没导出的方法会返回 405。

动态路由

接口路径里经常带参数,比如 /users/123 里的 123 是用户 ID。Pages 用方括号文件名表示动态参数。

单层参数用一个方括号,文件名 functions/users/[id].js 匹配 /users/任意单段,参数从 context.params 取。

// functions/users/[id].js
export function onRequest(context) {
  const id = context.params.id;
  return new Response("查询用户 " + id);
}

匹配规则如下。

请求路径 是否匹配 params.id
/users/123 123
/users/abc abc
/users/123/posts
/profile/123

单层参数只匹配一段路径,跨段匹配不到。

要匹配任意深度,用双层方括号,文件名 functions/users/[[path]].js 是捕获所有剩余段。

// functions/users/[[path]].js
export function onRequest(context) {
  const path = context.params.path;
  return new Response(JSON.stringify(path));
}
请求路径 是否匹配 params.path
/users/123 123
/users/123/posts/456 [“123”, “posts”, “456”]

单层参数返回字符串,多层参数返回数组,取值时要注意类型差别。

路由优先级

静态文件和 Functions 同时存在时,谁优先很关键。规则是更具体的路由胜出。

路由类型 优先级 示例
静态文件 dist/api/users.json
具名函数文件 functions/api/users.js
单层动态参数 functions/api/[id].js
多层捕获参数 最低 functions/api/[[rest]].js

也就是说,如果 dist/api/users.jsonfunctions/api/users.js 同时存在,静态文件赢,函数不会执行。设计接口时要避开和静态文件路径冲突。

本地开发

每次改完都推到 Git 等部署再调试太慢。Pages 提供本地开发命令,用 Wrangler 在本地起一个和线上一样的环境。

npx wrangler pages dev ./dist

这条命令以 dist 为静态文件目录启动本地服务,默认地址 http://localhost:8788。它会自动识别 functions 目录并挂载,改完代码热重载,请求直接打到本地函数。

本地开发还能模拟环境变量和绑定。

npx wrangler pages dev ./dist --var API_KEY:local-test-key

本地跑通再推送,能省下大量看构建日志的时间。

Pages Functions 的核心是文件路由。functions 目录的文件结构就是接口路径,index.js 代表目录本身,[param] 是单层动态参数,[[param]] 是多层捕获。每个端点导出 onRequest 或方法专属函数,用 context 拿请求信息并返回 Response。下一篇讲中间件,给一组接口统一加上鉴权、日志、A/B 测试这类公共逻辑。

上一篇 自定义域名与重定向规则
下一篇 Pages Functions 中间件

上一篇
自定义域名与重定向规则
下一篇
Pages Functions 中间件