Switch to Better Auth
How to change the authentication provider to Better Auth.




Better Auth is a comprehensive, open-source authentication framework for TypeScript. It is designed to be framework agnostic, but integrates well with Next.js and provides a lot of features out of the box.
1. Swap out the auth
package dependencies
Uninstall the existing Clerk dependencies from the auth
package...
pnpm remove @clerk/nextjs @clerk/themes @clerk/types --filter @repo/auth
...and install the Better Auth dependencies:
pnpm add better-auth next --filter @repo/auth
Additionally, add @repo/database
to the auth
package dependencies.
2. Update your environment variables
Generate a secret with the following command to add it to the .env.local
file in each Next.js application (app
, web
and api
):
npx @better-auth/cli secret
This will add a BETTER_AUTH_SECRET
environment variable to the .env.local
file.
3. Setup the server and client auth
Update the auth
package files with the following code:
Server
import { betterAuth } from 'better-auth';
import { nextCookies } from "better-auth/next-js";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { database } from "@repo/database"
export const auth = betterAuth({
database: prismaAdapter(database, {
provider: 'postgresql',
}),
plugins: [
nextCookies()
// organization() // if you want to use organization plugin
],
//...add more options here
});
Client
import { createAuthClient } from 'better-auth/react';
export const { signIn, signOut, signUp, useSession } = createAuthClient();
Read more in the Better Auth installation guide.
4. Update the auth components
Update both the sign-in.tsx
and sign-up.tsx
components in the auth
package to use the signIn
and signUp
functions from the client
file.
Sign In
"use client";
import { signIn } from '../client';
import { useState } from 'react';
export const SignIn = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
return (
<form
onSubmit={async (e) => {
e.preventDefault();
await signIn.email({
email,
password,
})
}}
>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Sign in</button>
</form>
);
}
Sign Up
"use client";
import { signUp } from '../client';
import { useState } from 'react';
export const SignUp = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [name, setName] = useState("");
return (
<form
onSubmit={async (e) => {
e.preventDefault();
await signUp.email({
email,
password,
name
})
}}
>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button type="submit">Sign up</button>
</form>
);
}
You can use different sign-in methods like social providers, phone, username etc. Read more about Better Auth basic usage.
5. Generate Prisma Models
From the root folder, generate Prisma models for Better Auth by running the following command:
npx @better-auth/cli generate --output ./packages/database/prisma/schema.prisma --config ./packages/auth/server.ts
You may have to comment out the server-only
directive in packages/database/index.ts
temporarily. Ensure you have environment variables set.
6. Update the Provider file
Better Auth has no concept of a Provider as a higher-order component, so you can either remove it entirely or just replace it with a stub, like so:
import type { ReactNode } from 'react';
type AuthProviderProps = {
children: ReactNode;
};
export const AuthProvider = ({ children }: AuthProviderProps) => children;
7. Change Middleware
Change the middleware in the auth
package to the following:
import type { NextRequest } from "next/server";
import { NextResponse } from 'next/server';
const isProtectedRoute = (request: NextRequest) => {
return request.url.startsWith("/dashboard"); // change this to your protected route
}
export const authMiddleware = async (request: NextRequest) => {
const url = new URL('/api/auth/get-session', request.nextUrl.origin);
const response = await fetch(url, {
headers: {
cookie: request.headers.get('cookie') || '',
},
});
const session = await response.json();
if (isProtectedRoute(request) && !session) {
return NextResponse.redirect(new URL("/sign-in", request.url));
}
return NextResponse.next();
}
8. Define and add Next.js Handlers to your app
Unlike
Clerk
, you need to host auth handlers which will retrieve sessions, authenticate requests etc...
import 'server-only';
import { toNextJsHandler } from 'better-auth/next-js';
import { auth } from './server';
export const { POST, GET } = toNextJsHandler(auth);
export { POST, GET } from '@repo/auth/handlers'
9. Update your apps
From here, you'll need to replace any remaining Clerk implementations in your apps with Better Auth.
Here is some inspiration:
const user = await currentUser();
const { redirectToSignIn } = await auth();
// to
const session = await auth.api.getSession({
headers: await headers(), // from next/headers
});
if (!session?.user) {
return redirect('/sign-in'); // from next/navigation
}
// do something with session.user
const { orgId } = await auth();
// to
const h = await headers(); // from next/headers
const session = await auth.api.getSession({
headers: h,
});
const orgId = session?.session.activeOrganizationId;
const fullOrganization = await auth.api.getFullOrganization({
headers: h,
query: { organizationId: orgId },
});
import { clerkClient } from '@repo/auth/server';
const clerk = await clerkClient();
const users = await clerk.users.getUserList();
const user = users.data.find(
(user) => user.privateMetadata.stripeCustomerId === customerId
);
// to
import { database } from '@repo/database';
const user = await database.user.findFirst({
where: {
privateMetadata: {
contains: { stripeCustomerId: customerId },
},
},
});
For using organization, check organization plugin and more from the Better Auth documentation.