Next.js
This guide covers integrating better-auth-ui into your Next.js project.
Starter Project
Want to skip the installation? Check out the starter here:
App Router
Follow these steps to set up better-auth-ui in your Next.js project using the App Router:
AuthUIProvider
The first step is to set up the <AuthUIProvider /> client component with your authClient, wrapping your layout. This is required to provide the context & hooks to your authentication components across your application.
"use client";
import { AuthUIProvider } from "better-auth-ui";
import Link from "next/link";
import { useRouter } from "next/navigation";
import type { ReactNode } from "react";
import { authClient } from "@/lib/auth-client";
export function Providers({ children }: { children: ReactNode }) {
const router = useRouter();
return (
<AuthUIProvider
authClient={authClient}
navigate={router.push}
replace={router.replace}
onSessionChange={() => {
// Clear router cache (protected routes)
router.refresh();
}}
Link={Link}
>
{children}
</AuthUIProvider>
);
}Note: Since the Next.js App Router caches routes by default,
navigation to protected routes may fail until you perform a router.refresh()
to clear the cache. To prevent this issue, you must use router.refresh() in
the provided onSessionChange callback. This forces Next.js to clear the
router cache and reload middleware-protected content, ensuring subsequent
navigations accurately reflect the current auth state.
Once configured, wrap your layout component with the Providers component:
import type { ReactNode } from "react";
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}The <AuthUIProvider /> can be fully customized with plugins, styles, localization and more. For more information and all available props, see the <AuthUIProvider /> component documentation.
Auth Pages
Create a dynamic route segment for authentication views in app/auth/[authView]/page.tsx.
import { AuthView } from "@/components/auth/auth-view";
import { authViewPaths } from "better-auth-ui";
export const dynamicParams = false;
export function generateStaticParams() {
return Object.values(authViewPaths).map((path) => ({ path }));
}
export default async function AuthPage({
params,
}: {
params: Promise<{ path: string }>;
}) {
const { path } = await params;
return (
<main className="container flex grow flex-col items-center justify-center self-center p-4 md:p-6">
<AuthView path={path} />
</main>
);
}The newly created dynamic route covers the following paths by default:
/auth/sign-in– Sign in via email/password and social providers/auth/sign-up– New account registration/auth/magic-link– Email login without a password/auth/forgot-password– Trigger email to reset forgotten password/auth/two-factor– Two-factor authentication/auth/recover-account– Recover account via backup code/auth/reset-password– Set new password after receiving reset link/auth/sign-out– Log the user out of the application/auth/callback– Internal route to handle Auth callbacks/auth/accept-invitation– Accept an invitation to an organization
Ensure that any links to the authentication process utilize these routes accordingly. All routes will render the <AuthView /> component and automatically handle navigation and authentication flow.
Account Pages
import { AccountView } from "@/components/settings/account-view";
import { accountViewPaths } from "better-auth-ui";
export const dynamicParams = false;
export function generateStaticParams() {
return Object.values(accountViewPaths).map((path) => ({ path }));
}
export default async function AccountPage({
params,
}: {
params: Promise<{ path: string }>;
}) {
const { path } = await params;
return (
<main className="container p-4 md:p-6">
<AccountView path={path} />
</main>
);
}Organization Pages
import { OrganizationView } from "@/components/organization/organization-view";
import { organizationViewPaths } from "better-auth-ui";
export const dynamicParams = false;
export function generateStaticParams() {
return Object.values(organizationViewPaths).map((path) => ({ path }));
}
export default async function OrganizationPage({
params,
}: {
params: Promise<{ path: string }>;
}) {
const { path } = await params;
return (
<main className="container p-4 md:p-6">
<OrganizationView path={path} />
</main>
);
}If you prefer slug-based org URLs, set organization={{ pathMode: "slug", basePath: "/organization", slug: currentSlug }} in the AuthUIProvider and structure your routes accordingly:
import { OrganizationView } from "@/components/organization/organization-view";
import { organizationViewPaths } from "better-auth-ui";
export default async function OrganizationPage({
params,
}: {
params: Promise<{ path: string }>;
}) {
const { path } = await params;
return (
<main className="container p-4 md:p-6">
<OrganizationView path={path} />
</main>
);
}Pages Router
Follow these steps to set up better-auth-ui in your Next.js project using the Pages Router:
AuthUIProvider
First set up the <AuthUIProvider /> within your custom App component in _app.tsx.
import type { AppProps } from "next/app";
import { AuthUIProvider } from "better-auth-ui";
import { useRouter } from "next/router";
import Link from "next/link";
import { authClient } from "@/lib/auth-client";
export default function App({ Component, pageProps }: AppProps) {
const router = useRouter();
return (
<AuthUIProvider
authClient={authClient}
navigate={router.push}
replace={router.replace}
Link={Link}
>
<Component {...pageProps} />
</AuthUIProvider>
);
}Now the authentication context is available across your entire application.
Auth Pages
Create a page with a dynamic segment in your Pages directory in pages/auth/[authView].tsx
import { AuthView } from "@/components/auth/auth-view";
import { authViewPaths } from "better-auth-ui";
export default function AuthPage({ path }: { path: string }) {
return (
<main className="container flex grow flex-col items-center justify-center gap-3 self-center p-4 md:p-6">
<AuthView path={path} />
</main>
);
}
export async function getStaticPaths() {
return {
paths: Object.values(authViewPaths).map((path) => ({ params: { path } })),
fallback: false,
};
}
export async function getStaticProps({ params }: { params: { path: string } }) {
return { props: { path: params.path } };
}These routes match the list shown in the App Router section above.
Account Pages
import { AccountView } from "@/components/settings/account-view";
import { accountViewPaths } from "better-auth-ui";
export default function AccountPage({ path }: { path: string }) {
return (
<main className="container p-4 md:p-6">
<AccountView path={path} />
</main>
);
}
export async function getStaticPaths() {
return {
paths: Object.values(accountViewPaths).map((path) => ({
params: { path },
})),
fallback: false,
};
}
export async function getStaticProps({ params }: { params: { path: string } }) {
return { props: { path: params.path } };
}Organization Pages
import { OrganizationView } from "@/components/organization/organization-view";
import { organizationViewPaths } from "better-auth-ui";
export default function OrganizationPage({ path }: { path: string }) {
return (
<main className="container p-4 md:p-6">
<OrganizationView path={path} />
</main>
);
}
export async function getStaticPaths() {
return {
paths: Object.values(organizationViewPaths).map((path) => ({
params: { path },
})),
fallback: false,
};
}
export async function getStaticProps({ params }: { params: { path: string } }) {
return { props: { organizationView: params.path } };
}