Wiring up Better Auth in a Cloudflare Workers project uses better-sqlite3 locally as a throwaway generator database — you run @better-auth/cli generate against a SQLite file, and it emits the Drizzle schema you check into version control. Production then uses D1, not better-sqlite3.
Under pnpm that generator step often errors out on first run:
Error: Could not locate the bindings file.It looks like a missing file, but the real problem is that the file was never built.
Why this happens#
better-sqlite3 is a native Node addon — when you install it, npm normally runs node-gyp to compile a better_sqlite3.node binary against your local Node version’s headers. That compiled binary is what require('bindings')(...) ends up loading at runtime.
pnpm changed this default. Since pnpm 10, install scripts for native dependencies are blocked unless the package appears in an allow-list. The reasoning is security: a malicious dependency can run arbitrary code at install time through a postinstall script, and pnpm’s position is that you should explicitly opt each package in.
So under pnpm, better-sqlite3 installs its JavaScript source but never compiles the native binary. require('bindings') then can’t find better_sqlite3.node and throws.
The fix#
Add an onlyBuiltDependencies allow-list to your package.json:
{
"pnpm": {
"onlyBuiltDependencies": [
"esbuild",
"better-sqlite3"
]
}
}Then wipe and reinstall:
rm -rf node_modules
pnpm installOn the fresh install pnpm sees the allow-list, runs better-sqlite3’s install script, and node-gyp compiles the binary. Next time you run the Better Auth generator it works.
esbuild is in the list for the same reason — it’s another native postinstall that pnpm blocks by default.
Where this bites you in the Better Auth flow#
The CLI command that triggers this is the schema generator:
pnpm dlx @better-auth/cli@latest generate
--config ./better-auth.config.ts
--output ./src/lib/server/db/better-auth-schema.tsbetter-auth.config.ts instantiates betterAuth({ database: drizzleAdapter(db, { provider: 'sqlite' }) }) where db is drizzle(new Database('./temp-betterauth.db')) — that Database is from better-sqlite3. The moment the CLI imports your config, it loads better-sqlite3, and that’s when the binding error surfaces.
In production (Cloudflare Workers + D1) you don’t hit this at all — the D1 adapter doesn’t touch better-sqlite3. The generator is the only place it appears.
Related#
The full Better Auth + Hono + SvelteKit walkthrough is over at Integrate Better Auth and Google One Tap with Hono and SvelteKit — this post extracts the one error that’s worth its own page for searchability.
References#
Related posts
- Integrate Better Auth and Google One Tap with Hono and SvelteKitA walkthrough of wiring Better Auth with Google One Tap into a SvelteKit app, using Hono for the API layer and Cloudflare D1 + Workers for the backend.better authgoogle one taphonosveltekitdrizzlecloudflare workerscloudflare D1
- Setting up D1 Database with Drizzle in a Hono Cloudflare Worker AppThis guide covers configuring a D1 database using Drizzle in a Hono app deployed using Cloudflare Workers.cloudflare workerscloudflare D1honodrizzle
- D1 has no transactions — using client.batch() for multi-step writesCloudflare D1 has no BEGIN/COMMIT. The official answer is client.batch(), but it has four subtle rules worth writing down. Worked example from DuitGee's fund-transfer handler.cloudflare D1drizzlehonosveltekitduitgee