BETTER-AUTH. UI
Integrations

Next.js

Integrate Better Auth UI with Next.js

Prerequisites

Make sure you've completed the Quick Start guide first.

Integration

Configure AuthProvider

Configure AuthProvider with Next.js navigation.

components/providers.tsx
"use client"

import { AuthProvider } from "@better-auth-ui/react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { useTheme } from "next-themes"
import type { ReactNode } from "react"

import { authClient } from "@/lib/auth-client"
import { Toaster } from "./ui/sonner"

export function Providers({ children }: { children: ReactNode }) {
  const router = useRouter()
  const { theme, setTheme } = useTheme()

  return (
    <AuthProvider
      authClient={authClient}
      socialProviders={["google", "github"]}
      deleteUser={{ enabled: true }}
      magicLink
      multiSession
      redirectTo="/dashboard"
      navigate={({ to, replace }) =>
        replace ? router.replace(to) : router.push(to)
      }
      settings={{
        appearance: { theme, setTheme }
      }}
      Link={Link}
    >
      {children}

      <Toaster />
    </AuthProvider>
  )
}
components/providers.tsx
"use client"

import { AuthProvider } from "@better-auth-ui/shadcn/react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { useTheme } from "next-themes"
import type { ReactNode } from "react"

import { authClient } from "@/lib/auth-client"
import { Toaster } from "./ui/sonner"

export function Providers({ children }: { children: ReactNode }) {
  const router = useRouter()
  const { theme, setTheme } = useTheme()

  return (
    <AuthProvider
      authClient={authClient}
      socialProviders={["google", "github"]}
      deleteUser={{ enabled: true }}
      magicLink
      multiSession
      redirectTo="/dashboard"
      navigate={({ to, replace }) =>
        replace ? router.replace(to) : router.push(to)
      }
      settings={{
        appearance: { theme, setTheme }
      }}
      Link={Link}
    >
      {children}

      <Toaster />
    </AuthProvider>
  )
}

The navigate and Link props integrate with Next.js's navigation system. The navigate prop accepts { to, replace } options to handle both push and replace navigation.

Update the Root Layout

Wrap your application with the Providers component in your root layout.

app/layout.tsx
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import { ThemeProvider } from "next-themes"
import type { ReactNode } from "react"

import "@/styles/app.css"

import { Header } from "@/components/header"
import { Providers } from "@/components/providers"
import { cn } from "@/lib/utils"

const inter = Inter({ subsets: ["latin"], variable: "--font-sans" })

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app"
}

export default function RootLayout({
  children
}: Readonly<{
  children: ReactNode
}>) {
  return (
    <html
      lang="en"
      suppressHydrationWarning
      className={cn("font-sans", inter.variable)}
    >
      <body className="antialiased min-h-svh flex flex-col">
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          <Providers>
            <Header />
            {children}
          </Providers>
        </ThemeProvider>
      </body>
    </html>
  )
}

Create the Auth Page

Create a dynamic auth page that renders the appropriate authentication view based on the path.

app/auth/[path]/page.tsx
import { viewPaths } from "@better-auth-ui/react/core"
import { notFound } from "next/navigation"

import { Auth } from "@/components/auth/auth"

export default async function AuthPage({
  params
}: {
  params: Promise<{
    path: string
  }>
}) {
  const { path } = await params

  if (!Object.values(viewPaths.auth).includes(path)) {
    notFound()
  }

  return (
    <div className="flex justify-center my-auto p-4 md:p-6">
      <Auth path={path} />
    </div>
  )
}
app/auth/[path]/page.tsx
import { Auth } from "@better-auth-ui/shadcn"
import { viewPaths } from "@better-auth-ui/shadcn/core"
import { notFound } from "next/navigation"

export default async function AuthPage({
  params
}: {
  params: Promise<{
    path: string
  }>
}) {
  const { path } = await params

  if (!Object.values(viewPaths.auth).includes(path)) {
    notFound()
  }

  return (
    <div className="flex justify-center my-auto p-4 md:p-6">
      <Auth path={path} />
    </div>
  )
}

The viewPaths.auth object contains all valid auth paths: sign-in, sign-up, sign-out, forgot-password, reset-password, and magic-link.

Create the Settings page

Create a dynamic settings route that renders the Settings component for the URL segment. Validate the segment against viewPaths.settings so unknown paths return a 404.

app/settings/[path]/page.tsx
import { viewPaths } from "@better-auth-ui/react/core"
import { notFound } from "next/navigation"

import { Settings } from "@/components/settings/settings"

export default async function SettingsPage({
  params
}: {
  params: Promise<{
    path: string
  }>
}) {
  const { path } = await params

  if (!Object.values(viewPaths.settings).includes(path)) {
    notFound()
  }

  return (
    <div className="w-full max-w-3xl mx-auto p-4 md:p-6">
      <Settings path={path} />
    </div>
  )
}
app/settings/[path]/page.tsx
import { Settings } from "@better-auth-ui/shadcn"
import { viewPaths } from "@better-auth-ui/shadcn/core"
import { notFound } from "next/navigation"

export default async function SettingsPage({
  params
}: {
  params: Promise<{
    path: string
  }>
}) {
  const { path } = await params

  if (!Object.values(viewPaths.settings).includes(path)) {
    notFound()
  }

  return (
    <div className="w-full max-w-3xl mx-auto p-4 md:p-6">
      <Settings path={path} />
    </div>
  )
}

The viewPaths.settings object contains valid settings path segments: account and security.

Protecting Routes

Use the useAuthenticate hook to protect routes and access session data.

app/dashboard/page.tsx
"use client"

import { useAuthenticate } from "@better-auth-ui/react"
import Link from "next/link"

import { Spinner } from "@/components/ui/spinner"

export default function Dashboard() {
  const { data: sessionData } = useAuthenticate()

  if (!sessionData) {
    return (
      <div className="flex justify-center my-auto">
        <Spinner color="current" />
      </div>
    )
  }

  return (
    <div className="flex flex-col items-center my-auto">
      <h1 className="text-2xl">Hello, {sessionData.user.email}</h1>

      <Link href="/auth/sign-out">Sign Out</Link>
    </div>
  )
}
app/dashboard/page.tsx
"use client"

import { useAuthenticate } from "@better-auth-ui/shadcn/react"
import Link from "next/link"

import { Spinner } from "@/components/ui/spinner"

export default function Dashboard() {
  const { data: sessionData } = useAuthenticate()

  if (!sessionData) {
    return (
      <div className="flex justify-center my-auto">
        <Spinner color="current" />
      </div>
    )
  }

  return (
    <div className="flex flex-col items-center my-auto">
      <h1 className="text-2xl">Hello, {sessionData.user.email}</h1>

      <Link href="/auth/sign-out">Sign Out</Link>
    </div>
  )
}

The useAuthenticate hook will automatically redirect unauthenticated users to the sign-in page.

Example Project

For complete working examples, see next-shadcn-example (npm package) and next-shadcn-registry-example (shadcn registry) in the repository.

Last updated on

On this page