BETTER-AUTH. UI
Integrations

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.

app/providers.tsx
"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:

app/layout.tsx
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.

app/auth/[path]/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

app/account/[path]/page.tsx
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

app/organization/[path]/page.tsx
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:

app/organization/[slug]/[path]/page.tsx
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.

pages/_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

pages/auth/[path].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

pages/account/[path].tsx
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

pages/organization/[path].tsx
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 } };
}