guide
A complete, real-world guide to setting up Better Auth in Next.js App Router with secure sessions, sign-in flows, and server-first best practices.

Authentication in Next.js looks very different with the App Router.
Server Components, streaming, and layouts mean that traditional client-heavy auth patterns no longer fit well.
With the App Router, authentication should be:
This is exactly where Better Auth fits in.
Better Auth is a modern authentication solution designed for server-first frameworks like Next.js.
It focuses on:
Instead of pushing auth logic to the client, Better Auth keeps it where it belongs — on the server.
Start by installing Better Auth in your Next.js project:
pnpm add better-authIf you plan to use a database-backed session store (recommended for production):
pnpm add @better-auth/prismaAdd the required environment variables to your .env file:
AUTH_SECRET=your-long-random-secret
AUTH_URL=http://localhost:3000Always use strong secrets and HTTPS in production.
Create a single, centralized auth configuration file.
// lib/auth.ts
import { betterAuth } from "better-auth";
export const auth = betterAuth({
secret: process.env.AUTH_SECRET!,
});This file should only be imported on the server.
With the App Router, authentication routes live inside app/api.
// app/api/auth/[...better]/route.ts
import { auth } from "@/lib/auth";
export const { GET, POST } = auth.handler();No controllers, no boilerplate — Better Auth handles everything internally.
One of the biggest advantages of Better Auth is accessing the session directly inside Server Components.
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const session = await auth.getSession();
if (!session) {
redirect("/signin");
}
return (
<div>
<h1>Welcome back, {session.user.email}</h1>
</div>
);
}This approach avoids client-side loading states and hydration issues.
Sign-in is handled on the client, but the logic stays minimal.
"use client";
import { signIn } from "better-auth/client";
export default function SignInPage() {
return (
<button
onClick={() => signIn()}
className="rounded-md border px-4 py-2"
>
Sign in
</button>
);
}Better Auth automatically manages redirects and cookies.
Signing out is just as simple:
"use client";
import { signOut } from "better-auth/client";
<button onClick={() => signOut()}>
Sign out
</button>You can also protect API routes using the same server-side session check:
import { auth } from "@/lib/auth";
export async function GET() {
const session = await auth.getSession();
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
return Response.json({ user: session.user });
}This keeps authorization logic consistent across your app.
Better Auth supports OAuth providers like GitHub and Google.
betterAuth({
providers: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
});Trigger OAuth sign-in with:
signIn("github");Better Auth helps avoid these issues when used correctly.
Authentication should be boring, predictable, and secure.
With Better Auth and the App Router, it finally is.
I share new articles, insights, and experiments in modern web development. No spam — just useful content.