🔐 Protected Routes in React (Traditional vs Modern)
Protected routes restrict access to specific pages unless a user is authenticated. Below is a structured explanation showing the older repetitive pattern and the cleaner modern architecture using Context and layout-based routing.
⚠️ Traditional Way (Component-Level Protection)
In the traditional approach, authentication logic is written directly inside multiple components. Each component checks whether an authentication token exists in localStorage and redirects if not found.
Problems with this approach:
- ❌ Repeated logic across components
- ❌ Harder to maintain
- ❌ Authentication logic scattered throughout the app
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
const Component = () => {
const navigate = useNavigate();
useEffect(() => {
if (!localStorage.getItem("authToken")) {
navigate("/auth/login");
}
}, []);
return <div>Protected Content</div>;
};
export default Component;
This works, but it does not scale well as the application grows.
🚀 Modern Way (Centralized Protected Routing)
The modern structure centralizes authentication using:
- 🧠 Context API for global auth state
- 🧩 Layout-based routing
- 🛡️ A dedicated Protected Route wrapper
1️⃣ Create Auth Context
The Auth Context stores authentication state globally so all components can access it without repeating logic.
File: context/AuthContext.jsx
import { createContext, useContext, useState, useEffect } from "react";
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState(null);
useEffect(() => {
getToken();
}, []);
const getToken = () => {
const token = localStorage.getItem("authToken");
if (token) {
setAuth(JSON.parse(token));
} else {
setAuth(null);
}
};
const contextData = { auth, setAuth };
return (
<AuthContext.Provider value={contextData}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
return useContext(AuthContext);
};
export default AuthContext;
✅ Authentication logic now exists in one place.
2️⃣ Create Root Layout
Layouts allow shared UI like headers and footers while rendering nested routes using Outlet.
File: layouts/RootLayout.jsx
import { Outlet } from "react-router-dom";
export const RootLayout = () => {
return (
<>
{/* Header */}
<Outlet />
{/* Footer */}
</>
);
};
3️⃣ Wrap Layout With Auth Provider
Wrapping the layout ensures authentication is available everywhere in the app.
import { Outlet } from "react-router-dom";
import { AuthProvider } from "../context/AuthContext";
export const RootLayout = () => {
return (
<AuthProvider>
{/* Header */}
<Outlet />
{/* Footer */}
</AuthProvider>
);
};
4️⃣ Configure Router
Define the routing structure using nested routes.
File: App.jsx
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { RootLayout } from "./layouts/RootLayout";
import ProfileElement from "./components/ProfileElement";
import DashboardElement from "./components/DashboardElement";
const router = createBrowserRouter([
{
element: <RootLayout />,
children: [
{
path: "/profile",
element: <ProfileElement />
},
{
path: "/dashboard",
element: <DashboardElement />
}
]
}
]);
const App = () => {
return <RouterProvider router={router} />;
};
export default App;
5️⃣ Create Protected Routes Wrapper
This component decides whether a route should be accessible.
File: components/ProtectedRoutes.jsx
import { Outlet, Navigate } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
export const ProtectedRoutes = () => {
const { auth } = useAuth();
return auth ? <Outlet /> : <Navigate to="/auth/login" />;
};
🛡️ If authentication is missing, users are redirected to the login page.
6️⃣ Apply Protected Routes
Wrap only the routes that require authentication.
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { RootLayout } from "./layouts/RootLayout";
import { ProtectedRoutes } from "./components/ProtectedRoutes";
import ProfileElement from "./components/ProfileElement";
import DashboardElement from "./components/DashboardElement";
const router = createBrowserRouter([
{
element: <RootLayout />,
children: [
{
element: <ProtectedRoutes />,
children: [
{
path: "/profile",
element: <ProfileElement />
},
{
path: "/dashboard",
element: <DashboardElement />
}
]
}
]
}
]);
const App = () => {
return <RouterProvider router={router} />;
};
export default App;
📊 Flow Summary
- 1️⃣ Token loads from localStorage inside AuthProvider
- 2️⃣ RootLayout wraps the entire app
- 3️⃣ ProtectedRoutes checks authentication
- 4️⃣ Unauthorized users are redirected
- 5️⃣ Authorized users access protected pages
✨ This structure keeps authentication clean, reusable, and scalable for larger applications.
