Co-authored by Bereket Engida 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 @repo/database next --filter @repo/auth
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:
packages/auth/server.ts
packages/auth/client.ts
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
});
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.
packages/auth/components/sign-in.tsx
packages/auth/components/sign-up.tsx
"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 ,
name
})
} }
>
< 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 >
);
}
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.
Best practice is to have a better-auth.ts
file, but we’re just specifying the existing server.ts
left over from Clerk here.
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:
packages/auth/provider.tsx
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:
packages/auth/middleware.ts
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. 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 .