Facebook Pixel

Navigation Guards

In this tutorial, we will learn how to implement navigation guards in React Router 7 using modern React 19 patterns. Navigation guards control who can access a route and when navigation is allowed.


What We Will Learn

  • What navigation guards are
  • When navigation guards are needed
  • Protecting routes with loaders
  • Redirecting unauthenticated users
  • Guarding entire route sections
  • Blocking navigation based on conditions
  • Best practices for route protection

What Is a Navigation Guard

A navigation guard is logic that runs before a route renders.

Common use cases:

  • Authentication checks
  • Authorization and roles
  • Preventing access to private pages
  • Redirecting users to login
  • Blocking navigation when data is unsaved

In React Router 7, guards are implemented at the routing level, not inside components.


Why Guards Belong in the Router

Putting guard logic in components causes problems:

  • UI flashes before redirect
  • Logic is duplicated
  • Routes are harder to reason about

React Router 7 encourages data first routing, where checks happen before rendering.


Guarding Routes with Loaders

The primary way to create navigation guards is using loaders.

A loader runs before navigation completes.


Basic Folder Structure

/src
  /app
    app.tsx
    root-layout.tsx
    auth.ts
  /pages
    login.tsx
    dashboard.tsx
  main.tsx

Creating an Auth Utility

This simulates authentication state.

// src/app/auth.ts
export function isAuthenticated() {
  return Boolean(localStorage.getItem("token"));
}

Creating a Protected Loader

The loader checks authentication and redirects if needed.

// src/app/app.tsx
import { createBrowserRouter, redirect } from "react-router";
import RootLayout from "./root-layout";
import Dashboard from "../pages/dashboard";
import Login from "../pages/login";
import { isAuthenticated } from "./auth";
 
function requireAuth() {
  if (!isAuthenticated()) {
    throw redirect("/login");
  }
 
  return null;
}

Throwing redirect immediately stops navigation.


Applying the Guard to a Route

// src/app/app.tsx
export const router = createBrowserRouter([
  {
    element: <RootLayout />,
    children: [
      { path: "/login", element: <Login /> },
      {
        path: "/dashboard",
        element: <Dashboard />,
        loader: requireAuth,
      },
    ],
  },
]);

Now /dashboard is protected.


Guarding an Entire Section with Nested Routes

Guards become more powerful with nested layouts.

// src/app/app.tsx
import DashboardLayout from "./dashboard-layout";
 
export const router = createBrowserRouter([
  {
    element: <RootLayout />,
    children: [
      { path: "/login", element: <Login /> },
      {
        element: <DashboardLayout />,
        loader: requireAuth,
        children: [
          { path: "/dashboard", element: <Dashboard /> },
          { path: "/dashboard/settings", element: <Settings /> },
        ],
      },
    ],
  },
]);

The loader runs for all child routes.


Creating the Login Page

// src/pages/login.tsx
"use client";
 
export default function Login() {
  function handleLogin() {
    localStorage.setItem("token", "demo");
    window.location.href = "/dashboard";
  }
 
  return (
    <>
      <title>Login</title>
 
      <h1>Login</h1>
      <button onClick={handleLogin}>Sign In</button>
    </>
  );
}

Once logged in, protected routes become accessible.


Preventing Access for Authenticated Users

You can also guard public routes like login pages.

function redirectIfAuthenticated() {
  if (isAuthenticated()) {
    throw redirect("/dashboard");
  }
 
  return null;
}
{
  path: "/login",
  element: <Login />,
  loader: redirectIfAuthenticated,
}

Blocking Navigation Based on Conditions

Sometimes you want to block navigation, not redirect.

Example: unsaved form data.

React Router 7 provides navigation blocking through router APIs rather than effects inside components.

The recommended approach is:

  • Keep form state in the route
  • Prevent navigation via loader or action conditions
  • Ask for confirmation before redirecting

This avoids unreliable component based guards.


Why Not Use Component Guards

Avoid patterns like:

  • Redirecting inside components
  • Using effects to navigate
  • Conditional rendering for protection

These cause:

  • UI flicker
  • Inconsistent behavior
  • Hard to maintain logic

Routing logic belongs in the router.


Best Practices for Navigation Guards

  • Use loaders for access control
  • Throw redirects instead of navigating manually
  • Guard layouts, not individual pages when possible
  • Keep auth logic centralized
  • Treat routing as part of application state

Common Mistakes

  • Checking auth inside components
  • Using effects to redirect
  • Protecting routes after render
  • Duplicating guard logic

Conclusion

Navigation guards in React Router 7 are clean, predictable, and powerful when implemented with loaders. By moving access control into the router, you prevent UI flashes, reduce duplication, and keep your application easy to reason about.

If a user should never see a page, the router should stop them before it renders.