字节笔记本
2026年2月20日
Next.js + Supabase + Cloudflare Worker + Hyperdrive 最佳实践(2026终极版)
本文分享从 Vercel 迁移到 Cloudflare Worker + Supabase + Hyperdrive 的完整实战经验,包括环境变量处理、连接池配置和数据库连接重构等关键问题。
为什么要逃离 Vercel?
Vercel 很好,Developer Experience 极佳。但是当用户量开始增长,你会发现 Vercel 的账单成长得更快……看着每个月几十上百的账单,你会不由得焦虑起来。
Cloudflare Workers 提供了极高的性价比和几乎无限的并发能力,区区 $5/月,媲美 Vercel $100/月以上的额度;还有 Hyperdrive —— 那个声称能让你的数据库连接像开了加速器一样的神奇功能。
是否应该选择 Supabase?
如果你只是需要一个数据库,其实不太推荐 Supabase。相对来说,Supabase 是个非常强力的一站式解决方案,除了 Serverless 数据库,它还支持 Auth 和 Edge Function。
但如果你并不了解 Serverless 数据库应该怎么开发应用,或者只是需要一个关系型数据库,那么 Supabase 提供的好处你恐怕享受不到,而它的免费额度要远低于 D1 或者 TiDB Cloud。
第一关:消失的环境变量
并不是简单的 process.env
在 Vercel(或者标准的 Node.js 环境)里,我们习惯了这样拿数据库连接串:
import postgres from 'postgres';
const connectionString = process.env.DATABASE_URL;
const client = postgres(connectionString)
export const db = drizzle(client, config);代码写完,本地 next dev 一跑,完美。部署到 Cloudflare Worker,报错……
原因:Cloudflare Workers 的环境变量机制和 Node.js 不同。
在 Workers 运行时里,Hyperdrive 是 binding,并不是典型的环境变量,自然也不会挂在 process.env 上,而是通过 env 对象传递给请求的上下文。特别是当你使用了 @opennextjs/cloudflare 适配器时,你需要显式地去获取上下文。
正确的获取方式
你需要把所有直接读取 process.env 的代码,改成通过 getCloudflareContext() 获取:
import { getCloudflareContext } from '@opennextjs/cloudflare';
// 必须在函数内部调用,因为只有其实在处理请求时才有 Context
function getConnectionInfo() {
const runtimeEnv = getCloudflareContext().env;
const hyperdrive = runtimeEnv.HYPERDRIVE as { connectionString?: string } | undefined;
if (hyperdrive?.connectionString) {
return { connectionString: hyperdrive.connectionString, source: 'hyperdrive' };
}
const localHyperdrive = runtimeEnv.CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE;
if (localHyperdrive) {
return { connectionString: localHyperdrive, source: 'hyperdrive-local' };
}
throw new Error('HYPERDRIVE is not configured');
}这带来了一个巨大的架构变动:你不能在文件顶层(Top Level)初始化数据库连接了。
之前我们可以:
// global.ts
export const db = drizzle(client); // 全局单例,直接导出现在如果你在顶层调用 getCloudflareContext(),它会抛错或者返回 undefined,因为模块加载时还没有请求进来。
第二关:Hyperdrive 真正的奥义:连接池
数据库面对 Serverless 的噩梦:连接数耗尽
简单解释一下:传统的 Node.js 服务是长驻的,启动服务后就会建立数据库连接池(比如 max: 10),这个应用里所有请求共用这 10 个连接。
但是 Cloudflare Workers 是 Serverless 的,这意味着:
- 流量来了,瞬间启动几千个 Worker 实例
- 每个 Worker 实例都要去连数据库
- 如果不加控制,瞬间几千个连接打到你的 Postgres 数据库上
- 数据库直接挂掉("Too many connections")
Hyperdrive 的基本设计思路:Worker 不直接连真实的数据库,而是连 Cloudflare 并在全球边缘节点部署的 Hyperdrive 代理。这些代理维护着到真实数据库的长连接池。你的 Worker 连 Hyperdrive 极快(内网级别),而 Hyperdrive 帮你复用在那有限的几十个真实数据库连接上。
Supabase 的坑中坑
Supabase 为了不要动不动就被阻塞,自己也提供连接池(Transaction Pooler),而且默认只让用户使用连接池(连接 URL 中包含 pooler 字眼,使用 6543 端口)。
直觉:既然要连接池,那我用 Hyperdrive 连 Supabase 的 Pooler 岂不是双倍快乐?
现实:报错。
Hyperdrive 必须连接数据库的 Direct Connection(通常是 port 5432)。它自己就是一个 Pooler,它不支持去连另一个 Pooler。
三个连锁反应的坑:
- 必须用直连 Direct Connection:配置 Hyperdrive 时,Host 必须是 Supabase 的直连地址(特征:端口 5432)
- 本地开发连不上:Supabase 的免费版(Free Tier)直连地址只支持 IPv6。如果你的本地网络环境或者开发工具只支持 IPv4,你在本地是死活连不上这个 Direct 地址的
- 必须关闭 SSL:Hyperdrive 和你的 Worker 都在 Cloudflare 内网,必须显式关闭 SSL
export const getDb = cache(() => {
const { connectionString, source } = getConnectionInfo();
return createDatabase({
connectionString,
// Hyperdrive 相当于内网,没有 SSL,必须关掉!
enableSSL: source === 'hyperdrive' ? false : 'require',
});
});第三关:每个连接用一次,用完即弃
如果按照惯例缓存数据库连接实例反复使用,就会遇到这个错误:
The Workers runtime canceled this request because it detected that your Worker's code had hung and would never generate a response.
表现为没有响应,到达设置的时限之后报超时错误。
解决方案:不能全局缓存数据库连接实例,必须每个请求都需要创建新的连接实例才行。
重构前后对比
Before: Global Singleton
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
// 无论 import 多少次,client 和 db 只有一份
const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client);After: Function based
// 使用 React Cache 确保同一个请求内只创建一次以复用
export const getDb = cache(() => {
const { connectionString, source } = getConnectionInfo();
return createDatabase({
connectionString,
enableSSL: source === 'hyperdrive' ? false : 'require',
});
});业务代码改动
所有的 Server Action、API Route、Data Access Layer 全部要改:
Before:
import { db } from '@/lib/db';
export async function getUser(id: string) {
return await db.query.users.findFirst({ ... });
}After:
import { getDb } from '@/lib/db';
export async function getUser(id: string) {
const db = await getDb(); // <--- 每一处都要加这个
return await db.query.users.findFirst({ ... });
}总结
使用 Cloudflare Worker + Supabase 总攻略:
- 修改环境变量,妥善利用
getCloudflareContext().env - 使用 Supabase 直连 URL(端口5432) 配置 Hyperdrive,创建数据库连接
- 对于本地开发,直接使用 Supabase 的 pooler URL(端口 6543)创建数据库连接
- 把全局 db 单例重构成
await getDb(),然后按需获取
Cloudflare Workers 的冷启动几乎可以忽略不计,配合 Hyperdrive,数据库查询速度在边缘节点也相当可观。最重要的是,再也不用担心 Next.js 部署在 Vercel 上的高昂账单了。
来源: 山维空间 (Meathill) 发布时间: 2026-02-19 原文链接: https://meathill.com/cloudflare-worker/nextjs-supabase-cloudflare-worker-hyperdrive