Skip to main content
Tambo360 uses React Router v7 for client-side routing. The application implements protected routes, public routes, and automatic redirects based on authentication state.

Overview

Protected Routes

Require authentication to access

Public Routes

Accessible without authentication

Nested Routes

Layout wraps all protected routes

Route Guards

Automatic redirects for auth state

Route Configuration

All routes are defined in a centralized file:

File Location

apps/frontend/src/routes/AppRoutes.tsx

Implementation

AppRoutes.tsx
import { Routes, Route, Navigate, Outlet } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'
import ProtectedRoute from './ProtectedRoute'
import PublicRoute from './PublicRoute'
import CheckEstablishment from './CheckEstablishment'
import Layout from '../components/layout/Layout'
import LoadingSpinner from '../components/layout/LoadingSpinner'

// Page imports
import Dashboard from '../pages/Dashboard'
import Login from '../pages/Login'
import Register from '../pages/Register'
import Produccion from '../pages/Produccion'
import TamboEngine from '../pages/TamboEngine'
import Perfil from '../pages/Perfil'
import ResetPassword from '../pages/ResetPassword'
import VerifyUser from '../pages/VerifyUser'
import BatchDetails from '../pages/BatchDetails'
import Establishment from '../pages/Establishment'

import { ROUTES } from '../constants/routes'

export const AppRoutes = () => {
  const { loading } = useAuth()

  if (loading) return <LoadingSpinner />

  return (
    <Routes>
      {/* PUBLIC ROUTES */}
      <Route
        element={
          <PublicRoute>
            <Outlet />
          </PublicRoute>
        }
      >
        <Route path={ROUTES.LOGIN} element={<Login />} />
        <Route path={ROUTES.REGISTER} element={<Register />} />
        <Route path="/auth/reset-password" element={<ResetPassword />} />
      </Route>

      <Route path="/auth/verify" element={<VerifyUser />} />
      <Route path="/establecimiento" element={<Establishment />} />

      {/* PROTECTED ROUTES */}
      <Route
        element={
          <ProtectedRoute>
            <CheckEstablishment>
              <Layout />
            </CheckEstablishment>
          </ProtectedRoute>
        }
      >
        {/* Redirect root to dashboard */}
        <Route index element={<Navigate to={ROUTES.DASHBOARD} replace />} />

        <Route path={ROUTES.DASHBOARD} element={<Dashboard />} />
        <Route path="/produccion" element={<Produccion />} />
        <Route path="/produccion/lote/:loteId" element={<BatchDetails />} />
        <Route path="/tambo-engine" element={<TamboEngine />} />
        <Route path="/perfil" element={<Perfil />} />
      </Route>

      {/* Catch-all: redirect unknown routes */}
      <Route path="*" element={<Navigate to="/" replace />} />
    </Routes>
  )
}

export default AppRoutes
Location: apps/frontend/src/routes/AppRoutes.tsx

Route Constants

Route paths are defined as constants for consistency:
routes.ts
export const ROUTES = {
  // Public Routes
  HOME: '/',
  LOGIN: '/login',
  REGISTER: '/register',
  RESET_PASSWORD: '/auth/reset-password',

  // Protected Routes
  DASHBOARD: '/dashboard',
  PRODUCCION: '/produccion',
  MERMAS: '/mermas',
  COSTOS: '/costos',
  REPORTES: '/reportes',
  ALERTAS: '/alertas',
} as const
Location: apps/frontend/src/constants/routes.ts

Usage

import { ROUTES } from '@/src/constants/routes'
import { Link } from 'react-router-dom'

<Link to={ROUTES.DASHBOARD}>Dashboard</Link>
<Link to={ROUTES.PRODUCCION}>Producción</Link>

Protected Routes

Protected routes require authentication and automatically redirect to login if the user is not authenticated.

ProtectedRoute Component

ProtectedRoute.tsx
import { Navigate } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'
import Loading from '@/src/components/layout/Loading'

interface ProtectedRouteProps {
  children: React.ReactNode
}

const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
  const { user, loading } = useAuth()

  // Show loading while checking auth
  if (loading) {
    return <Loading />
  }

  // Redirect to login if not authenticated
  if (!user) {
    return <Navigate to="/login" replace />
  }

  // Render children if authenticated
  return <>{children}</>
}

export default ProtectedRoute
Location: apps/frontend/src/routes/ProtectedRoute.tsx

Usage

Wrap protected content with ProtectedRoute:
<Route
  element={
    <ProtectedRoute>
      <Layout />
    </ProtectedRoute>
  }
>
  <Route path="/dashboard" element={<Dashboard />} />
  <Route path="/produccion" element={<Produccion />} />
</Route>

Public Routes

Public routes are accessible without authentication and redirect authenticated users to the dashboard.

PublicRoute Component

PublicRoute.tsx
import { Navigate } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'

interface PublicRouteProps {
  children: React.ReactNode
}

const PublicRoute = ({ children }: PublicRouteProps) => {
  const { user } = useAuth()

  // Redirect to dashboard if already authenticated
  if (user) {
    return <Navigate to="/dashboard" replace />
  }

  // Render children if not authenticated
  return <>{children}</>
}

export default PublicRoute
Location: apps/frontend/src/routes/PublicRoute.tsx

Usage

<Route
  element={
    <PublicRoute>
      <Outlet />
    </PublicRoute>
  }
>
  <Route path="/login" element={<Login />} />
  <Route path="/register" element={<Register />} />
</Route>

Nested Routes with Layout

All protected routes render inside the Layout component using <Outlet />.

How It Works

<Route
  element={
    <ProtectedRoute>
      <Layout />  {/* Layout contains <Outlet /> */}
    </ProtectedRoute>
  }
>
  {/* These render inside Layout's <Outlet /> */}
  <Route path="/dashboard" element={<Dashboard />} />
  <Route path="/produccion" element={<Produccion />} />
</Route>
The Layout component includes <Outlet /> to render child routes:
Layout.tsx
import { Outlet } from 'react-router-dom'

export default function Layout() {
  return (
    <div className="flex h-screen">
      <Sidebar />
      <div className="flex-1">
        <Navbar />
        <main>
          <Outlet />  {/* Child routes render here */}
        </main>
      </div>
    </div>
  )
}
Location: apps/frontend/src/components/layout/Layout.tsx:57 Use React Router’s Link component for internal navigation:
import { Link } from 'react-router-dom'

<Link to="/dashboard">Go to Dashboard</Link>
<Link to="/produccion">Producción</Link>
<Link to={`/produccion/lote/${batchId}`}>Ver Lote</Link>

Programmatic Navigation

Use useNavigate hook for navigation in event handlers:
import { useNavigate } from 'react-router-dom'

const MyComponent = () => {
  const navigate = useNavigate()

  const handleSubmit = async (data) => {
    await createBatch(data)
    navigate('/produccion')  // Navigate after success
  }

  const goBack = () => {
    navigate(-1)  // Go back one page
  }

  return (
    <button onClick={() => navigate('/dashboard')}>
      Go to Dashboard
    </button>
  )
}
Pass state to the next route:
import { useNavigate, useLocation } from 'react-router-dom'

// Navigate with state
const navigate = useNavigate()
navigate('/produccion', { 
  state: { fromDashboard: true } 
})

// Access state in destination component
const location = useLocation()
const fromDashboard = location.state?.fromDashboard

Route Parameters

Extract dynamic parameters from URLs:

Defining Routes with Parameters

<Route path="/produccion/lote/:loteId" element={<BatchDetails />} />

Accessing Parameters

BatchDetails.tsx
import { useParams } from 'react-router-dom'

const BatchDetails = () => {
  const { loteId } = useParams<{ loteId: string }>()

  return <div>Viewing batch: {loteId}</div>
}

Multiple Parameters

<Route path="/produccion/:year/:month/:batchId" element={<BatchDetails />} />

const BatchDetails = () => {
  const { year, month, batchId } = useParams()
}

Query Parameters

Read and set URL query parameters:

Reading Query Params

import { useSearchParams } from 'react-router-dom'

const MyComponent = () => {
  const [searchParams] = useSearchParams()
  
  const filter = searchParams.get('filter')  // ?filter=active
  const page = searchParams.get('page')      // ?page=2

  return <div>Filter: {filter}, Page: {page}</div>
}

Setting Query Params

import { useSearchParams } from 'react-router-dom'

const MyComponent = () => {
  const [searchParams, setSearchParams] = useSearchParams()

  const updateFilter = (filter: string) => {
    setSearchParams({ filter, page: '1' })
    // URL becomes: ?filter=active&page=1
  }

  return (
    <button onClick={() => updateFilter('active')}>
      Filter Active
    </button>
  )
}

Active Route Styling

Highlight the current active route in navigation:
AppSidebar.tsx
import { Link, useLocation } from 'react-router-dom'

const AppSidebar = () => {
  const location = useLocation()

  const menuItems = [
    { title: 'Dashboard', url: '/dashboard' },
    { title: 'Producción', url: '/produccion' },
    { title: 'TamboEngine', url: '/tambo-engine' },
  ]

  return (
    <nav>
      {menuItems.map((item) => {
        const isActive = location.pathname.startsWith(item.url)
        
        return (
          <Link
            key={item.url}
            to={item.url}
            className={isActive ? 'bg-[#D7ECAF] text-[#669213]' : 'text-gray-400'}
          >
            {item.title}
          </Link>
        )
      })}
    </nav>
  )
}
Location: apps/frontend/src/components/layout/AppSidebar.tsx:75

Redirects

Simple Redirect

import { Navigate } from 'react-router-dom'

<Route index element={<Navigate to="/dashboard" replace />} />
<Route path="*" element={<Navigate to="/" replace />} />

Conditional Redirect

const MyComponent = () => {
  const { user } = useAuth()

  if (!user) {
    return <Navigate to="/login" replace />
  }

  return <div>Protected content</div>
}

Programmatic Redirect

import { useNavigate } from 'react-router-dom'

const MyComponent = () => {
  const navigate = useNavigate()

  useEffect(() => {
    if (!isValid) {
      navigate('/error', { replace: true })
    }
  }, [isValid])
}

CheckEstablishment Guard

Custom route guard that ensures users have an establishment configured:
CheckEstablishment.tsx
import { Navigate } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'

interface CheckEstablishmentProps {
  children: React.ReactNode
}

const CheckEstablishment = ({ children }: CheckEstablishmentProps) => {
  const { user } = useAuth()

  // Redirect to establishment setup if user has no establishment
  if (user && (!user.establecimientos || user.establecimientos.length === 0)) {
    return <Navigate to="/establecimiento" replace />
  }

  return <>{children}</>
}

export default CheckEstablishment
Location: apps/frontend/src/routes/CheckEstablishment.tsx

Route Loading States

Show loading UI while checking authentication:
AppRoutes.tsx
import { useAuth } from '../context/AuthContext'
import LoadingSpinner from '../components/layout/LoadingSpinner'

export const AppRoutes = () => {
  const { loading } = useAuth()

  // Show loading spinner while checking auth state
  if (loading) return <LoadingSpinner />

  return <Routes>...</Routes>
}
Location: apps/frontend/src/routes/AppRoutes.tsx:21

Error Boundaries

Handle routing errors gracefully:
import { useRouteError } from 'react-router-dom'

export const ErrorBoundary = () => {
  const error = useRouteError()

  return (
    <div>
      <h1>Oops!</h1>
      <p>Sorry, an unexpected error has occurred.</p>
      <p>
        <i>{error.statusText || error.message}</i>
      </p>
    </div>
  )
}

Best Practices

Use route constants

Define all routes in constants/routes.ts for consistency

Always use replace

Use replace prop on redirects to avoid back button issues

Show loading states

Display loading UI while checking authentication

Catch-all route

Always include a * route to handle 404s
Never hardcode route paths. Always use constants from ROUTES object.
React Router v7 uses the same API as v6. Most migration guides for v6 apply to v7.

Route Structure Summary

Public Routes (no auth required):
  /login              - Login page
  /register           - Registration page
  /auth/reset-password - Password reset
  /auth/verify        - Email verification

Setup Routes (auth required, no establishment check):
  /establecimiento    - Establishment setup

Protected Routes (auth + establishment required):
  /                   - Redirects to /dashboard
  /dashboard          - Main dashboard
  /produccion         - Production management
  /produccion/lote/:id - Batch details
  /tambo-engine       - TamboEngine alerts
  /perfil             - User profile

Catch-all:
  *                   - Redirects to /

Auth Context

Authentication state used by route guards

Layout Components

Layout component that wraps protected routes

Navigation Components

Sidebar and navigation implementation

External Resources

React Router v7 Docs

Official React Router documentation

Build docs developers (and LLMs) love