📘 ReactJS

React Router — Routing & Protected Routes

React Router provides client-side routing for React applications. It lets users move between pages without performing a complete browser reload.

S

Site Administrator

📅 Sep 28, 2026 🕒 6 min read 👁 29 views
☆ Save 𝕏 in f ✆
React Router — Routing & Protected Routes

Routing is one of the first places where a small React application becomes a real application. A production application usually has public pages, authenticated pages, nested layouts, route parameters, query parameters, 404 handling, redirects, role-based authorization, and a login flow that remembers the page the user originally requested. This chapter builds those concepts from the basics into a complete example.

What routing actually solves

Without a routing library, an application has to manually decide which component should appear for a particular URL. React Router gives the application a structured way to map URLs to React components while keeping the SPA experience.

flowchart LR
A[Browser URL] --> B[Router]
B --> C[Match route]
C --> D[Render layout]
D --> E[Render page]
E --> F[Update browser history]

Install and project structure

A practical project might be organized like this:

src/
  app/
    App.jsx
  auth/
    AuthContext.jsx
    ProtectedRoute.jsx
  layouts/
    AppLayout.jsx
    AdminLayout.jsx
  pages/
    Home.jsx
    Login.jsx
    Dashboard.jsx
    Orders.jsx
    OrderDetails.jsx
    AdminUsers.jsx
    NotFound.jsx
  main.jsx

The important idea is separation of concerns: route definitions describe navigation, authentication manages identity, and pages contain screen-level behavior.

Basic route configuration

import {
  BrowserRouter,
  Routes,
  Route
} from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/login" element={<Login />} />
        <Route path="/orders" element={<Orders />} />
        <Route path="/orders/:orderId" element={<OrderDetails />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

React Router checks the current URL and renders the element belonging to the matching route.

For navigation inside an SPA, use React Router's Link or NavLink. A normal anchor can cause the browser to perform a full document navigation.

import { Link, NavLink } from "react-router-dom";

<Link to="/orders">Orders</Link>

<NavLink to="/dashboard">
  Dashboard
</NavLink>

NavLink is especially useful for navigation menus because it can expose whether the route is active.

Programmatic navigation

After a successful login, save operation, or workflow completion, the application may need to navigate from code.

import { useNavigate } from "react-router-dom";

function CreateOrder() {
  const navigate = useNavigate();

  async function handleSubmit() {
    const order = await saveOrder();
    navigate(`/orders/${order.id}`);
  }

  return <button onClick={handleSubmit}>Create Order</button>;
}

Route parameters

Route parameters are ideal when the URL identifies a particular resource.

<Route
  path="/orders/:orderId"
  element={<OrderDetails />}
/>

function OrderDetails() {
  const { orderId } = useParams();

  return <h2>Order ID: {orderId}</h2>;
}

For /orders/5001, the component receives orderId = 5001.

Query parameters

Query parameters are commonly used for filters, search, sorting, and pagination.

// URL:
// /orders?status=approved&page=2

const [params] = useSearchParams();

const status = params.get("status");
const page = params.get("page");

This keeps filter state represented in the URL, which makes the page shareable and bookmarkable.

Nested routes and shared layouts

Large applications usually have a layout around a group of screens. Nested routes allow the layout to remain mounted while only the child page changes.

<Route path="/app" element={<AppLayout />}>
  <Route index element={<Dashboard />} />
  <Route path="orders" element={<Orders />} />
  <Route path="orders/:orderId" element={<OrderDetails />} />
  <Route path="settings" element={<Settings />} />
</Route>
import { Outlet } from "react-router-dom";

function AppLayout() {
  return (
    <>
      <Header />
      <Sidebar />
      <main>
        <Outlet />
      </main>
    </>
  );
}

Authentication state

Authentication should be represented separately from routing. A simple auth context can expose the current user and login/logout operations to the rest of the application.

const AuthContext = createContext(null);

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  async function login(credentials) {
    const result = await loginApi(credentials);
    setUser(result.user);
  }

  function logout() {
    setUser(null);
  }

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

function useAuth() {
  return useContext(AuthContext);
}

Protected route

A protected route checks authentication before rendering the private page. If the user is not authenticated, it redirects to login.

function ProtectedRoute({ children }) {
  const { user } = useAuth();
  const location = useLocation();

  if (!user) {
    return (
      <Navigate
        to="/login"
        replace
        state={{ from: location }}
      />
    );
  }

  return children;
}

Complete protected route configuration

<Route path="/login" element={<Login />} />

<Route
  path="/app"
  element={
    <ProtectedRoute>
      <AppLayout />
    </ProtectedRoute>
  }
>
  <Route index element={<Dashboard />} />
  <Route path="orders" element={<Orders />} />
  <Route path="orders/:orderId" element={<OrderDetails />} />
</Route>

Now the entire /app route tree is protected instead of repeating authentication logic in every page.

Login and return to original page

A common production experience is: the user opens /app/orders, is redirected to login, signs in successfully, and is returned to /app/orders.

function Login() {
  const navigate = useNavigate();
  const location = useLocation();
  const { login } = useAuth();

  async function handleLogin(credentials) {
    await login(credentials);

    const from = location.state?.from?.pathname || "/app";
    navigate(from, { replace: true });
  }

  // form omitted
}

Authentication vs authorization

Authentication answers who the user is. Authorization answers what that authenticated user can do. A protected route based only on authentication is not enough for an administrator page.

function AdminRoute({ children }) {
  const { user } = useAuth();

  if (!user) {
    return <Navigate to="/login" replace />;
  }

  if (!user.permissions.includes("users.manage")) {
    return <Forbidden />;
  }

  return children;
}

Role-based routing

Some systems use roles such as Admin, Manager, and Employee. Others use permissions. Permissions are often more flexible because a user can have several independent capabilities.

const user = {
  id: 101,
  name: "Ravi",
  role: "Manager",
  permissions: [
    "orders.read",
    "orders.create",
    "reports.read"
  ]
};

Important security rule

Frontend route protection is a user-interface mechanism, not a security boundary. A user can directly call an API without using your React UI. The backend must independently validate authentication and authorization for every protected operation.

flowchart TD
A[React protected route] --> B[User sees private page]
C[Direct API request] --> D[Backend authentication]
D --> E[Backend authorization]
E --> F[Allow or deny]

404 handling

A catch-all route provides a proper page for unknown URLs.

<Route
  path="*"
  element={<NotFound />}
/>

Complete example output

Imagine the following navigation:

  • User opens /app/orders/5001.
  • Router matches the protected app route and order-detail route.
  • If the user is not logged in, the user is redirected to /login.
  • After successful login, the application returns to /app/orders/5001.
  • The order page reads 5001 from the route parameter.
  • The backend still checks the user's authorization before returning order data.
Browser URL:
http://localhost:5173/app/orders/5001

Visible output:

Order Details
Order ID: 5001
Status: Approved
Customer: ABC Corporation

Common production mistakes

  • Checking authentication only in the UI and not on the API.
  • Duplicating protected-route logic in every component.
  • Using local component state for authentication that disappears unexpectedly.
  • Not preserving the requested URL after login.
  • Using roles everywhere when fine-grained permissions are actually needed.
  • Putting every route in one huge unreadable file.

Interview questions

  • How does React Router work?
  • What is the difference between Link and an anchor?
  • How do you implement protected routes?
  • Authentication vs authorization?
  • How do nested routes and Outlet work?
  • How do you preserve the original URL after login?
  • Why can frontend route protection never replace backend authorization?

Key takeaways

  • Use routing to map URLs to screens.
  • Use nested routes for shared application layouts.
  • Protect private route trees instead of duplicating checks.
  • Separate authentication from authorization.
  • Always enforce authorization on the backend.

Comments & Ratings (0)

Log in or create a student account to comment and rate this article.

Be the first to comment on this article.

Never miss a new post 📬

Get an email whenever a new article, project, cheatsheet or video drops.

✅

ℹ️