Skip to main content
MKing Admin uses React Router v6 for client-side routing with a permission-based protection system and animated page transitions.

Routing Architecture

AppRouter

Main routing configuration with all application routes

ProtectedRoute

Permission-based route protection wrapper

MenuSidebar

Central configuration for routes, permissions, and menu items

AppRouter Component

The main router component handles all application routing, authentication checks, and page transitions.
src/router/AppRouter.tsx
import React, { useEffect } from "react";
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
import { AnimatePresence, motion } from "framer-motion";
import { LoginPage } from "../auth/pages";
import { ProtectedRoute } from "./ProtectedRoute";
import { MenuSidebar } from "../core/MenuSidebar";
import { getInfoUser } from "../services/service";
import { useAuthStore } from "../store/authStore";

// Auth redirect component
const AuthRedirect = () => {
  const isLoggedIn = !!localStorage.getItem("token");
  if (isLoggedIn) {
    return <Navigate to="/home" replace />;
  } else {
    return <Navigate to="/login" replace />;
  }
};

// Page transition wrapper
const PageTransition = ({ children }: { children: React.ReactNode }) => (
  <motion.div
    initial={{ opacity: 0, x: 20 }}
    animate={{ opacity: 1, x: 0 }}
    exit={{ opacity: 0, x: -20 }}
    transition={{ duration: 0.3 }}
    style={{ width: "100%", height: "100%" }}
  >
    {children}
  </motion.div>
);

export const AppRouter = () => {
  const location = useLocation();
  const { permissions, setAuth, isAuthenticated } = useAuthStore();

  // Fetch user info on mount if token exists
  useEffect(() => {
    const token = localStorage.getItem("token");
    if (token && !isAuthenticated) {
      getInfoUser()
        .then((res) => {
          const user = res.data;
          const perms = user.roles.flatMap((role: any) =>
            role.permissions.map((p: any) => p.slug)
          );
          setAuth(user, perms);
        })
        .catch((err) => {
          console.error("Error fetching user info in router:", err);
        });
    }
  }, [isAuthenticated, setAuth]);

  // Filter routes based on permissions
  const routeList = MenuSidebar.filter(
    (item) => permissions.includes(item.permission) || item.permission === "auth"
  );

  // Generate route components from MenuSidebar
  const routeMaps = routeList.map(({ id, page, permission, path }: any) => (
    <Route
      key={id}
      element={<ProtectedRoute isAllowed={permissions.includes(permission) || permission === "auth"} />}
    >
      <Route path={path} element={<PageTransition>{page}</PageTransition>} />
    </Route>
  ));

  return (
    <AnimatePresence mode="wait">
      <Routes location={location} key={location.pathname}>
        <Route path="/" element={<PageTransition><HomePage /></PageTransition>} />
        <Route path="/login" element={<PageTransition><LoginPage /></PageTransition>} />
        <Route path="/home" element={<PageTransition><HomePage /></PageTransition>} />
        <Route path="/catalogs" element={<PageTransition><CatalogPage /></PageTransition>} />
        <Route path="/quotations/create" element={<PageTransition><CreateAdminQuotation /></PageTransition>} />
        <Route path="/roles/create" element={<PageTransition><CreateRole /></PageTransition>} />
        <Route path="/roles/edit/:id" element={<PageTransition><CreateRole /></PageTransition>} />
        <Route path="/employees" element={<PageTransition><EmployeesPage /></PageTransition>} />
        <Route path="/users" element={<PageTransition><UsersPage /></PageTransition>} />
        <Route path="/calendar" element={<PageTransition><Calendar /></PageTransition>} />
        <Route path="/product" element={<PageTransition><ProductPage /></PageTransition>} />
        <Route index element={<AuthRedirect />} />
        <Route path="*" element={<AuthRedirect />} />
        {routeMaps}
      </Routes>
    </AnimatePresence>
  );
};

Key Features

When the router mounts, it checks for a token in localStorage. If a token exists but the user isn’t authenticated in the store, it fetches user information:
useEffect(() => {
  const token = localStorage.getItem("token");
  if (token && !isAuthenticated) {
    getInfoUser().then((res) => {
      const user = res.data;
      const perms = user.roles.flatMap((role: any) =>
        role.permissions.map((p: any) => p.slug)
      );
      setAuth(user, perms);
    });
  }
}, [isAuthenticated, setAuth]);
This ensures users stay logged in across page refreshes.
Routes are dynamically generated from the MenuSidebar configuration:
const routeList = MenuSidebar.filter(
  (item) => permissions.includes(item.permission) || item.permission === "auth"
);

const routeMaps = routeList.map(({ id, page, permission, path }) => (
  <Route
    key={id}
    element={<ProtectedRoute isAllowed={permissions.includes(permission)} />}
  >
    <Route path={path} element={<PageTransition>{page}</PageTransition>} />
  </Route>
));
This centralizes route configuration and ensures the sidebar and routes stay in sync.
Uses Framer Motion for smooth page transitions:
const PageTransition = ({ children }) => (
  <motion.div
    initial={{ opacity: 0, x: 20 }}
    animate={{ opacity: 1, x: 0 }}
    exit={{ opacity: 0, x: -20 }}
    transition={{ duration: 0.3 }}
  >
    {children}
  </motion.div>
);
Pages slide in from the right and slide out to the left on navigation.
The AuthRedirect component handles fallback routing:
const AuthRedirect = () => {
  const isLoggedIn = !!localStorage.getItem("token");
  if (isLoggedIn) {
    return <Navigate to="/home" replace />;
  } else {
    return <Navigate to="/login" replace />;
  }
};
  • Authenticated users accessing / or unknown routes → redirected to /home
  • Unauthenticated users → redirected to /login

ProtectedRoute Component

A simple but powerful wrapper that protects routes based on permissions.
src/router/ProtectedRoute.tsx
import { Navigate, Outlet } from "react-router-dom";

export const ProtectedRoute = ({
  isAllowed,
  redirectTo = "/home",
  children,
}: any) => {
  if (!isAllowed) {
    return <Navigate to={redirectTo} replace />;
  }
  return children ? children : <Outlet />;
};

How It Works

1

Check Permission

The isAllowed prop determines if the user has permission to access the route.
2

Redirect if Unauthorized

If isAllowed is false, the user is redirected to the redirectTo path (defaults to /home).
3

Render Route

If authorized, it renders either the children prop or an <Outlet /> for nested routes.

Usage Examples

<Route
  element={
    <ProtectedRoute isAllowed={permissions.includes('users.view')} />
  }
>
  <Route path="/users" element={<UsersPage />} />
</Route>
The MenuSidebar array serves as the single source of truth for routes, permissions, and navigation.
src/core/MenuSidebar.tsx
import { HomePage } from "../home/pages/HomePage";
import { ProductPage } from "../pages/Product/ProductPage";
import HomeIcon from "@mui/icons-material/Home";
import ViewListIcon from "@mui/icons-material/ViewList";

export const MenuSidebar = [
  {
    id: 1,
    title: "Principal",
    text_menu: "Home",
    icon: <HomeIcon />,
    path: "/home",
    permission: "home.panel",
    page: <HomePage />
  },
  {
    id: 2,
    title: "Productos",
    text_menu: "Productos",
    icon: <ViewListIcon />,
    path: "/product",
    permission: "products.view",
    page: <ProductPage />
  },
  // ... more menu items
];
id
number
required
Unique identifier for the menu item
title
string
required
Page title displayed in the UI
text_menu
string
required
Text shown in the sidebar menu
icon
ReactNode
required
MUI icon component for the menu item
path
string
required
Route path (e.g., /product, /users)
permission
string
required
Permission slug required to access this route (e.g., "products.view")
page
ReactNode
required
React component to render for this route

Permission System

Permissions are managed through a role-based access control (RBAC) system.

Permission Flow

1

User Login

User authenticates and receives a JWT token stored in localStorage.
2

Fetch User Data

AppRouter fetches user information including roles and permissions.
3

Extract Permissions

Permissions are extracted from user roles and stored in Zustand:
const perms = user.roles.flatMap((role: any) =>
  role.permissions.map((p: any) => p.slug)
);
setAuth(user, perms);
4

Route Filtering

Routes are filtered based on user permissions:
const routeList = MenuSidebar.filter(
  (item) => permissions.includes(item.permission)
);
5

Access Control

ProtectedRoute enforces access control on route navigation.

Permission Naming Convention

Permissions follow the pattern: resource.action
home.panel         # Access to home dashboard
products.view      # View products
products.create    # Create products
products.edit      # Edit products
products.delete    # Delete products
users.view         # View users
users.manage       # Manage users
roles.view         # View roles
roles.edit         # Edit roles
catalogs.manage    # Manage catalogs

Route Parameters

React Router v6 supports dynamic route parameters.

Defining Parameterized Routes

<Route path="/roles/edit/:id" element={<CreateRole />} />

Accessing Route Parameters

import { useParams } from 'react-router-dom';

function CreateRole() {
  const { id } = useParams();
  
  useEffect(() => {
    if (id) {
      // Edit mode - fetch role data
      fetchRole(id);
    }
  }, [id]);
  
  return <div>Role ID: {id}</div>;
}

Programmatic Navigation

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

function MyComponent() {
  const navigate = useNavigate();

  const goToProducts = () => {
    navigate('/product');
  };

  const goBack = () => {
    navigate(-1);
  };

  return (
    <>
      <button onClick={goToProducts}>View Products</button>
      <button onClick={goBack}>Go Back</button>
    </>
  );
}

Best Practices

Centralize Route Config

Use MenuSidebar as the single source of truth for routes and permissions.

Always Use Replace

Use replace prop on redirects to prevent back button issues.

Permission-First

Check permissions before rendering components, not inside them.

Handle 404s

Always include a catch-all route for unmatched paths.

Common Patterns

Conditional Routing Based on Auth

function App() {
  const isAuthenticated = useAuthStore((state) => state.isAuthenticated);

  return (
    <Routes>
      <Route path="/login" element={
        isAuthenticated ? <Navigate to="/home" /> : <LoginPage />
      } />
      <Route path="/home" element={
        isAuthenticated ? <HomePage /> : <Navigate to="/login" />
      } />
    </Routes>
  );
}

Loading State During Auth Check

function AppRouter() {
  const [isLoading, setIsLoading] = useState(true);
  const { setAuth, isAuthenticated } = useAuthStore();

  useEffect(() => {
    const token = localStorage.getItem("token");
    if (token) {
      getInfoUser()
        .then((res) => {
          // Set auth...
        })
        .finally(() => setIsLoading(false));
    } else {
      setIsLoading(false);
    }
  }, []);

  if (isLoading) {
    return <LoadingSpinner />;
  }

  return <Routes>...</Routes>;
}

Next Steps

Project Structure

Understand the overall project organization

State Management

Learn about Redux and Zustand state management

Build docs developers (and LLMs) love