基于 Cloudflare Workers 构建 Nextjs 全栈,与在 Vercel 上开发部署相比,大家都说有很多难点。对我来说,我还没试过 Vercel,既然都有难点,那就直接选 Cloudflare Workers 好了。Cloudflare 的特点是性价比高、赛博菩萨的美誉度。如果我们搞通了 Workers,可以说我们上站噼里啪啦的,都不需要什么成本,在没有赚钱之前,顶多就是每月 5美元。
在正式开始之前,我尝试了很多方案,从简单的 html 纯静态,到 pages 动态方法,再到 workers 全栈的能力,大致了解了不同的产品如何针对不同的项目。选择技术栈时,在 linux.do 社区看到某个帖子,参考了他的技术选型[1],对小白程度的新手来说,这就像明灯一样指引着我,特别有帮助。
如果账户是免费模式,目前的限制主要有以下 3 点:
| 特征 | 免费账户 | 付费账户 |
|---|---|---|
| CPU 时间 | 每次10ms | 每次 30ms |
| 压缩包大小 | 3MB | 10MB |
| 项目数量 | 100个 | 500个 |
经过测试发现,压缩包大小限制 3MB,项目一般不太容易超过,如果采用多个Workers 组合形式也可以破解这一限制;关键是在CPU 每次10ms 比较容易触及,如果缓存策略到位的话还好(这是重点),否则会经常显示 503 错误。正式运营的项目,建议还是将账户升级为付费模式,缓存策略后续会具体说。
本地开发环境包括采用 Linux 环境,通过VS Code 编辑器连接项目进行开发,具体配置如下:
下面以项目名称 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 [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 包 [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 提供的图像剪切服务,对标 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 的配置文件,应包含如下:
// 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 说明:
关于变量的说明:
npx wrangler secret put {NAME}中配置;手动检查 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;
}
}
手动检查其他配置
NEXTJS_ENV=development 外,不应该包括任何其他。NODE_ENV="development",及所有本地环境变量。基础组件
// 增加黑暗模式
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 编辑器插件安装:
安装时间格式
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]: 环境变量官方文档