Capítulo 17 de 24

Chapter 17: Authenticated Routes

Core Idea

This example guards a group of routes behind login using a pathless layout route (_auth.tsx) whose beforeLoad checks auth context and throws a redirect to /login (carrying the original URL) when the user isn't authenticated. The login route mirrors this by redirecting already-authenticated users away from itself.

Setup

  • Routing style: file-based (createFileRoute)
  • Key dependencies: @tanstack/react-router (beforeLoad, redirect, useRouterState), @tanstack/router-plugin, zod (search param validation), @tanstack/react-router-devtools
  • Structure: src/auth.tsx defines a AuthProvider/useAuth context backed by localStorage; src/routes/_auth.tsx is a pathless layout route that gates its children (/dashboard, /invoices in this example's tree) behind context.auth.isAuthenticated; src/routes/login.tsx handles the credential form and redirect-back flow.

Code Example

// src/auth.tsx
export function useAuth() {
  const context = React.useContext(AuthContext)
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider')
  }
  return context
}
// src/routes/_auth.tsx
export const Route = createFileRoute('/_auth')({
  beforeLoad: ({ context, location }) => {
    if (!context.auth.isAuthenticated) {
      throw redirect({
        to: '/login',
        search: {
          redirect: location.href,
        },
      })
    }
  },
  component: AuthLayout,
})

The login route reverses the check (bounce authenticated users away) and returns to the originally requested page on success:

// src/routes/login.tsx
export const Route = createFileRoute('/login')({
  validateSearch: z.object({
    redirect: z.string().optional().catch(''),
  }),
  beforeLoad: ({ context, search }) => {
    if (context.auth.isAuthenticated) {
      throw redirect({ to: search.redirect || fallback })
    }
  },
  component: LoginComponent,
})

// inside the form submit handler:
await auth.login(username)
await router.invalidate()
await sleep(1) // wait for auth state to propagate before navigating
await navigate({ to: search.redirect || fallback })
  • What it demonstrates: beforeLoad runs before a route (and its children) load, and throw redirect(...) there aborts the pending navigation and redirects instead of rendering; context.auth is supplied to the router at creation time so every route's beforeLoad/loader can read auth state without prop drilling.

Key Takeaways

  1. Put auth gating on a pathless layout route (_auth.tsx, no path segment of its own) so every nested route under it inherits the same beforeLoad check without repeating it per-route.
  2. Carry the attempted destination through search: { redirect: location.href } on the redirect to /login, then read it back in login.tsx's validateSearch/beforeLoad to bounce the user to where they meant to go after signing in.
  3. After a successful login, call router.invalidate() before navigating so route loaders (and other beforeLoad checks) re-run against the fresh auth context instead of stale cached state.
  4. The login route's own beforeLoad redirects away from /login when already authenticated, preventing a logged-in user from seeing the login form again via back-navigation or a stale link.

Connects To

  • Navigation Blocking (ch014): both rely on router-level interception (beforeLoad vs useBlocker) to stop or redirect a navigation before it completes.
  • Kitchen Sink examples (ch005/ch006): those examples include their own utils/auth.tsx login/auth utility used across a broader multi-feature app; this chapter's _auth.tsx pattern is the minimal, focused version of the same idea.