BetterStarter logoBetterStarter
_archiveGuides

Protected Routes

Create routes that require authentication.

Docs are in beta — content is improving rapidly. Found something missing? Open an issue on GitHub or reach out on Twitter.

Overview

Protected routes require users to be authenticated before accessing them. BetterStarter uses file-based routing with an underscore convention for protected routes.

Creating a Protected Route

Use the _ prefix in your route file to denote it as protected:

File Structure

src/routes/
  profile/
    __root.tsx        # Layout
    index.tsx         # Public profile list (/profile/)
    $_/               # Protected routes group
      settings.tsx    # /profile/settings (requires auth)
      edit.tsx        # /profile/edit (requires auth)

Route Example

// src/routes/profile/$_/settings.tsx
import { createFileRoute, notFound } from '@tanstack/react-router'
import { authMiddleware } from '@/lib/auth/middleware'

const loader = async (opts) => {
  const user = await getAuthUser(opts)
  if (!user) {
    throw notFound()
  }
  return { user }
}

export const Route = createFileRoute('/profile/$_/settings')({
  loader,
  component: SettingsView,
})

function SettingsView() {
  const { user } = Route.useLoaderData()
  return <div>Settings for {user.name}</div>
}

Accessing User Context

In your route loader or component:

import { Route } from '@tanstack/react-router'

function MyComponent() {
  const context = Route.useMatch()
  const user = context?.context?.user
  
  if (!user) {
    return <div>Not authenticated</div>
  }
  
  return <div>Hello, {user.name}</div>
}

Redirect on Auth Required

For routes that should redirect unauthenticated users:

import { redirect } from '@tanstack/react-router'

const loader = async () => {
  const user = await getCurrentUser()
  if (!user) {
    throw redirect({ to: '/auth/signin' })
  }
  return { user }
}

Pattern: Role-Based Access

const loader = async () => {
  const user = await getCurrentUser()
  if (!user || user.role !== 'admin') {
    throw notFound()
  }
  return { user }
}

Testing Protected Routes

Use the route directly in tests:

test('redirects to signin when not authenticated', async () => {
  const loader = Route.options.loader
  expect(() => loader({ params: {} })).toThrow(redirect)
})

On this page