Implementing Permission-Based Dynamic Routing in React with RBAC
This article explains how to build a permission‑driven dynamic routing system for management applications using React, React‑Router, and RBAC, covering the conceptual overview, server‑side role mapping, state management with Zustand, and step‑by‑step code examples for route definition, mapping, and rendering.
When developing a management system, permission management is a fundamental requirement, and permission routing plays a crucial role by letting the backend inform the frontend which pages a user can access based on their role.
Preface
RBAC
RBAC (Role‑Based Access Control) is the most common model: users are assigned roles, roles are linked to permissions, and the backend determines a user's accessible pages.
In a typical management system, the role layer greatly improves security and efficiency.
Implementation Idea
Permission routing means the server returns a list of pages a user can view; the frontend renders only those routes.
The flow is:
Because RBAC data is stored on the server, the frontend only needs to consume the permission list and build routes accordingly.
Code Implementation
Below is a React‑based example (Vue is similar). The routing library used is React‑Router.
Route Definition
Define static routes in routes.tsx :
import {
createBrowserRouter,
} from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: (
Hello World
),
},
{
path: "about",
element:
About
,
},
]);
export default router;In the root component:
import * as React from "react";
import { createRoot } from "react-dom/client";
import {
RouterProvider,
} from "react-router-dom";
import router from './routes'
createRoot(document.getElementById("root")).render(
);For dynamic routes, the server returns a structure like:
const mockRes = [
{
// page unique key
key: 'Home',
// page url
url: '/home',
chidrens: [
key: 'User',
url: '/user'
]
}
]Only /home and /home/user should be visible to the user.
State management is handled with a store (Zustand shown here):
/**
* Common state
* @loginStatus whether logged in
* @permissionRoute user route permissions
* @action state update actions
*/
interface CommonStoreProps {
loginStatus: boolean;
permissionRoute: RouteObject[];
action: {
updatePermissionRoute: (routes: RouteObject[]) => void;
updateLoginStatus: (status: boolean) => void;
}
}
export useRoutesStore = create
()((set) => ({
loginStatus: false,
permissionRoute: [],
action: {
updateLoginStatus: (status) => set(state => ({ loginStatus: status })),
updatePermissionRoute: (routes) => set(state => ({ permissionRoute: routes }))
}
}))
export const getLoginStatus = () => useCommonStore(state => state.loginStatus);
export const getPermissionRoute = () => useCommonStore(state => state.permissionRoute);
export const getCommonAction = () => useCommonStore(state => state.action)Map server keys to component modules:
import React, { lazy } from "react";
/** Dynamic routes */
export const asyncRoutes = {
Home: lazy(() => import("@/pages/Home")),
User: lazy(() => import("@/pages/User")),
};
/** Public routes */
export const commonRoutes = {
{
path: "/login",
Component: lazy(() => import("@/pages/login")),
},
};
/** 404 page */
export const notFoundRoutes = {
path: "*",
Component: lazy(() => import("@/pages/notFound")),
};Convert the permission list into React‑Router objects with a recursive factory function:
import { asyncRoutes, commonRoutes } from "../routes";
import BasicLayout from "@/layout";
export const formatPermissionRoutes = (routes: ResponseRoutesType[]): RouteObject[] => {
if (!routes.length) {
return [];
}
return [{
element:
,
path: "/",
children:[
...recursion(routes)
],
}];
};
export const recursion = (
routes: ResponseRoutesType[],
formatRoutesList: RouteObject[] = []
): RouteObject[] => {
if (!routes || !routes.length) {
return [];
}
routes.forEach((item) => {
formatRoutesList.push({
path: item.url,
Component: asyncRoutes[item.key],
children: recursionRoutes(item.chidrens),
});
});
return formatRoutesList;
};Use a custom hook to fetch permissions and build the router:
import { useState, useEffect } from "react";
import { getAction, getPermissionRoute, getLoginStatus } from '@/store'
import { commonRoutes, notFoundRoutes } from './map'
import { formatPermissionRoutes } from './utils';
import { useRoutes } from "react-router-dom";
const useRouter = () => {
const [loading, setLoading] = useState
(false)
const { updatePermissionRoute } = getCommonAction()
const permissionRoute = getCommonPermissionRoute()
const loginStatus = getCommonLoginStatus()
const init = async () => {
setLoading(true);
const permissionRoutes = await fetchRoutes();
const finalRoutes = formatPermissionRoutes(permissionRoutes);
updatePermissionRoute([
...finalRoutes,
notFoundRoutes
]);
}
const Router = () => useRoutes([...permissionRoute, ...commonRoutes]);
useEffect(() => {
if(loginStatus) {
init();
} else {
if(!withCommonRoute()) {
jumoToLogin();
}
}
}, [loginStatus])
}Layout with Ant Design and Outlet for content rendering:
import React, { FC } from "react";
import { Outlet } from "react-router-dom";
import { Layout } from "antd";
const { Content } = Layout;
const BasicLayout: FC = () => {
return (
{/* Sidebar */}
{/* Main content */}
)
}Finally, render the app with suspense and router:
import { Suspense } from 'react'
import { BrowserRouter } from 'react-router-dom'
import useRoutes from './routes'
function App(){
const { loading, Router } = useRoutes()
return (
{loading ? (
loading...
) : (
)}
)
}With these steps, a complete permission‑based dynamic routing system is built using React and React‑Router.
Conclusion
The provided code demonstrates how to leverage React, React‑Router, and RBAC to create a concise yet powerful permission routing solution for management applications.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.