BETTER-AUTH. UI
Integrations

TanStack Start

Integrate Better Auth UI with TanStack Start

Prerequisites

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

Integration

Configure AuthProvider

Configure AuthProvider with TanStack Router's navigation.

components/providers.tsx
import { Link, useNavigate } from "@tanstack/react-router"
import { ThemeProvider } from "next-themes"
import type { ReactNode } from "react"

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

export function Providers({ children }: { children: ReactNode }) {
  const navigate = useNavigate()

  return (
    <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
      <AuthProvider
        authClient={authClient}
        magicLink
        socialProviders={["github", "google"]}
        navigate={navigate}
        Link={({ href, ...props }) => <Link to={href} {...props} />}
      >
        {children}

        <Toaster />
      </AuthProvider>
    </ThemeProvider>
  )
}
components/providers.tsx
import { AuthProvider } from "@better-auth-ui/shadcn"
import { Link, useNavigate } from "@tanstack/react-router"
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 navigate = useNavigate()
  const { theme, setTheme } = useTheme()

  return (
    <AuthProvider
      authClient={authClient}
      magicLink
      multiSession
      socialProviders={["github", "google"]}
      redirectTo="/dashboard"
      navigate={navigate}
      settings={{
        theme,
        setTheme
      }}
      Link={({ href, ...props }) => <Link to={href} {...props} />}
    >
      {children}

      <Toaster />
    </AuthProvider>
  )
}

The navigate and Link props integrate with TanStack Router's navigation system. For TanStack Router, you can pass the navigate function directly since it accepts { href, replace } options.

Update the Root Route

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

routes/__root.tsx
import { TanStackDevtools } from "@tanstack/react-devtools"
import { createRootRoute, HeadContent, Scripts } from "@tanstack/react-router"
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools"
import { ThemeProvider } from "next-themes"
import type { ReactNode } from "react"
import { Header } from "@/components/header"
import { Providers } from "@/components/providers"
import appCss from "@/styles/app.css?url"

export const Route = createRootRoute({
  head: () => ({
    meta: [
      {
        charSet: "utf-8"
      },
      {
        name: "viewport",
        content: "width=device-width, initial-scale=1"
      },
      {
        title: "Start shadcn/ui Example"
      }
    ],
    links: [
      {
        rel: "stylesheet",
        href: appCss
      }
    ]
  }),
  shellComponent: RootDocument
})

function RootDocument({ children }: { children: ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        <HeadContent />
      </head>

      <body className="antialiased min-h-svh flex flex-col">
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          <Providers>
            <Header />

            {children}
          </Providers>
        </ThemeProvider>

        <TanStackDevtools
          config={{
            position: "bottom-right"
          }}
          plugins={[
            {
              name: "TanStack Router",
              render: <TanStackRouterDevtoolsPanel />
            }
          ]}
        />

        <Scripts />
      </body>
    </html>
  )
}

Create the Auth Page

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

routes/auth/$path.tsx
import { viewPaths } from "@better-auth-ui/react/core"
import { createFileRoute, redirect } from "@tanstack/react-router"

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

export const Route = createFileRoute("/auth/$path")({
  beforeLoad({ params: { path } }) {
    if (!Object.values(viewPaths.auth).includes(path)) {
      throw redirect({ to: "/" })
    }
  },
  component: AuthPage
})

function AuthPage() {
  const { path } = Route.useParams()

  return (
    <div className="min-h-svh flex items-center justify-center p-4 md:p-6">
      <Auth path={path} />
    </div>
  )
}
routes/auth/$path.tsx
import { Auth } from "@better-auth-ui/shadcn"
import { viewPaths } from "@better-auth-ui/shadcn/core"
import { createFileRoute, redirect } from "@tanstack/react-router"

export const Route = createFileRoute("/auth/$path")({
  beforeLoad({ params: { path } }) {
    if (!Object.values(viewPaths.auth).includes(path)) {
      throw redirect({ to: "/" })
    }
  },
  component: AuthPage
})

function AuthPage() {
  const { path } = Route.useParams()

  return (
    <div className="grow flex items-center justify-center 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.

Protecting Routes

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

routes/dashboard.tsx
import { createFileRoute, Link } from "@tanstack/react-router"

import { Spinner } from "@/components/ui/spinner"
import { useAuthenticate } from "@/hooks/auth/use-authenticate"

export const Route = createFileRoute("/dashboard")({
  component: Dashboard
})

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

  if (!sessionData) {
    return (
      <div className="min-h-svh flex items-center justify-center">
        <Spinner />
      </div>
    )
  }

  return (
    <div className="min-h-svh flex flex-col items-center justify-center gap-4">
      <h1 className="text-2xl">Hello, {sessionData.user.email}</h1>

      <Link to="/auth/$path" params={{ path: "sign-out" }}>
        Sign Out
      </Link>
    </div>
  )
}
routes/dashboard.tsx
import { UserButton } from "@better-auth-ui/shadcn"
import { useAuthenticate } from "@better-auth-ui/shadcn/react"
import { createFileRoute } from "@tanstack/react-router"
import { Spinner } from "@/components/ui/spinner"

export const Route = createFileRoute("/dashboard")({
  component: Dashboard
})

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

  if (!sessionData) {
    return (
      <div className="min-h-svh flex items-center justify-center">
        <Spinner />
      </div>
    )
  }

  return (
    <div className="min-h-svh flex flex-col items-center justify-center gap-4">
      <UserButton />
    </div>
  )
}

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

Example Project

For a complete working example, check out the start-shadcn-example in the repository.

Last updated on

On this page