Pages Functions 部署指南

本指南详细介绍如何编写 Cloudflare Pages 项目,使其完美适配本应用的部署能力。

标准模式 高级模式 TypeScript JSX / TSX 模块 import

目录

1 概览

本应用支持将 Cloudflare Pages 项目打包为 .zip 文件后直接部署。应用会自动识别项目类型,处理 TypeScript / JSX 转换、模块间 import 路径重写、路由配置生成等工作,无需 wrangleresbuild

支持能力一览

功能 支持状态 说明
_worker.js 单文件支持高级模式,原样上传
functions/ 目录支持标准模式,自动路由
纯静态网站支持HTML / CSS / JS / 图片
文件路由 / 动态路由 / Catch-all支持含嵌套目录
中间件 _middleware.js支持含嵌套中间件 + context.next()
方法级别导出支持onRequestGet / Post / Put / Delete 等
TypeScript .ts支持Sucrase 类型剥离
JSX .jsx支持@jsx pragma
TSX .tsx支持TS + JSX 同时转换
模块间 import支持相对路径自动重写
静态资源回退支持env.ASSETS.fetch
NPM 依赖不支持无 bundler
_worker.js/ 目录模式不支持仅支持单文件
TS path aliases不支持不读取 tsconfig.json

2 项目结构

将项目打包为 .zip 文件上传。Zip 文件的根目录就是项目根目录。

标准模式项目(推荐)

my-project/ ├── functions/ # Functions 目录 │ ├── hello.js # → /hello │ ├── api/ # 嵌套目录 │ │ ├── index.js # → /api │ │ ├── time.js # → /api/time │ │ ├── _middleware.js # /api/* 的中间件 │ │ └── user/ │ │ └── [id].js # → /api/user/:id │ ├── _middleware.js # 全局中间件 │ └── utils/ # 工具模块(非路由) │ ├── types.ts # TypeScript 类型定义 │ └── helpers.ts # 辅助函数 ├── public/ # 静态资源(可选) │ ├── index.html # 首页 │ ├── style.css │ └── favicon.ico └── _routes.json # 路由规则(可选,自动生成)

注意:functions/ 目录是标准模式的核心。应用检测到此目录后会自动扫描所有 .js / .ts / .tsx / .jsx 文件,生成路由配置和入口脚本。

高级模式项目

my-project/ ├── _worker.js # Worker 入口(ES Module 格式) ├── public/ # 静态资源 │ └── index.html └── _routes.json # 路由规则(可选)

注意:_worker.js 必须放在项目根目录(不是 public/ 内)。应用会将其原样上传为 _worker.bundle,不做任何转换。

3 三种部署模式

应用按以下优先级自动检测部署模式:

_worker.js 高级模式

项目根目录存在 _worker.js 文件时触发。应用原样上传,适合完整的自定义 Worker。

functions/ 标准模式

项目根目录存在 functions/ 目录时触发。自动生成路由、转换 TS/JSX、重写 import。

高级模式:_worker.js

_worker.js 使用 ES Module 格式,必须 export default 一个对象,包含 fetch 方法:

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

    if (url.pathname === '/') {
      return new Response('Hello World!');
    }

    if (url.pathname === '/api') {
      return Response.json({ message: 'API' });
    }

    // 静态资源回退
    return env.ASSETS.fetch(request);
  }
};

标准模式:functions/ 目录

标准模式下,应用自动处理一切 — 路由生成、类型转换、import 重写。你只需按约定写文件即可。

// functions/hello.js
export function onRequestGet(context) {
  return new Response('Hello from Functions!');
}

4 文件路由

标准模式下,functions/ 目录中的文件路径直接映射为 URL 路由。

路由规则

文件路径 URL 路由 说明
functions/hello.js/hello基础路由
functions/api/time.js/api/time嵌套目录
functions/api/index.js/apiindex 文件
functions/api/user/[id].js/api/user/:id动态参数
functions/api/[[slug]].js/api/:slug*Catch-all(返回数组)
functions/_middleware.js所有路由根中间件
functions/api/_middleware.js/api/*嵌套中间件

动态路由参数

使用 [param] 语法定义动态段,通过 context.params 获取:

// functions/api/user/[id].js
export function onRequestGet(context) {
  const { id } = context.params;
  return Response.json({
    id: Number(id),
    message: `User ${id}`
  });
}

Catch-all 路由

使用 [[slug]] 语法匹配多级路径,参数以数组形式返回:

// functions/api/[[slug]].js
export function onRequestGet(context) {
  const { slug } = context.params;
  // slug 是数组,如 /api/a/b/c → ['a', 'b', 'c']
  return Response.json({ segments: slug });
}

路由优先级

当多个路由可能匹配同一 URL 时,按以下优先级排序:

  1. 静态段/api/user/123 优先匹配 api/user/[id].js
  2. 动态段:id 参数
  3. Catch-all:slug* 最低优先级

5 HTTP 方法

通过导出不同名称的函数来处理不同的 HTTP 方法。一个文件可以同时导出多个方法处理函数。

导出函数名 HTTP 方法
onRequestGetGET
onRequestPostPOST
onRequestPutPUT
onRequestPatchPATCH
onRequestDeleteDELETE
onRequestOptionsOPTIONS
onRequestHeadHEAD
onRequest所有方法

同文件多方法

// functions/api/items.js
export function onRequestGet(context) {
  return Response.json({ items: [] });
}

export function onRequestPost(context) {
  return new Response('Created', { status: 201 });
}

export function onRequestDelete(context) {
  return new Response(null, { status: 204 });
}

提示:同一个文件中导出的多个方法共用同一个模块,应用会自动去重,模块只上传一次。

6 中间件

中间件文件命名为 _middleware.js,可以拦截请求、修改响应、通过 context.data 传递数据给下游路由。

基本用法

// functions/_middleware.js — 全局中间件
export function onRequest(context) {
  // 在请求处理前执行
  const requestId = crypto.randomUUID();

  // 将数据传递给下游
  context.data.requestId = requestId;
  context.data.timestamp = Date.now();

  // 调用下一个处理函数
  const response = await context.next();

  // 在响应返回前修改
  response.headers.set('X-Request-ID', requestId);

  return response;
}

嵌套中间件

中间件作用于其所在目录及子目录的所有路由:

functions/ ├── _middleware.js # 作用于所有路由 ├── hello.js # ← 受根中间件影响 └── api/ ├── _middleware.js # 作用于 /api/* ├── users.js # ← 受根 + api 中间件影响 └── admin/ └── dashboard.js # ← 受根 + api 中间件影响

中间件按从外到内的顺序执行,context.next() 将控制权交给下一层。

读取中间件数据

// functions/api/users.js
export function onRequestGet(context) {
  // 读取上游中间件传递的数据
  const { requestId, timestamp } = context.data;

  return Response.json({
    users: [],
    requestId: requestId,
    processedAt: Date.now() - timestamp
  });
}

7 TypeScript

应用内置 Sucrase 转换器,支持 .ts 文件的类型剥离。无需 tsconfig.json,无需编译步骤。

支持的 TS 特性

示例

// functions/utils/types.ts
export interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

export enum Status {
  Active = 'active',
  Inactive = 'inactive',
  Pending = 'pending',
}

export function isAdmin(user: User): user is User {
  return user.role === 'admin';
}
// functions/api/ts.ts — 使用 TS 类型
import { type User, Status } from '../utils/types';

export function onRequestGet(): Response {
  const users: User[] = [
    { id: 1, name: 'Alice', email: 'alice@test.com', role: 'admin' },
  ];

  const status: Status = Status.Active;

  return Response.json({ users, status });
}

8 JSX / TSX

应用支持 .jsx.tsx 文件的 JSX 语法转换。由于 Workers 环境没有 React,必须使用 @jsx pragma 指定自定义的 jsx runtime

关键:每个包含 JSX 语法的文件,必须在文件顶部添加 /** @jsx h */ 注释,否则 Sucrase 会默认使用 React.createElement,导致运行时报错。

JSX 示例(.jsx)

/** @jsx h */
// 上面这行必须放在文件顶部,告诉 Sucrase 用 h() 而不是 React.createElement

// 自定义 jsx runtime
function h(tag, props, ...children) {
  return {
    tag,
    props: props || {},
    children: children.flat().filter(c => c != null && c !== false)
  };
}

function jsxToHtml(element) {
  if (typeof element === 'string') return element;
  if (Array.isArray(element)) return element.map(jsxToHtml).join('');
  if (typeof element === 'object' && element.tag) {
    const attrs = Object.entries(element.props)
      .map(([k, v]) => ` ${k}="${v}"`).join('');
    return `<${element.tag}${attrs}>${element.children.map(jsxToHtml).join('')}</${element.tag}>`;
  }
  return '';
}

export function onRequestGet() {
  const page = (
    <html>
      <body><h1>Hello JSX!</h1></body>
    </html>
  );
  return new Response('<!DOCTYPE html>' + jsxToHtml(page), {
    headers: { 'Content-Type': 'text/html' }
  });
}

TSX 示例(.tsx)

/** @jsx h */
import { type User } from './utils/types';

// 自定义 jsx runtime(带类型)
function h(tag: string, props: Record<string, unknown>, ...children: unknown[]): Record<string, unknown> {
  return { tag, props, children };
}

function Card(props: { user: User }) {
  return (
    <div>
      <h2>{props.user.name}</h2>
      <p>{props.user.email}</p>
    </div>
  );
}

export function onRequestGet() {
  const user: User = { id: 1, name: 'Alice', email: 'a@t.com', role: 'admin' };
  const html = jsxToHtml(Card({ user }));
  return new Response(html, { headers: { 'Content-Type': 'text/html' } });
}

9 模块 import

应用支持模块间相对路径 import。部署时自动将所有相对路径重写为 ./func_N.js 格式,匹配 Cloudflare Workers 的模块规范。

支持的 import 形式

1. 命名导入

import { User, Status } from '../utils/types';

2. Type-only 导入

import { type User } from '../utils/types';

3. 默认导入

import helpers from '../utils/helpers';

4. 命名空间导入

import * as utils from '../utils/helpers';

5. 副作用导入

import './polyfill';

6. 动态导入

const module = await import('../utils/helpers');

7. Re-export

export { formatUser, paginate } from './helpers';

路径解析规则

应用自动解析以下路径变体,无需手动指定扩展名:

import 路径 尝试匹配
./typestypestypes.tstypes.jstypes.tsxtypes.jsxtypes/index.js → ...
../utils/helpers../utils/helpers../utils/helpers.ts → ...
./utils./utils./utils/index.ts./utils/index.js → ...

提示:import 路径不要写扩展名(./types 而非 ./types.ts),应用会自动尝试所有可能的扩展名。

实际项目示例

functions/ ├── utils/ │ ├── types.ts # 导出 interface, enum │ ├── helpers.ts # import { User } from './types' │ └── shared.js # 纯 JS 工具 └── api/ ├── import.ts # import from '../utils/helpers', '../utils/shared', '../utils/types' ├── mixed.ts # import + 中间件 + TS └── ts/user/[id].ts # import from '../../../utils/types'(深层路径)
// functions/api/import.ts — 多模块 import
import { formatUser, paginate } from '../utils/helpers';
import { capitalize, slugify } from '../utils/shared';
import { type User, Status } from '../utils/types';

export function onRequestGet() {
  const users: User[] = [/* ... */];
  return Response.json({
    formatted: users.map(formatUser),
    page1: paginate(users, 1, 2),
    slugs: users.map(u => slugify(u.name)),
    status: Status.Active
  });
}

10 静态资源

静态文件放在 public/ 目录中,会与 Functions 一起部署。当请求的路径没有匹配的 Function 时,自动回退到静态资源。

目录结构

my-project/ ├── functions/ │ └── api.js # → /api ├── public/ │ ├── index.html # → /(静态首页) │ ├── about.html # → /about │ ├── css/ │ │ └── style.css # → /css/style.css │ └── images/ │ └── logo.png # → /images/logo.png └── _routes.json

_routes.json

_routes.json 控制哪些路径触发 Functions,哪些直接返回静态资源。如果项目没有提供,应用会自动生成。

{
  "version": 1,
  "include": ["/*"],
  "exclude": ["/css/*", "/images/*", "/favicon.ico"]
}

在 Function 中返回静态资源

// 在 Function 中手动获取静态资源
export function onRequestGet(context) {
  return context.env.ASSETS.fetch(context.request);
}

11 最佳实践

项目组织

JSX 项目

TypeScript 项目

打包

完整项目模板

my-app/ ├── functions/ │ ├── _middleware.js # 全局中间件(请求日志、CORS) │ ├── index.js # → / │ ├── api/ │ │ ├── _middleware.js # API 鉴权中间件 │ │ ├── users.js # → /api/users(GET + POST) │ │ └── user/ │ │ └── [id].js # → /api/user/:id(GET + PUT + DELETE) │ └── utils/ │ ├── types.ts # 共享类型定义 │ ├── helpers.ts # 辅助函数 │ └── db.ts # 数据库操作封装 ├── public/ │ ├── index.html │ └── assets/ │ └── style.css └── _routes.json

12 限制与 FAQ

不支持的功能

功能 原因 替代方案
NPM 依赖 无 bundler,无法解析 import React from 'react' 将依赖代码复制到项目中,或使用 Workers 原生 API
_worker.js/ 目录模式 只支持单文件 _worker.js 使用 functions/ 标准模式
TS path aliases 不读取 tsconfig.json 使用相对路径 ../utils/types
动态 import 变量 import(varName) 无法静态分析 使用静态字符串 import('./module')
JSON import import data from './data.json' 不支持 使用 fetch() 或内联为 JS 对象
CSS / 文件 import import styles from './style.css' 不支持 在 HTML 中用 <link> 引用静态 CSS
Node.js API Workers 是 V8 环境,不是 Node.js 使用 Web API(cryptofetchTextEncoder 等)
TS 装饰器 Sucrase 默认不支持 decorators transform 使用高阶函数或普通类

FAQ

Q: 部署后访问报 1101 "Worker threw exception"

通常是运行时 JavaScript 错误。最常见的原因是 JSX 文件缺少 /** @jsx h */ pragma,导致 Sucrase 输出 React.createElement 但 Workers 环境没有 React。在文件顶部添加 pragma 即可。

Q: 部署后访问报 404 "Not Found"

路由未匹配且没有静态资源可回退。检查文件路径是否正确,或添加 public/index.html 作为兜底页面。

Q: 部署时报 "Invalid module specifier"

import 路径未被正确重写。确保 import 使用的是相对路径(./../),不要使用 bare specifier(如 react)。

Q: 需要配置 tsconfig.json 吗?

不需要。应用使用 Sucrase 做类型剥离,不读取 tsconfig.json。TypeScript 的 pathsbaseUrl 等配置也不会被处理,请使用相对路径。

Q: 可以使用 npm 包吗?

不能。应用没有 bundler,无法解析 NPM 依赖。如果需要第三方库,将其源码直接复制到项目中作为本地模块。

Q: 部署模式和 Cloudflare 官方 wrangler 有什么区别?

功能上基本一致。主要区别:不支持 NPM 依赖和 TS path aliases(因为无 bundler),但支持 TS / JSX / TSX 转换和模块间 import(通过 Sucrase + 路径重写实现)。

Q: 一个文件可以同时导出多个 HTTP 方法吗?

可以。导出 onRequestGet + onRequestPost 等多个函数即可,模块只上传一次不会重复。

Q: 工具文件(无 onRequest 导出)会被当作路由吗?

会被注册为路由(默认分配 onRequest),但实际访问时会走静态资源回退,不影响功能。建议放在 functions/utils/ 目录下以示区分。