Skip to main content

Overview

The Auth Dashboard uses React Router v6 for client-side routing with a nested route structure and authentication guards.
The routing system provides automatic route protection, layout composition, and type-safe navigation.

Router Configuration

The main router is configured in src/app/router.tsx:12-32:
import { createBrowserRouter } from "react-router-dom";
import Login from "../pages/Login";
import ProtectedRoute from "./ProtectedRoute";
import { DashboardLayout } from "../shared/layout/DashboardLayout";
import Dashboard from "../pages/dashboard/Dashboard";
import PublicRoute from "./PublicRoute";
import Users from "../pages/dashboard/Users";
import UserDetails from "../pages/dashboard/UserDetails";
import Settings from "../pages/dashboard/Settings";

export const router = createBrowserRouter([
  {
    element: <PublicRoute />,
    children: [{ path: "/login", element: <Login /> }],
  },
  {
    element: <ProtectedRoute />,
    children: [
      {
        path: "/",
        element: <DashboardLayout />,
        children: [
          { index: true, element: <Dashboard /> },
          { path: "users", element: <Users /> },
          { path: "users/:id", element: <UserDetails /> },
          { path: "settings", element: <Settings /> },
        ],
      },
    ],
  },
]);

Route Structure

The application has a clear route hierarchy:
/
├── /login (public only)
└── / (protected)
    ├── / (dashboard home)
    ├── /users (user list)
    ├── /users/:id (user details)
    └── /settings (app settings)

Route Types

Routes accessible only when NOT authenticated:
  • /login - Login page
When an authenticated user tries to access these routes, they’re redirected to the dashboard.

Route Guards

The application uses two route guard components to control access.

Protected Route

The ProtectedRoute component (src/app/ProtectedRoute.tsx:4-10) ensures only authenticated users can access routes:
import { Navigate, Outlet } from "react-router-dom";
import { useAuthStore } from "../features/auth/authStore";

const ProtectedRoute = () => {
  const user = useAuthStore((state) => state.user);

  return user ? <Outlet /> : <Navigate to="/login" replace />;
};

export default ProtectedRoute;
1

Check authentication

Reads the user from the auth store
2

Render or redirect

If authenticated, renders child routes via <Outlet />. Otherwise, redirects to /login

Public Route

The PublicRoute component (src/app/PublicRoute.tsx:4-10) prevents authenticated users from accessing public-only pages:
import { Navigate, Outlet } from "react-router-dom";
import { useAuthStore } from "../features/auth/authStore";

const PublicRoute = () => {
  const user = useAuthStore((state) => state.user);

  return user ? <Navigate to="/" replace /> : <Outlet />;
};

export default PublicRoute;
Both guards use replace to prevent the redirect from being added to browser history, ensuring a clean back button experience.

Nested Routes & Layouts

The router uses nested routes to compose layouts:

Dashboard Layout

The DashboardLayout component (src/shared/layout/DashboardLayout.tsx:8-27) wraps all protected pages:
import { Outlet } from "react-router-dom";
import Topbar from "../components/Topbar";
import SideBar from "../components/Sidebar";
import { Toast } from "../store/Toast";
import { useSettingsStore } from "../store/useSettingsStore";
import { useEffect } from "react";

export const DashboardLayout = () => {
  const theme = useSettingsStore((s) => s.theme);

  useEffect(() => {
    document.documentElement.classList.toggle("dark", theme === "dark");
  }, [theme]);

  return (
    <div className="flex h-screen overflow-hidden w-full">
      <SideBar />

      <div className="flex-1 flex flex-col bg-slate-100 dark:bg-slate-400">
        <Topbar />
        <Toast />
        <main className="flex-1 overflow-y-auto p-6 w-full">
          <Outlet />
        </main>
      </div>
    </div>
  );
};
Layout Features:
  • Sidebar navigation - Persistent navigation menu
  • Topbar - User info and actions
  • Toast notifications - Global notification system
  • Theme support - Dark/light mode with Tailwind CSS
  • Outlet - Renders nested route content
The Sidebar component (src/shared/components/Sidebar.tsx:4-52) uses NavLink for active route styling:
import { NavLink } from "react-router-dom";
import { useTranslation } from "react-i18next";

const SideBar = () => {
  const { t } = useTranslation();

  return (
    <aside className="w-64 bg-slate-900 text-white min-h-screen p-4">
      <nav className="flex flex-col gap-2">
        <NavLink
          to="/"
          end
          className={({ isActive }) =>
            `px-3 py-2 rounded transition-colors ${
              isActive
                ? "bg-slate-700 text-white"
                : "text-slate-300 hover:bg-slate-800 hover:text-white"
            }`
          }
        >
          {t("dashboard")}
        </NavLink>

        <NavLink
          to="users"
          className={({ isActive }) =>
            `px-3 py-2 rounded transition-colors ${
              isActive
                ? "bg-slate-700 text-white"
                : "text-slate-300 hover:bg-slate-800 hover:text-white"
            }`
          }
        >
          {t("users")}
        </NavLink>

        <NavLink
          to="settings"
          className={({ isActive }) =>
            `px-3 py-2 rounded transition-colors ${
              isActive
                ? "bg-slate-700 text-white"
                : "text-slate-300 hover:bg-slate-800 hover:text-white"
            }`
          }
        >
          {t("settings")}
        </NavLink>
      </nav>
    </aside>
  );
};
The end prop on the root route ensures it’s only active for exact matches. Without it, / would be active on all routes since they all start with /.
The sidebar uses react-i18next for translating navigation labels, supporting both English and Spanish.

Programmatic Navigation

Use the useNavigate hook for programmatic navigation:
import { useNavigate } from "react-router-dom";

function UserList() {
  const navigate = useNavigate();
  
  const handleUserClick = (userId: number) => {
    navigate(`/users/${userId}`);
  };
  
  const handleBack = () => {
    navigate(-1); // Go back
  };
  
  return (
    <div>
      {users.map(user => (
        <div onClick={() => handleUserClick(user.id)}>
          {user.name}
        </div>
      ))}
      <button onClick={handleBack}>Back</button>
    </div>
  );
}

Route Parameters

Access URL parameters with the useParams hook:
import { useParams } from "react-router-dom";
import { useUsersStore } from "@/features/users/usersStore";

function UserDetails() {
  const { id } = useParams<{ id: string }>();
  const users = useUsersStore((state) => state.users);
  
  const user = users.find(u => u.id === Number(id));
  
  if (!user) return <div>User not found</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Route-Based Code Splitting

For larger applications, use lazy loading to split routes into separate bundles:
import { lazy, Suspense } from "react";
import { createBrowserRouter } from "react-router-dom";

// Lazy load route components
const Dashboard = lazy(() => import("../pages/dashboard/Dashboard"));
const Users = lazy(() => import("../pages/dashboard/Users"));
const Settings = lazy(() => import("../pages/dashboard/Settings"));

export const router = createBrowserRouter([
  {
    element: <ProtectedRoute />,
    children: [
      {
        path: "/",
        element: <DashboardLayout />,
        children: [
          {
            index: true,
            element: (
              <Suspense fallback={<div>Loading...</div>}>
                <Dashboard />
              </Suspense>
            ),
          },
          {
            path: "users",
            element: (
              <Suspense fallback={<div>Loading...</div>}>
                <Users />
              </Suspense>
            ),
          },
          // ... more routes
        ],
      },
    ],
  },
]);

Authentication Flow

The authentication flow leverages the routing system:

Login Redirect

After successful login, redirect to the originally requested page:
import { useNavigate, useLocation } from "react-router-dom";
import { useAuthStore } from "@/features/auth/authStore";

function Login() {
  const navigate = useNavigate();
  const location = useLocation();
  const login = useAuthStore((state) => state.login);
  
  const from = location.state?.from?.pathname || "/";
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    await login(username, password);
    navigate(from, { replace: true });
  };
  
  // ...
}
Update ProtectedRoute to save the attempted location:
import { Navigate, Outlet, useLocation } from "react-router-dom";

const ProtectedRoute = () => {
  const user = useAuthStore((state) => state.user);
  const location = useLocation();

  return user ? (
    <Outlet />
  ) : (
    <Navigate to="/login" replace state={{ from: location }} />
  );
};

Best Practices

Use Route Guards

Always protect routes that require authentication with the ProtectedRoute guard.

Nested Layouts

Use nested routes and <Outlet /> to compose layouts instead of duplicating layout code.

NavLink for Menus

Use NavLink in navigation menus to get automatic active styling.

Type-Safe Params

Use TypeScript generics with useParams<{ id: string }>() for type-safe route parameters.

Common Patterns

Adding New Routes

1

Create page component

Create your page component in src/pages/ or src/pages/dashboard/:
// src/pages/dashboard/Reports.tsx
export default function Reports() {
  return <div>Reports Page</div>;
}
2

Add route configuration

Add the route to src/app/router.tsx:
import Reports from "../pages/dashboard/Reports";

// Inside DashboardLayout children:
{ path: "reports", element: <Reports /> }
3

Add navigation link

Add a link in src/shared/components/Sidebar.tsx:
<NavLink to="reports" className={/* ... */}>
  Reports
</NavLink>
4

Test the route

Navigate to /reports and verify the page renders correctly.

Troubleshooting

  • Verify the route path in router.tsx
  • Check for typos in the path
  • Ensure the component is imported correctly
  • Add a catch-all route for debugging:
{ path: "*", element: <div>404 - Route not found</div> }
  • Check your route guards for circular logic
  • Ensure PublicRoute and ProtectedRoute have opposite conditions
  • Verify the auth store is properly initialized
  • Verify <Outlet /> is present in the layout component
  • Check the route hierarchy in router.tsx
  • Ensure the layout route doesn’t have a path prop

Next Steps

Architecture

Learn how routing fits into the overall architecture

State Management

Use auth store for route protection

Components

Build pages with shared components

React Router Docs

Official React Router documentation

Build docs developers (and LLMs) love