-
Notifications
You must be signed in to change notification settings - Fork 246
Description
Description
I am deploying a Next.js application to Cloudflare Workers (using vinext). I am using Drizzle ORM with the postgres driver connecting to a Supabase database.
Because Cloudflare Workers do not allow asynchronous I/O (like establishing a database connection) in the global scope, the standard Drizzle initialization fails during the build step. To bypass this, I implemented a lazy initialization pattern using a JavaScript Proxy.
While this successfully builds on both Vercel and Cloudflare Workers, runtime behavior on Cloudflare is inconsistent. Specifically, requests fail exactly every other time:
- 1st Request: Success
- 2nd Request: Fail
- 3rd Request: Success
- 4th Request: Fail
(This happens regardless of whether I use Hyperdrive or a DB pooler URL).
Steps to Reproduce
Attempt 1: Standard Drizzle Setup (Fails during build)
If I use the standard initialization, the worker fails to build/deploy due to global scope I/O restrictions.
import * as schema from "./schemas/schema-main";
import * as schema2 from "./schemas/schema-secondary";
import { drizzle } from 'drizzle-orm/postgres-js'
import { env } from "cloudflare:workers";
import postgres from 'postgres'
const poolDb = postgres(
env.HYPERDRIVE.connectionString ?? "", {
prepare: false,
});
export const db = drizzle({
client: poolDb,
schema: schema,
})Build Error (Attempt 1)
16:48:27.291 ✘ [ERROR] A request to the Cloudflare API (/accounts/<ACCOUNT_ID>/workers/scripts/<PROJECT_NAME>/versions) failed.
16:48:27.291 Uncaught Error: Disallowed operation called within global scope. Asynchronous I/O (ex: fetch() or connect()), setting a timeout, and generating random values are not allowed within global scope. To fix this error, perform this operation within a handler. https://developers.cloudflare.com/workers/runtime-apis/handlers/
16:48:27.291 at null.<anonymous> (index.js:36463:41)
16:48:27.291 [code: 10021]
Attempt 2: Lazy Evaluation using Proxy (Builds successfully, but runtime alternates failing)
To fix the build error, I used a Proxy to lazily instantiate the database connection only when a query is actually executed.
import * as schema from "./schemas/schema-main";
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
function isCloudflareWorkersRuntime(): boolean {
return (
typeof navigator !== "undefined" &&
navigator.userAgent === "Cloudflare-Workers"
);
}
// PRIMARY DB LAZY INIT
let _db: ReturnType<typeof drizzle> | null = null;
export const db = new Proxy({} as any, {
get: (target, prop) => {
if (!_db) {
function connectionString() {
if (isCloudflareWorkersRuntime()) {
const { env } = require("cloudflare:workers");
return env.HYPERDRIVE.connectionString ?? "";
}
return process.env.MY_PRIMARY_DB_URL ?? "";
};
const poolDb = postgres(
connectionString(), {
prepare: false,
});
_db = drizzle({
client: poolDb,
schema: schema,
});
}
return (_db as any)[prop];
}
}) as ReturnType<typeof drizzle>;Expected Behavior
The lazy evaluated database connection should remain persistent and successfully resolve requests consistently on Cloudflare Workers, just as it does on Vercel.
Environment
- Framework: Vinext (0.0.30) and NextJS (16.1.6)
- Database: Supabase (Postgres)
- ORM: Drizzle ORM (
drizzle-orm/postgres-js) - Driver:
postgres - Cloudflare Bindings: Hyperdrive
(Also tested without Hyperdrive using standard Pooler URLs via process.env, but the alternating failure behavior remains exactly the same).