Create your own
Lesson illustration

Typed `beforeLoad` for Server-Side Protected Routes

Hello! Welcome to your next lesson in mastering TanStack Start.

Introduction

In our previous lesson, we successfully established a synchronized authentication context. We learned how TanStack Start uses the "Dehydrate-Inject-Hydrate" pattern with useServerFn to make server-side data, like the user's authentication status, available on the client from the very first render. We also integrated this state into the TanStack Router's own context, making it accessible throughout our application's routing layer.

Today, we will leverage that foundation to address the learning outcome: Implement protected routes using a typed, reusable beforeLoad function for server-side authentication checks.

We will explore TanStack Router's beforeLoad lifecycle hook, which is the ideal place to verify a user's credentials before they can access a specific part of your application. You will learn how to create a scalable and maintainable pattern for protecting entire sections of your site, not just individual pages.


1. The beforeLoad Lifecycle Hook

In TanStack Router, each route can have several lifecycle hooks that run at different stages of a navigation event. For our purpose, the most important one is beforeLoad.

As its name implies, beforeLoad is an async function that executes before the route's loader and before its component is rendered. This makes it the perfect "gatekeeper".

Its primary responsibilities in an authentication context are:

  1. Check for authentication: Verify if the user has a valid session.
  2. Redirect if necessary: If the user is not authenticated, abort the current navigation and redirect them to a login page.
  3. Pass context: If the user is authenticated, pass their information down to the route's loader and component.

This hook runs on the server for the initial page load and on the client for subsequent client-side navigations, providing a consistent security model.

To see a basic implementation, let's watch a short clip.

TanStack Router: Authenticated Routes (Guards)

The video "TanStack Router: Authenticated Routes (Guards)" by Dev Leonardo provides an excellent starting point. This first clip demonstrates how to protect a single route.

Watch the segment from 00:57 to 02:41. Focus on how the beforeLoad function is defined on a route and how it uses throw redirect to control navigation.

As you saw, protecting a single route is straightforward. However, applying this to every single protected route in your application would lead to a lot of repeated code. We need a more reusable solution.


2. The Reusable Solution: Protected Layout Routes

A much cleaner and more scalable approach is to group all protected routes under a common layout route. This layout route will contain a single beforeLoad function that protects all of its children.

TanStack Router's file-based routing has a convention for this: a folder prefixed with an underscore (_). For example, a layout route file at src/routes/_authed.tsx will apply its logic to all routes inside the src/routes/_authed/ directory (e.g., dashboard.tsx, settings.tsx), but the _authed part will not appear in the URL.

Let's see this pattern in action.

How I Protect My Tanstack Start Applications

This next clip from Web Dev Cody's video, "How I Protect My Tanstack Start Applications", shows a real-world example of using a layout route to protect an admin section.

Watch from 00:36 to 01:22. Notice how the beforeLoad function is placed in the layout for the /admin path, securing all routes under it.

Now, let's dive into the official documentation to see the canonical implementation of this pattern.

Authentication | TanStack Start React Docs

The official TanStack Start documentation provides the definitive guide for authentication. We will focus on the section about route protection, which perfectly illustrates the _authed layout route pattern.

Please read the section titled "4. Route Protection". You will see two code blocks: one for _authed.tsx (the layout) and one for dashboard.tsx (the child route). Pay close attention to how they work together.


3. Dissecting the Protected Layout Pattern

Let's break down the code from the documentation you just read. It elegantly solves our problem.

The Layout Route (_authed.tsx)

This file acts as the gatekeeper for all nested routes.

// src/routes/_authed.tsx
import { createFileRoute, redirect } from '@tanstack/react-router'
import { getCurrentUserFn } from '../server/auth' // Our server function

export const Route = createFileRoute('/_authed')({
  // This async function runs before any child route can load
  beforeLoad: async ({ location }) => {
    // 1. Perform the server-side check
    const user = await getCurrentUserFn()

    // 2. If not authenticated, redirect to login
    if (!user) {
      throw redirect({
        to: '/login',
        // Preserve the original URL to redirect back after login
        search: { redirect: location.href },
      })
    }

    // 3. If authenticated, pass the user data to child routes
    return { user }
  },
})

Key Points:

  1. Server-Side Check: await getCurrentUserFn() calls the server function we defined in a previous lesson. It runs on the server, reads the secure HTTP-only cookie, and validates the session. This is our server-side authentication check.
  2. Redirect: throw redirect({...}) is the crucial part. It stops the navigation to the protected route and starts a new one to /login. We also pass the intended destination (location.href) as a query parameter, which we'll use in the next lesson to create a seamless login flow.
  3. Typed Context: By returning { user }, we are adding the authenticated user object to this route's context. Because we're using TypeScript, this user object is fully typed and available to all child routes, loaders, and components.

The Child Route (_authed/dashboard.tsx)

This is an example of a page that is now protected by the _authed layout.

// src/routes/_authed/dashboard.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/_authed/dashboard')({
  component: DashboardComponent,
})

function DashboardComponent() {
  // Access the typed user object from the parent layout's context
  const { user } = Route.useRouteContext()

  return (
    <div>
      <h1>Welcome, {user.email}!</h1>
      {/* Dashboard content */}
    </div>
  )
}

Key Points:

  • Simplicity: The dashboard route itself contains no authentication logic. It simply assumes it's protected.
  • Accessing Data: It uses Route.useRouteContext() to safely access the user object that the parent layout route provided. This is efficient because the user data was fetched only once in the layout's beforeLoad.

This pattern is powerful because it's:

  • Reusable: All routes under _authed/ are automatically protected.
  • Type-Safe: TypeScript knows the shape of the user object in the context.
  • Secure: The check happens on the server before any sensitive data is loaded or UI is rendered.
  • Maintainable: The authentication logic is centralized in one file.

Conclusion

In this lesson, you've learned how to implement one of the most critical features of any web application: protected routes. You now have a robust, reusable, and type-safe pattern for securing entire sections of your TanStack Start application.

Key Takeaways:

  • The beforeLoad route lifecycle hook is the designated place for running authentication checks.
  • You can abort navigation and send users to a login page by using throw redirect() from within beforeLoad.
  • Using an underscore-prefixed layout route (e.g., _authed.tsx) is the standard pattern for applying a reusable beforeLoad check to a group of routes.
  • Data returned from a parent's beforeLoad function (like the authenticated user object) is passed down via context and can be accessed in child routes with useRouteContext().

Next Up:

We've implemented the redirect to the login page, including the original URL the user wanted to visit. In our next lesson, we will complete the loop by focusing on: Handling authentication redirects with return URL preservation for a seamless user experience. We'll make our login page read that redirect query parameter and send the user to their intended destination right after they sign in.

Can't find a good explanation? Sign up and we'll make it for you

Sign up