Using Multiple Routers with React Router v6 to Build a Multi-Section Application

Using Multiple Routers with React Router v6 to Build a Multi-Section Application

When building large-scale web applications, one common pattern is to split different parts of the app (e.g., public-facing pages, dashboards, admin areas) into their modules or sections. With React Router v6, you can use multiple routers and lazy-load routes to enhance both modularity and performance.

In this post, we’ll explore how to set up an app with multiple routers that feel like independent applications for different sections, while sharing a common structure. We’ll also handle lazy-loaded routes, protected pages, and error handling for a robust setup.

The RouteConfig Type

Here’s the type definition for our routes, which we’ll use to configure different sections of the app:

export type RouteConfig = {
    path: string;
    exact?: boolean;
    title?: string;
    component: ComponentType;
    protected?: boolean;
    userRole: UserRole;
};

This structure lets you define whether routes are protected, what user role has access, and how to lazy-load components for performance optimization.

Setting Up Lazy-Loaded Public Routes

Let’s take a look at how to define the public routes for the guest-facing section of your app. Each route is lazily loaded and can be protected based on the user's role.

import { lazy } from 'react';
import { RouteConfig } from 'types';

const NotFound = lazy(() => import('../pages/NotFound'));
const HomePage = lazy(() => import('../pages/Home'));
const SignUp = lazy(() => import('../pages/SignUp'));

const publicRoutes: RouteConfig[] = [
    { path: '/', component: HomePage, userRole: 'guest' },
    { path: '/signup', component: SignUp, userRole: 'guest' },
    { path: '/*', component: NotFound, userRole: 'guest' }
];

export default publicRoutes;

Building the Router for the Public Section

Here’s how you can set up the router for your public section using React Router v6. This router will support lazy-loaded routes, error handling, and protected routes based on the userRole and protected flags defined in the RouteConfig type.

import {
    createBrowserRouter,
    createRoutesFromElements,
    Route,
    RouterProvider
} from 'react-router-dom';
import { Spin } from 'antd';
import { Suspense } from 'react';
import ErrorElement from 'components/global/ErrorElement';
import ProtectedRoute from 'components/routing/ProtectedRoutes';
import ReloadPrompt from 'components/ReloadPrompt';
import publicRoutes from './guest_routes';

const router = createBrowserRouter(
    createRoutesFromElements(
        publicRoutes.map(
            ({ path, component: Component, protected: isProtected, userRole }) => (
                <Route
                    key={path}
                    path={path}
                    errorElement={<ErrorElement />}
                    element={
                        isProtected ? (
                            <ProtectedRoute userRole={userRole}>
                                <Component />
                            </ProtectedRoute>
                        ) : (
                            <Component />
                        )
                    }
                />
            )
        )
    )
);

const GuestEnd = () => {
    return (
        <Suspense fallback={<Spin />}>
            <RouterProvider router={router} />
            <ReloadPrompt />
        </Suspense>
    );
};

export default GuestEnd;

Breaking It Down:

  • Lazy Loading with Suspense: All components are lazy-loaded using Suspense to show a loading spinner (<Spin /> from Ant Design) while routes are being fetched. This improves performance by loading only the necessary routes when needed.

  • Error Handling: The errorElement prop ensures that if there’s an issue loading a route, the user will see an appropriate error message through the ErrorElement component.

  • Protected Routes: The ProtectedRoute component wraps around the routes Component if the route is marked as protected. It ensures that only users with the appropriate role can access certain pages.

Combining Sections Together

Now that we have a router for the guest/public section, you can combine it with other sections (like a dashboard or admin area) in the main App.tsx file.

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import GuestEnd from './routers/GuestEnd';
import DashboardRouter from './routers/DashboardRouter';
import AdminRouter from './routers/AdminRouter';

function App() {
  return (
    <Router>
      <Routes>
        {/* Guest Routes */}
        <Route path="/*" element={<GuestEnd />} />

        {/* Dashboard Routes */}
        <Route path="/dashboard/*" element={<DashboardRouter />} />

        {/* Admin Routes */}
        <Route path="/admin/*" element={<AdminRouter />} />
      </Routes>
    </Router>
  );
}

export default App;

This setup allows for different sections of your application (guest, dashboard, admin) to be lazily loaded and handled separately while still being part of the same application.

Conclusion

Using React Router v6 with lazy loading, protected routes, and error handling allows you to build complex, modular web applications that feel like multiple apps. By splitting your routes into different sections, you can improve performance, enhance security, and maintain a cleaner, more scalable codebase. This approach is perfect for apps with distinct user roles and sections, such as guest areas, user dashboards, and admin panels.

By leveraging tools like Ant Design for user experience and React Router’s flexible configuration, you can keep your app efficient and organized. Happy coding!