Cloudflare 的这套组合适合中小型全栈项目:
- Cloudflare Pages:托管 React、Vue、Vite、静态站点等前端产物;
- Cloudflare Pages Functions:在边缘运行 API 代码,不需要单独购买或维护传统服务器;
- Cloudflare D1:Cloudflare 托管的 serverless SQLite 数据库;
- GitHub 集成:推送代码后自动构建、部署;PR 或非生产分支可以得到预览环境。
Pages Functions 可以通过绑定直接访问 D1;前端静态资源与 API 可以同域部署,因此通常不需要额外处理跨域问题。(developers.cloudflare.com)
一、最终架构

一次请求的大致路径如下:
Cloudflare Pages 的 Git 集成支持推送自动部署、PR/分支预览 URL,以及 GitHub 内的构建状态检查。(developers.cloudflare.com)
二、准备条件
开始前需要准备:
- 一个 Cloudflare 账号;
- 一个 GitHub 仓库;
- Node.js 20 或更高版本;
- npm、pnpm 或 yarn;
- 一个本地 Git 环境;
- 可选:Cloudflare 自己托管的域名。
本文以 React + Vite + TypeScript 为例,但后端部分同样适用于 Vue、Svelte、Astro 或纯静态 HTML 项目。
三、初始化 Vite 项目
npm create vite@latest cf-pages-d1-demo — –template react-ts
cd cf-pages-d1-demo
npm install
npm install -D wrangler @cloudflare/workers-types
建议在 package.json 中准备以下脚本:
{
"scripts": {
"dev": "vite",
"build": "tsc –noEmit && vite build",
"preview": "vite preview",
"dev:pages": "npm run build && wrangler pages dev dist"
}
}
Vite 默认会把生产构建产物输出到 dist,而 Cloudflare Pages 的构建目录需要填写实际输出目录。(developers.cloudflare.com)
四、创建 D1 数据库
先登录 Cloudflare:
npx wrangler login
创建生产数据库:
npx wrangler d1 create cf-pages-d1-demo
终端会输出类似以下内容:
database_name = "cf-pages-d1-demo"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
建议再创建一个预览数据库,避免测试分支或 PR 误写入正式数据:
npx wrangler d1 create cf-pages-d1-demo-preview
需要保存两组信息:
- 生产数据库名称和 ID;
- 预览数据库名称和 ID。
五、配置 Wrangler 和 D1 绑定
在项目根目录新建 wrangler.toml:
name = "cf-pages-d1-demo"
pages_build_output_dir = "./dist"
compatibility_date = "2026-07-31"
[[d1_databases]]
binding = "DB"
database_name = "cf-pages-d1-demo"
database_id = "<你的生产数据库 ID>"
preview_database_id = "<你的预览数据库 ID>"
migrations_dir = "migrations"
这里最重要的是:
binding = "DB"
它决定了后端代码中访问数据库的变量名:
context.env.DB
Cloudflare Pages 的 D1 绑定既可以通过 Dashboard 设置,也可以写入 Wrangler 配置文件。对于长期维护项目,更推荐把 Wrangler 配置提交到 Git,让配置和代码一起版本化;一旦以 Wrangler 配置作为来源,就应避免和 Dashboard 中的同类设置长期混用,以免出现配置不一致。(developers.cloudflare.com)
如果你暂时不想维护 wrangler.toml,也可以在 Cloudflare Dashboard 中进入:
Workers & Pages
→ 选择项目
→ Settings
→ Bindings
→ Add
→ D1 database bindings
绑定变量名填写 DB,然后选择创建好的 D1 数据库。完成后需要重新部署,绑定才会生效。(developers.cloudflare.com)
六、创建数据库迁移文件
不要直接在生产数据库中手工改表。推荐把数据库结构变更写成迁移文件,并提交到 Git。
创建迁移:
npx wrangler d1 migrations create cf-pages-d1-demo initial_schema
在 migrations/ 目录中写入 SQL,例如:
— migrations/0001_initial_schema.sql
CREATE TABLE IF NOT EXISTS todos (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
done INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_todos_created_at
ON todos(created_at DESC);
先在本地数据库应用迁移:
npx wrangler d1 migrations apply cf-pages-d1-demo –local
确认无误后,再应用到生产数据库:
npx wrangler d1 migrations apply cf-pages-d1-demo –remote
如果配置了预览数据库,也应同步执行:
npx wrangler d1 migrations apply cf-pages-d1-demo –preview
D1 的迁移文件会记录已应用状态;migrations apply 会应用尚未执行的迁移。对于生产环境,建议用数据库名称而不是绑定名执行迁移,避免因为绑定名被修改而误操作错误的数据库。(developers.cloudflare.com)
重要提醒: Wrangler 的 D1 命令默认偏向本地数据库行为。真正执行生产迁移或查询时,必须明确带上 –remote。(developers.cloudflare.com)
七、编写 Pages Functions API
Cloudflare Pages 会根据根目录 functions/ 中的文件路径自动生成路由。
目录结构如下:
cf-pages-d1-demo/
├── functions/
│ └── api/
│ └── todos.ts
├── migrations/
│ └── 0001_initial_schema.sql
├── src/
│ └── App.tsx
├── wrangler.toml
└── package.json
其中:
functions/api/todos.ts
会自动对应:
/api/todos
Pages Functions 的 functions/ 必须位于项目根目录,而不是 src/ 或 dist/ 内。(developers.cloudflare.com)
示例:查询和新增待办事项
// functions/api/todos.ts
type Env = {
DB: D1Database;
};
type Todo = {
id: string;
title: string;
done: number;
created_at: string;
};
export const onRequestGet: PagesFunction<Env> = async ({ env }) => {
const result = await env.DB
.prepare(
`SELECT id, title, done, created_at
FROM todos
ORDER BY created_at DESC`,
)
.all<Todo>();
return Response.json(result.results);
};
export const onRequestPost: PagesFunction<Env> = async ({ request, env }) => {
const input = await request.json() as { title?: unknown };
const title = typeof input.title === "string" ? input.title.trim() : "";
if (!title || title.length > 120) {
return Response.json(
{ error: "title 必须是 1 到 120 个字符" },
{ status: 400 },
);
}
const todo = {
id: crypto.randomUUID(),
title,
done: 0,
created_at: new Date().toISOString(),
};
await env.DB
.prepare(
`INSERT INTO todos (id, title, done, created_at)
VALUES (?, ?, ?, ?)`,
)
.bind(todo.id, todo.title, todo.done, todo.created_at)
.run();
return Response.json(todo, { status: 201 });
};
D1 应始终使用 prepare(…).bind(…) 参数绑定,而不是把用户输入直接拼接到 SQL 字符串中。D1 的 Workers Binding API 支持 prepare、bind、run、first、all 和批量执行等操作。(developers.cloudflare.com)
八、前端调用 API
在 src/App.tsx 中写一个最小示例:
import { FormEvent, useEffect, useState } from "react";
type Todo = {
id: string;
title: string;
done: number;
created_at: string;
};
export default function App() {
const [todos, setTodos] = useState<Todo[]>([]);
const [title, setTitle] = useState("");
const [loading, setLoading] = useState(true);
async function loadTodos() {
setLoading(true);
const response = await fetch("/api/todos", {
cache: "no-store",
});
if (!response.ok) {
throw new Error("加载失败");
}
setTodos(await response.json());
setLoading(false);
}
useEffect(() => {
void loadTodos();
}, []);
async function addTodo(event: FormEvent) {
event.preventDefault();
const response = await fetch("/api/todos", {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({ title }),
});
if (!response.ok) {
alert("新增失败");
return;
}
setTitle("");
await loadTodos();
}
return (
<main>
<h1>Cloudflare Pages + D1 Demo</h1>
<form onSubmit={addTodo}>
<input
value={title}
placeholder="输入待办事项"
onChange={(event) => setTitle(event.target.value)}
/>
<button disabled={!title.trim()} type="submit">
添加
</button>
</form>
{loading ? (
<p>加载中…</p>
) : (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)}
</main>
);
}
前端和 Functions 部署在同一个 Pages 项目中时,调用 /api/todos 即可,不需要写本地开发端口或单独维护后端域名。
九、本地运行完整环境
仅执行:
npm run dev
通常只能运行 Vite 前端,不能完整模拟 Pages Functions 和 D1。
要本地测试“前端 + Functions + D1”,先构建,再使用 Wrangler:
npm run build
npx wrangler pages dev dist –d1 DB=<你的数据库 ID>
也可以写入脚本:
{
"scripts": {
"dev:pages": "npm run build && wrangler pages dev dist –d1 DB=<你的数据库 ID>"
}
}
然后启动:
npm run dev:pages
本地浏览器访问 Wrangler 输出的地址,例如:
http://127.0.0.1:8788
Cloudflare 的本地开发默认使用本地持久化数据,而不是生产 D1;因此应先在本地验证迁移和 API,再明确使用 –remote 操作远程数据库。(developers.cloudflare.com)
十、配置环境变量与密钥
1. 非敏感变量
例如运行环境名称、功能开关等:
[vars]
APP_ENV = "production"
在 Function 中读取:
type Env = {
DB: D1Database;
APP_ENV: string;
};
export const onRequest: PagesFunction<Env> = async ({ env }) => {
return Response.json({
environment: env.APP_ENV,
});
};
2. 敏感变量
例如管理员密码、JWT 密钥、第三方 API Key,必须使用 Secret。
本地创建 .dev.vars:
ADMIN_PASSWORD="replace-me"
JWT_SECRET="replace-me-too"
并加入 .gitignore:
.dev.vars*
.env*
生产环境可以在 Dashboard 中进入:
Workers & Pages
→ 项目
→ Settings
→ Variables and Secrets
→ Add
→ Encrypt
也可以通过 Wrangler 写入:
npx wrangler pages secret put ADMIN_PASSWORD \\
–project-name cf-pages-d1-demo
npx wrangler pages secret put JWT_SECRET \\
–project-name cf-pages-d1-demo
Pages 的 Secret 只能在运行时通过 context.env 读取;本地开发可以使用 .dev.vars 或 .env,但这类文件不应提交到 Git。(developers.cloudflare.com)
十一、部署到 Cloudflare Pages:推荐 GitHub 自动部署
这是最适合长期项目的方案。
1. 初始化 Git 并推送 GitHub
git init
git add .
git commit -m "feat: initial Cloudflare Pages app"
git branch -M main
git remote add origin git@github.com:<你的用户名>/cf-pages-d1-demo.git
git push -u origin main
2. 在 Cloudflare Dashboard 创建 Pages 项目
进入:
Workers & Pages
→ Create application
→ Pages
→ Connect to Git
然后:
授权 Cloudflare 访问 GitHub;
选择仓库;
设置生产分支为 main;
设置构建命令:
npm run build
设置输出目录:
dist
创建并等待第一次部署完成。
如果项目已在 GitHub 中集成 Pages,后续推送到生产分支会触发自动构建和发布;其他分支或 PR 可用于生成预览部署。(developers.cloudflare.com)
3. 之后的日常发布流程
git add .
git commit -m "feat: add todo API"
git push origin main
Cloudflare Pages 会自动完成:
拉取仓库
→ 安装依赖
→ 执行构建
→ 部署静态资源
→ 部署 Functions
→ 更新 pages.dev 和自定义域名
十二、命令行直接部署方案
如果不想用 GitHub 自动部署,也可以直接用 Wrangler。
先创建 Pages 项目:
npx wrangler pages project create cf-pages-d1-demo \\
–production-branch main
构建并部署:
npm run build
npx wrangler pages deploy dist \\
–project-name cf-pages-d1-demo \\
–branch main
wrangler pages deploy 可以直接发布构建目录;–project-name 指向目标 Pages 项目,–branch 用于指定部署分支。(developers.cloudflare.com)
需要注意的是:
- 带 Functions 的项目不能依赖 Dashboard 的 Direct Upload;
- 若项目包含 functions/,应选择 Git 集成或 Wrangler 进行部署。(developers.cloudflare.com)
十三、部署前后的数据库流程
推荐把发布拆成两个步骤:

生产发布时的推荐命令顺序:
# 1. 查看生产库尚未执行的迁移
npx wrangler d1 migrations list cf-pages-d1-demo –remote
# 2. 应用生产迁移
npx wrangler d1 migrations apply cf-pages-d1-demo –remote
# 3. 查询确认表结构
npx wrangler d1 execute cf-pages-d1-demo \\
–remote \\
–command "SELECT name FROM sqlite_schema WHERE type='table' ORDER BY name;"
# 4. 发布代码
git push origin main
D1 支持通过 migrations list 查看未应用迁移,并通过 –remote 对生产数据库执行迁移或查询。(developers.cloudflare.com)
十四、自定义域名
部署成功后,默认会得到:
https://<项目名>.pages.dev
添加自己的域名时,进入:
Workers & Pages
→ 项目
→ Custom domains
→ Set up a custom domain
如果域名本身由 Cloudflare 托管,通常可以在 Dashboard 中完成关联;如果 DNS 在其他平台,则按 Cloudflare 给出的 CNAME 或 DNS 提示配置。
建议至少准备:
example.com
www.example.com
并把其中一个配置为主域名,另一个做跳转。
十五、常见问题与排查
1. 页面正常,但 /api/* 返回 404
检查:
- functions/ 是否位于项目根目录;
- 文件路径是否正确,例如 functions/api/todos.ts;
- 是否重新部署;
- 是否误把 Functions 放进了 src/ 或 dist/。
Pages Functions 的路由来自根目录 functions/ 的文件结构。(developers.cloudflare.com)
2. Function 报错:Cannot read properties of undefined (reading 'prepare')
通常表示:
context.env.DB
没有拿到 D1 binding。
检查:
- wrangler.toml 的 binding = "DB";
- 代码是否同样使用 env.DB;
- Dashboard 中是否正确绑定;
- 修改 binding 后是否重新部署。
绑定名必须在配置、部署环境和代码中保持一致。(developers.cloudflare.com)
3. 本地可用,生产环境提示“没有表”或 no such table
通常是只执行了:
npx wrangler d1 migrations apply … –local
但没有执行:
npx wrangler d1 migrations apply … –remote
本地 D1 和生产 D1 是不同的数据环境;生产数据库需要单独应用迁移。(developers.cloudflare.com)
4. Dashboard 配置和 wrangler.toml 对不上
例如:
- Dashboard 中绑定了数据库 A;
- 仓库里的 wrangler.toml 指向数据库 B;
- 下一次 Git 部署后,行为突然改变。
解决方法:
npx wrangler pages download config <项目名>
下载当前项目配置后再进行统一维护。(developers.cloudflare.com)
5. 把密码或 API Key 写进前端代码
不要这样做:
const ADMIN_PASSWORD = "123456";
浏览器最终会下载前端 JavaScript,任何写在前端包里的“密钥”都不是真正的秘密。
正确做法:
- Secret 存在 Cloudflare;
- Function 使用 context.env.SECRET_NAME;
- 前端只调用 API,不接触真正密钥。
6. D1 查询越来越慢
优先检查:
- 是否为 WHERE、JOIN、ORDER BY 常用字段建立索引;
- 是否避免查询全部数据;
- 是否分页;
- 是否把多条相互依赖较低的 SQL 放到批量操作中;
- 是否给高频查询查看执行计划。
D1 的索引适用于经常作为筛选条件、唯一约束或联合查询条件的列。(developers.cloudflare.com)
十六、生产环境建议
数据库
- 所有表结构变更都必须通过 migration;
- 生产迁移前先在本地和 Preview 验证;
- 不要直接在线手改结构;
- 高频查询字段建立索引;
- 保存 created_at、updated_at、操作日志等审计字段。
API
- 所有写操作都验证输入;
- SQL 一律使用参数绑定;
- 管理接口必须鉴权;
- 对写操作增加幂等键或请求 ID;
- 对错误返回统一 JSON 格式。
前端
- API 请求使用同域相对路径,如 /api/todos;
- 写操作完成后刷新或更新本地状态;
- 对网络错误、加载状态、空状态做处理;
- 不把管理员密码、第三方密钥写到浏览器代码里。
部署
- main 只放可上线代码;
- 开发分支通过 Pages Preview 验证;
- 数据库迁移与代码发布按顺序执行;
- 每次生产发布后至少验证:
- 首页;
- 一个读接口;
- 一个写接口;
- 数据库是否写入;
- Pages 构建日志是否成功。
十七、上线检查清单
[ ] 前端 npm run build 成功
[ ] functions/ 位于项目根目录
[ ] wrangler.toml 的 pages_build_output_dir 指向 dist
[ ] D1 binding 名称与代码中的 env.DB 一致
[ ] 本地 migration 已通过
[ ] Preview D1 migration 已通过
[ ] Production D1 migration 已通过
[ ] 所有 Secret 已在 Cloudflare 配置
[ ] .dev.vars / .env 未提交 Git
[ ] GitHub main 已连接到 Pages 生产分支
[ ] Pages 构建命令为 npm run build
[ ] Pages 输出目录为 dist
[ ] 部署后 API 与数据库读写已验证
[ ] 自定义域名和 HTTPS 已验证
结语
Cloudflare Pages + Functions + D1 的核心价值是把传统的:
前端托管 + Node 服务端 + 数据库 + CI/CD
简化为:
GitHub 仓库
→ Cloudflare Pages
→ Pages Functions
→ D1
对于作品集、管理后台、预约系统、点餐工具、内容管理、小型 SaaS 原型、内部工具等项目,这套架构通常已经足够。静态资源由 Pages 承载,Function 请求计入 Workers 使用额度;因此正式上线前仍应结合项目访问量与当前套餐限制评估容量。(developers.cloudflare.com)
网硕互联帮助中心





评论前必须登录!
注册