30 分钟基于 Cloudflare Workers 构建 Nextjs 全栈开发项目,全程流水线介绍
基于 Cloudflare Workers 构建 Nextjs 全栈,与在 Vercel 上开发部署相比,大家都说有很多难点,Cloudflare 的特点是性价比高、赛博菩萨的美誉度。选择技术栈时,在 linux.do 社区看到某个帖子,参考了他的技术选型[1],对小白程度的新手来说,文章像明灯一样指引着我,特别有帮助。
开发方案
基于 Cloudflare Workers 构建 Nextjs 全栈,与在 Vercel 上开发部署相比,大家都说有很多难点。对我来说,我还没试过 Vercel,既然都有难点,那就直接选 Cloudflare Workers 好了。Cloudflare 的特点是性价比高、赛博菩萨的美誉度。如果我们搞通了 Workers,可以说我们上站噼里啪啦的,都不需要什么成本,在没有赚钱之前,顶多就是每月 5美元。
在正式开始之前,我尝试了很多方案,从简单的 html 纯静态,到 pages 动态方法,再到 workers 全栈的能力,大致了解了不同的产品如何针对不同的项目。选择技术栈时,在 linux.do 社区看到某个帖子,参考了他的技术选型[1],对小白程度的新手来说,这就像明灯一样指引着我,特别有帮助。
运行环境
- cloudflare Workers 环境
- cloudflare D1 数据库
- cloudflare R2 对象存储
- cloudflare Workers KV
- cloudflare Workers AI
- cloudflare Images
技术栈
- React 库
- Next.js 框架
- Cloudflare OpenNext 适配器
- TypeScript 类型语言
- Drizzle ORM 对象关系映射
- Better Auth 身份验证
- Tailwindcss 样式库
- Shadcn/UI 组件库
部署限制
如果账户是免费模式,目前的限制主要有以下 3 点:
| 特征 | 免费账户 | 付费账户 |
|---|---|---|
| CPU 时间 | 每次10ms | 每次 30ms |
| 压缩包大小 | 3MB | 10MB |
| 项目数量 | 100个 | 500个 |
经过测试发现,压缩包大小限制 3MB,项目一般不太容易超过,如果采用多个Workers 组合形式也可以破解这一限制;关键是在CPU 每次10ms 比较容易触及,如果缓存策略到位的话还好(这是重点),否则会经常显示 503 错误。正式运营的项目,建议还是将账户升级为付费模式,缓存策略后续会具体说。
本地搭建
开发环境
本地开发环境包括采用 Linux 环境,通过VS Code 编辑器连接项目进行开发,具体配置如下:
- Windows 11 WSL Ubuntu 系统 [2]
- Windows 11 Visual Studio Code 编辑器与 WSL 插件
- Windows 11 GitHub Desktop 可视化git 工具
初始化安装
下面以项目名称 blueseo-app 安装到 /home/cf/blueseo-app 为例,打开 WSL 终端依次输入命令如下:
// 文件夹授权(可选)
sudo chmod -R 777 /home
// 进入目录 /home/cf
cd /home/cf
// 创建 Nextjs 项目
npm create cloudflare@latest -- blueseo-app --framework=next
// 备用命令
// 修复依赖
npm install
// 安全漏洞
npm audit fix --force
// 删除文件夹
sudo rm -rf /home/cf
资源创建与绑定
数据库名以 blueseo-db 为例,注意:存储桶 KV 都是我自己的命名,请修改为自己的。
// 进入新项目
cd blueseo-app
// 创建D1数据库
npx wrangler@latest d1 create blueseo-db
// 创建存储桶
npx wrangler r2 bucket create blueseo-bucket
// 创建 KV 命名空间
npx wrangler kv namespace create blueseo-kv
项目配置
Cloudflare Workers 有给 AI 大模型的提示词,针对 VS Code 编辑器,存放目录为 .github。
- 增加
copilot-instructions.md,从 Workers 文档下载 [3] - 增加
requirement.md你的项目需求书(备用)
Drizzle ORM 配置
安装 Drizzle ORM [4]
// 安装 Drizzle ORM 包 npm i drizzle-orm dotenv npm i -D drizzle-kit tsx // 安装 drizzle-zod 依赖(支持从 Drizzle ORM 模式生成 Zod 模式) npm install drizzle-zod创建声明表文件(以下为示例)
// src/db/schema.ts // Drizzle ORM 数据库表声明 // 选项表(例句) export const options = sqliteTable("options", { id: integer("id", { mode: 'number' }).primaryKey({ autoIncrement: true }), name: text("name").unique().notNull(), value: text("value"), autoload: integer("autoload", { mode: 'boolean' }).default(false), created_at: integer("created_at", { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`), updated_at integer("updated_at", { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`), });创建配置文件(按照官方文档)
// drizzle.config.ts // Drizzle Kit 配置文件 import 'dotenv/config'; import { defineConfig } from 'drizzle-kit'; export default defineConfig({ schema: './src/db/schema.ts', out: './drizzle', dialect: 'sqlite', driver: 'd1-http', dbCredentials: { accountId: process.env.CLOUDFLARE_ACCOUNT_ID as string, databaseId: process.env.CLOUDFLARE_DATABASE_ID as string, token: process.env.CLOUDFLARE_D1_TOKEN as string, }, });增加环境变量,用于数据部署与迁移:
// 本地环境变量.env CLOUDFLARE_ACCOUNT_ID= <你的 Cloudflare 账户 ID> CLOUDFLARE_DATABASE_ID= <当前绑定 d1_databases> CLOUDFLARE_D1_TOKEN= <你的含 D1 编辑权限的API 令牌>增加 d1_databases 迁移模式
// wrangler.jsonc { "d1_databases": [{ "migrations_dir": "drizzle" }] }增加 cloudflare 全局类型
或运行
npm run cf-typegen自动增加// cloudflare-env.d.ts CloudflareEnv { DB: D1Database; }迁移数据表命令
Drizzle 生成迁移后,会创建与配置文件 drizzle.config.ts/out 目录的迁移文件
// Drizzle 生成迁移脚本 npx drizzle-kit generate // Drizzle 远程应用(可选) npx drizzle-kit migrate // Wrangler 应用迁移脚本(以数据库名 blueseo-db 为例) // 本地部署 npx wrangler d1 migrations apply blueseo-db --local // 远程部署 npx wrangler d1 migrations apply blueseo-db --remote // Wrangler 查询表格(备用) // 本地查询 npx wrangler d1 execute blueseo-db --local --command="SELECT name FROM sqlite_master WHERE type='table'" // 远程查询 npx wrangler d1 execute blueseo-db --remote --command="SELECT name FROM sqlite_master WHERE type='table'"迁移触发器
Drizzle 无法迁移触发器,需要 Wrangler 手动迁移。触发器我们一般存放到 sql/triggers.sql 位置。
// 本地部署 npx wrangler d1 execute blueseo-db --local --file=./sql/triggers.sql // 远程部署 npx wrangler d1 execute blueseo-db --remote --file=./sql/triggers.sql // Wrangler 查询触发器(备用) // 本地查询 npx wrangler d1 execute blueseo-db --local --command="SELECT name, sql FROM sqlite_master WHERE type = 'trigger'" // 远程查询 npx wrangler d1 execute blueseo-db --remote --command="SELECT name, sql FROM sqlite_master WHERE type = 'trigger'"增加脚本命令(可选)
// package.json { "db:generate": "npx drizzle-kit generate", "db:local": "npx wrangler d1 migrations apply blueseo-db --local", "db:remote": "npx wrangler d1 migrations apply blueseo-db --remote", "db:local:trigger": "npx wrangler d1 execute blueseo-db --local --file=./sql/triggers.sql", "db:remote:trigger": "npx wrangler d1 execute blueseo-db --remote --file=./sql/triggers.sql", }创建 Drizzle 客户端 [5]
// src/db/index.ts // 导入 OpenNext 提供的上下文获取工具 import { getCloudflareContext } from "@opennextjs/cloudflare"; // 导入 drizzle 适配器 import { drizzle, AnyD1Database } from "drizzle-orm/d1"; // 导入数据库声明 import * as schema from "@/db/schema"; // 导入缓存模块 import { cache } from "react"; // Drizzle ORM 客户端实例化 export const getDb = cache(async () => { try { // 运行时环境:返回 D1 数据库 const { env } = await getCloudflareContext({ async: true }); if (!env.DB) { throw new Error("D1 database not found"); } return drizzle(env.DB, { schema }); } catch { // 非运行环境(如本地 CLI/生成脚本)返回一个基于 null 的 drizzle 实例 return drizzle(null as unknown as AnyD1Database, { schema }); } });页面用例:
// src/app/page.tsx // 导入 Drizzle 客户端 import { db } from "@/db"; // 导入数据库设计表 import { user } from "@/db/schema"; export default async function Home() { // 请求结果 const results = await db.select().from(user).all(); console.log(results); }
Better Auth 安装与配置
安装 Better Auth 包 [6]
// 安装 Better Auth 包 npm install better-auth // 生成安全密钥 openssl rand -base64 32设置环境变量
// Better Auth BETTER_AUTH_SECRET= <自己生成的安全密钥> BETTER_AUTH_URL= <http://localhost:3000> // Google Auth GOOGLE_CLIENT_ID= <在谷歌获取的 ID> GOOGLE_CLIENT_SECRET= <在谷歌获取的 SECRET> // Github OAuth GITHUB_CLIENT_ID= <在 Github 获取的 ID> GITHUB_CLIENT_SECRET= <在 Github 获取的 SECRET>为 Cloudflare Workers 远程设置机密变量,本地和远程变量可以不一样。或非机密变量在 wrangler.jsonc/vars 中设置,机密变量必须通过 wrangler 设置或仪表盘设置。
// 设置机密变量 npx wrangler secret put GOOGLE_CLIENT_ID npx wrangler secret put GOOGLE_CLIENT_SECRET npx wrangler secret put GITHUB_CLIENT_ID npx wrangler secret put GITHUB_CLIENT_SECRET // 按提示输入变量即可增加 cloudflare 全局类型
或运行
npm run cf-typegen自动增加// cloudflare-env.d.ts CloudflareEnv { BETTER_AUTH_SECRET: string; BETTER_AUTH_URL: string; GOOGLE_CLIENT_ID: string; GOOGLE_CLIENT_SECRET: string; }创建身份验证实例
// src/lib/auth.ts // Better Auth 实例 import { betterAuth } from "better-auth"; import { nextCookies } from "better-auth/next-js"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { getDb } from "@/db"; // 创建异步函数来初始化 auth const createAuth = async () => { const db = await getDb(); return betterAuth({ // 使用 Drizzle ORM 配置数据库 database: drizzleAdapter(db, { provider: "sqlite", }), // 环境变量密钥 secret: process.env.BETTER_AUTH_SECRET as string, // 电子邮件和密码登陆 emailAndPassword: { enabled: process.env.NEXT_PUBLIC_ENABLE_EMAIL_PASSWORD === "true", }, // 社交登陆 socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, }, github: { clientId: process.env.GITHUB_CLIENT_ID as string, clientSecret: process.env.GITHUB_CLIENT_SECRET as string, }, }, // 高级功能 advanced: { useSecureCookies: true, database: { // 开启自增数字 ID(可选) useNumberId: true, }, }, // 插件 plugins: [ nextCookies(), ], // 用户表新增字段(可选) user: { additionalFields: { role: { type: "string", required: false, defaultValue: "user", input: false, }, }, }, // Cookie 缓存(可选) session: { cookieCache: { enabled: true, // 缓存持续时间(秒) maxAge: 5 * 60, }, }, }); } // 导出实例 export const auth = await createAuth();生成 ORM 模式或 SQL 迁移文件
npx @better-auth/cli@latest generate --output=./src/db/schema/auth-schema.ts生成成功后使用 Drizzle 生成迁移并部署(Drizzle 第 6 步)
// Drizzle 生成迁移脚本 npx drizzle-kit generate // 本地部署 npx wrangler d1 migrations apply blueseo-db --local // 远程部署 npx wrangler d1 migrations apply blueseo-db --remote挂载 API 处理程序
// src/app/api/auth/[...all]/route.ts // Better Auth 登录 API import { auth } from "@/lib/auth"; import { toNextJsHandler } from "better-auth/next-js"; export const { POST, GET } = toNextJsHandler(auth);创建客户端实例
// src/lib/auth-client.ts // Better Auth 客户端实例 // 导出客户端 import { createAuthClient } from "better-auth/react"; export const authClient = createAuthClient(); export const { signIn, signOut, signUp, useSession } = authClient;创建服务器端守卫(可选)
// src/lib/auth-guard.ts // Better Auth 服务器端守卫 import { auth } from "@/lib/auth"; import { jsonResponse } from "@/utils/json-response"; // 认证并获取用户会话 export async function requireUser( request: Request, ) { const session = await auth.api.getSession({ headers: request.headers, }); if (!session?.user) { return jsonResponse({ message: "Unauthorized.", }, { status: 401, }); } return session; }
Cloudflare Images 图像优化
由 Cloudflare Images 提供的图像剪切服务,对标 nextjs 的 Image 图片优化。
新增自定义加载器
R2 域名及网站域名下的图片,均使用该服务。外网图片 URL 不应该使用该服务,会导致错误。
// src/image-loader.ts // 图片加载器 import type { ImageLoaderProps } from "next/image"; const normalizeSrc = (src: string) => { return src.startsWith("/") ? src.slice(1) : src; }; export default function cloudflareLoader({ src, width, quality }: ImageLoaderProps) { if (process.env.NODE_ENV === "development") { return src; } // NEXT_PUBLIC_R2_PUBLIC_URL为 R2 绑定的独立图片域名(可选) const url = process.env.NEXT_PUBLIC_R2_PUBLIC_URL; if (src.startsWith('http') && (!url || !src.startsWith(url))) { return src; } const params = [`width=${width}`]; if (quality) { params.push(`quality=${quality}`); } const paramsString = params.join(","); return `/cdn-cgi/image/${paramsString}/${normalizeSrc(src)}`; }next 配置更新
将上述自定义加载器加入到配置环境中。
// next.config.ts import type { NextConfig } from "next"; const nextConfig: NextConfig = { // ... images: { loader: "custom", loaderFile: "./src/image-loader.ts", }, }; export default nextConfig;
增加对象缓存
这部分参考 Cloudflare OpenNext 适配器,OpenNext 官方写的比较仔细显得复杂,实际上我自己进一步进行了消化,如下标准化配置可满足绝大部分需求。
添加 Durable Object 绑定
// wrangler.jsonc // 持久对象队列缓存 "durable_objects": { "bindings": [ { "name": "NEXT_CACHE_DO_QUEUE", "class_name": "DOQueueHandler" } ] }, "migrations": [ { "tag": "v1", "new_sqlite_classes": ["DOQueueHandler"] } ]添加 queue 缓存配置
// open-next.config.ts import doQueue from "@opennextjs/cloudflare/overrides/queue/do-queue"; export default defineCloudflareConfig({ // 其他配置 queue: doQueue, });创建 R2 增量缓存 + tagCache D1 资源
// 使用 R2 存储增量缓存 npx wrangler r2 bucket create next-inc-cache // 使用 D1 标签缓存 (Next mode) npx wrangler d1 create next-tag-cache检查或手动增加 wrangler.jsonc 的配置文件,应包含如下:
- R2 增量缓存必须使用 binding = NEXT_INC_CACHE_R2_BUCKET
- D1 标签缓存必须使用 binding = NEXT_TAG_CACHE_D1
// wrangler.jsonc // 1. D1 标签缓存 "d1_databases": [ // 其他配置 { "binding": "NEXT_TAG_CACHE_D1", "database_name": "next-tag-cache", "database_id": "xxxxxx" }, ], // 2. R2 增量缓存 "r2_buckets": [ // 其他配置 { "bucket_name": "next-inc-cache", "binding": "NEXT_INC_CACHE_R2_BUCKET" }, ],添加 R2 + D1 缓存配置
// open-next.config.ts // 增加 tagCache 标识 import d1NextTagCache from "@opennextjs/cloudflare/overrides/tag-cache/d1-next-tag-cache"; import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache"; // 将 Next 的标签缓存存储到 D1 export default defineCloudflareConfig({ tagCache: d1NextTagCache, incrementalCache: r2IncrementalCache, })完整的 open-next.config 配置
// open-next.config.ts // OpenNext 缓存策略 import { defineCloudflareConfig } from "@opennextjs/cloudflare"; import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache"; import d1NextTagCache from "@opennextjs/cloudflare/overrides/tag-cache/d1-next-tag-cache"; import doQueue from "@opennextjs/cloudflare/overrides/queue/do-queue"; export default defineCloudflareConfig({ incrementalCache: r2IncrementalCache, queue: doQueue, tagCache: d1NextTagCache, enableCacheInterception: true, });
检查全局配置文件
手动检查 wrangler 配置
// wrangler.jsonc // 兼容日期及标志(尽可能为最新) "compatibility_date": "2025-03-01", "compatibility_flags": [ "nodejs_compat", "global_fetch_strictly_public" ], // 智能存放 "placement": { "mode": "smart" }, // 自身服务实例 "services": [ { "binding": "WORKER_SELF_REFERENCE", // 该服务应与当前项目名称相匹配 "service": "blueseo-app" } ], // 同步的生成环境变量 "vars": { // 社交渠道 "GOOGLE_CLIENT_ID": "xxx", "GITHUB_CLIENT_ID": "xxx", // CF turnstile "TURNSTILE_SECRET_KEY": "xxx", // 构建变量 "NEXT_PUBLIC_TURNSTILE_SITE_KEY": "xxx", "NEXT_PUBLIC_R2_PUBLIC_URL": "xxx", },关于 compatibility_flags 说明:
关于变量的说明:
- 开发环境全部在 .env 文件中配置;
- 生产环境非机密在 wrangler.jsonc/vars 中配置;
- 生产环境机密在仪表盘或
npx wrangler secret put {NAME}中配置; - 构建变量中加入所有以 NEXT_PUBLIC_ 开头的变量;
- 更多环境变量规则见环境变量官方文档 [11]。
手动检查 tsconfig 配置
// tsconfig.json // ECMAScript 输出版本 "compilerOptions": { "target": "ES2020", }手动检查 cloudflare-env 类型配置
// cloudflare-env.d.ts declare namespace Cloudflare { interface Env { // 绑定资源类型 DB: D1Database; KV: KVNamespace; UPLOADS: R2Bucket; NEXT_PUBLIC_R2_PUBLIC_URL: string; // 旋转门类型(可选) TURNSTILE_SECRET_KEY: string; NEXT_PUBLIC_TURNSTILE_SITE_KEY: string; // 社交登录类型(可选) GOOGLE_CLIENT_ID: string; GITHUB_CLIENT_ID: string; // 缓存相关类型 NEXT_CACHE_DO_QUEUE: DurableObjectNamespace<import("./.open-next/worker").DOQueueHandler>; NEXT_INC_CACHE_R2_BUCKET: R2Bucket; NEXT_TAG_CACHE_D1: D1Database; // 其他类型 WORKER_SELF_REFERENCE: Service<typeof import("./.open-next/worker").default>; NEXTJS_ENV: string; ASSETS: Fetcher; } }手动检查其他配置
- .dev.vars 除了
NEXTJS_ENV=development外,不应该包括任何其他。 - .env 包含
NODE_ENV="development",及所有本地环境变量。 - .gitignore 中不应该排除 .github、drizzle、sql等目录。
- .dev.vars 除了
UI 组件安装与配置
基础组件
// 增加黑暗模式 npm install next-themes // 增加顶部加载条(可选) npm install nextjs-toploader安装 Shadcn/UI 组件库
// 安装 Shadcn 包 npx shadcn@latest init // 增加需要的组件 npx shadcn@latest add button button-group label checkbox input textarea select switch form dropdown-menu navigation-menu menubar tabs breadcrumb pagination card table avatar separator skeleton sonner carousel resizable toggle tooltip drawer dialog alert empty sidebar spinner item badge alert-dialog collapsible aspect-ratio popover command input-group field toggle-group安装 markdown 组件
// 渲染插件 npm install react-markdown remark-gfm rehype-sanitize rehype-highlight rehype-raw highlight.js安装 @tailwindcss/typography
// @tailwindcss/typography 插件 npm install -D @tailwindcss/typography/* src/app/globals.css 新增 */ @plugin "@tailwindcss/typography";VS code 编辑器插件安装:
- Tailwind CSS IntelliSense
- PostCSS Language Support
安装时间格式
npm install dayjs npm install utc timezone
以上就是基于 Cloudflare Workers 构建 Nextjs 全栈开发项目安装与配置顺序,当我开发新的项目时,当我需要从头开始,必须以此为指南,一般情况下 30 分钟完成。
参考资料
[1]: 独立开发技术栈 2025
[2]: Windows 11 WSL Ubuntu 安装步骤
[3]: Workers Prompt 文档下载
[4]: Drizzle D1 官方文档
[6]: Better-auth 官方文档
[7]: Google Cloud Console 社交登录申请
[8]: Developer applications 社交登录申请
[9]: 启用Node.js API
[10]: 允许在您的应用中获取URL
[11]: 环境变量官方文档