This example demonstrates how to use better-auth-cloudflare, our authentication package specifically designed for Cloudflare, with a Next.js application deployed to Cloudflare Workers using the OpenNext Cloudflare adapter.
better-auth-cloudflare provides seamless authentication capabilities for applications deployed to Cloudflare's serverless platform. This package handles:
- User authentication and session management
- Integrating with Cloudflare's D1 database
- Support for the App Router architecture in Next.js
- Schema generation with Drizzle ORM
This example project showcases a complete implementation of our authentication solution in a real-world Next.js application.
First, run the development server:
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun devOpen http://localhost:3000 with your browser to see the authentication features in action.
Our package provides several scripts to help manage authentication:
pnpm auth:generate: Generates the Drizzle schema for Better Auth based on your configuration insrc/auth/index.ts. The output is saved tosrc/db/auth.schema.ts.pnpm auth:format: Formats the generatedauth.schema.tsfile using Prettier.pnpm auth:update: A convenience script that runs bothauth:generateandauth:formatin sequence.
The example configures better-auth-cloudflare to work with Cloudflare's D1 database:
pnpm db:generate: Generates SQL migration files based on changes in your Drizzle schema (defined insrc/db/schema.tsand the generatedsrc/db/auth.schema.ts).pnpm db:migrate:dev: Applies pending migrations to your local D1 database.pnpm db:migrate:prod: Applies pending migrations to your remote/production D1 database.pnpm db:studio:dev: Starts Drizzle Studio, a local GUI for browsing your local D1 database.pnpm db:studio:prod: Starts Drizzle Studio for your remote/production D1 database.
Deploy your Next.js application with Better Auth to Cloudflare:
pnpm build:cf: Builds the application specifically for Cloudflare Workers using OpenNext.pnpm deploy: Builds the application for Cloudflare and deploys it.pnpm preview: Builds the application for Cloudflare and allows you to preview it locally before deploying.
pnpm build: Creates an optimized production build of your Next.js application.pnpm clean: Removes build artifacts, cached files, andnode_modules.pnpm clean-deploy: Cleans the project, reinstalls dependencies, and then deploys.pnpm format: Formats all project files using Prettier.pnpm lint: Lints the project using Next.js's built-in ESLint configuration.
OpenNext.js requires a more complex auth configuration due to its async database initialization and singleton requirements. The configuration in src/auth/index.ts uses the following pattern:
import { getCloudflareContext } from "@opennextjs/cloudflare";
import { betterAuth } from "better-auth";
import { withCloudflare } from "better-auth-cloudflare";
import { drizzleAdapter } from "@better-auth/drizzle-adapter";
import { anonymous, openAPI } from "better-auth/plugins";
import { getDb } from "../db";
// Define an asynchronous function to build your auth configuration
async function authBuilder() {
const dbInstance = await getDb();
const cfCtx = getCloudflareContext();
return betterAuth({
...withCloudflare(
{
autoDetectIpAddress: true,
geolocationTracking: true,
cf: cfCtx.cf,
d1: {
db: dbInstance,
options: {
usePlural: true,
debugLogs: true,
},
},
kv: cfCtx.env.KV,
},
{
baseURL: cfCtx.env.BETTER_AUTH_URL,
trustedOrigins: (cfCtx.env.BETTER_AUTH_TRUSTED_ORIGINS ?? "").split(",").filter(Boolean),
rateLimit: {
enabled: true,
window: 60, // Minimum KV TTL is 60s
max: 100, // reqs/window
},
plugins: [openAPI(), anonymous()],
}
),
});
}
// Singleton pattern to ensure a single auth instance
let authInstance: Awaited<ReturnType<typeof authBuilder>> | null = null;
// Asynchronously initializes and retrieves the shared auth instance
export async function initAuth() {
if (!authInstance) {
authInstance = await authBuilder();
}
return authInstance;
}For production deployment, set the following via wrangler.toml [vars] or Cloudflare secrets:
BETTER_AUTH_URL— Your worker's primary base URL (e.g.,https://your-app.com). Set as a[vars]entry.BETTER_AUTH_TRUSTED_ORIGINS— Comma-separated list of additional trusted origins (e.g.,https://your-app.workers.dev). Set as a[vars]entry.BETTER_AUTH_SECRET— A random 32+ character secret. Set viawrangler secret put BETTER_AUTH_SECRET.
For the Better Auth CLI to generate schemas, a separate static configuration is required:
// This simplified configuration is used by the Better Auth CLI for schema generation.
// It's necessary because the main `authBuilder` performs async operations like `getDb()`
// which use `getCloudflareContext` (not available in CLI context).
export const auth = betterAuth({
...withCloudflare(
{
autoDetectIpAddress: true,
geolocationTracking: true,
cf: {},
r2: {
bucket: {} as any, // Mock bucket for schema generation
additionalFields: {
category: { type: "string", required: false },
isPublic: { type: "boolean", required: false },
description: { type: "string", required: false },
},
},
},
{
plugins: [openAPI(), anonymous()],
}
),
database: drizzleAdapter(process.env.DATABASE as any, {
provider: "sqlite",
usePlural: true,
debugLogs: true,
}),
});Unlike simpler frameworks, OpenNext.js requires this dual configuration because:
- Async Database Access:
getCloudflareContext()andgetDb()are async operations not available during CLI execution - Singleton Pattern: Ensures single auth instance across serverless functions
- CLI Compatibility: The static
authexport allows schema generation to work
For simpler frameworks like Hono, see the Hono example for a more streamlined single-configuration approach.
To learn more about Better Auth and its features, visit the Better Auth documentation. For better-auth-cloudflare specifics, see the package documentation.
For Next.js resources: