Frontend Development 10 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Implementing Permission-Based Dynamic Routing in React with RBAC

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.

frontendReactReact RouterRBACZustandDynamic RoutesPermission Routing
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.