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
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:
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
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
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:
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
Navigation
Link Component
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 >
)
}
Navigation with State
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
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:
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:
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:
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