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 usingSuspense
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 theErrorElement
component.Protected Routes: The
ProtectedRoute
component wraps around the routesComponent
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!