Skip to content

Build Better Auth per request instead of at module eval - #64

Merged
sarthakagrawal927 merged 2 commits into
mainfrom
fix/request-lazy-auth-init
Aug 23, 2026
Merged

Build Better Auth per request instead of at module eval#64
sarthakagrawal927 merged 2 commits into
mainfrom
fix/request-lazy-auth-init

Conversation

@sarthakagrawal927

Copy link
Copy Markdown
Member

Recovered from the local working tree, where it had been sitting uncommitted.

The bug

betterAuth() was called at module scope:

exportconstauth=betterAuth({secret: authSecret,// read at module evalbaseURL: process.env.BETTER_AUTH_URL,
...
});

OpenNext populates process.env from the Cloudflare request environment, which
happens after Worker module evaluation. So the Worker captured undefined
for the secret, base URL, and Google credentials, leaving auth non-functional in
production while working locally (where the env is present at import time).

The fix

buildAuthOptions(env = process.env) + getAuth() defer construction to the
first request. Callers move off the auth singleton:

  • src/app/api/auth/[...all]/route.ts
  • src/app/api/checkout/route.ts
  • src/lib/auth-utils.ts

Also included

  • migrations/0002_better_auth_tables.sql — the D1 tables Better Auth needs
    (user, session, account, verification). Singular names deliberately
    kept separate from the app's existing plural users table.
  • src/lib/auth.test.ts — covers the option builder against an injected env,
    which is what made the lazy split testable.
  • src/__tests__/user-menu.test.tsx — signed-out menu behaviour.
  • Sign-in callbackURL moves //dashboard.

🤖 Generated with Claude Code

sarthakagrawal927and others added 2 commits August 23, 2026 13:29
betterAuth() was called at module scope, so the Worker froze its secret,
baseURL, and Google credentials at module-evaluation time — before OpenNext
populates process.env from the Cloudflare request environment. In production
that captured undefined values and left auth non-functional.
buildAuthOptions()/getAuth() defer construction to the first request, and
callers move from the `auth` singleton to getAuth(). Also adds the Better Auth
D1 tables (singular `user`, `session`, `account`, `verification`, kept separate
from the app's existing plural `users` table) plus unit tests for the option
builder and the signed-out user menu.
The sign-in callback moves from '/' to '/dashboard' so a fresh sign-in lands
somewhere useful.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The route now resolves auth per request through getAuth(), so the existing
module mock exported a symbol the route no longer imports and vitest failed
with: No "getAuth" export is defined on the "@/lib/auth" mock.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sarthakagrawal927
sarthakagrawal927 merged commit 0b72593 into mainAug 23, 2026
6 checks passed
@sarthakagrawal927
sarthakagrawal927 deleted the fix/request-lazy-auth-init branch August 23, 2026 08:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@sarthakagrawal927