# How to Implement Protected Routes in React JS: A Complete Guide with Code Examples

> Learn to implement protected routes in React JS using React Router v6 and custom authentication context. Secure your application by conditionally rendering content or redirecting unauthenticated users effectively.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: how-to-guide
- Published: 2026-02-16

---

**The most effective way to implement protected routes in React JS is to combine React Router v6 with a custom authentication context that conditionally renders either the protected content via `<Outlet />` or redirects unauthenticated users using `<Navigate />`.**

When building secure React applications, implementing protected routes in React JS ensures that sensitive pages remain inaccessible to unauthenticated users. This guide leverages patterns found in the `facebook/react` repository to demonstrate how React's core Context API and modern routing libraries work together to create robust authentication guards.

## Understanding the Architecture of Protected Routes in React JS

The foundation of any protected route implementation relies on two core concepts: centralized authentication state and conditional route rendering. According to the React source code in [`packages/react/src/ReactContext.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactContext.js), the Context API provides a first-class primitive for sharing state across the component tree without prop-drilling.

### The Auth Provider Pattern

An authentication provider wraps your application and exposes the current user session to any component that needs it. This pattern uses `React.createContext` and a custom hook—similar to the internal implementation found in the React repository—to manage login state globally.

### Route Definitions with React Router

React Router v6 uses a declarative routing system where routes are defined as JSX elements. The fixture at [`fixtures/nesting/src/modern/App.js`](https://github.com/facebook/react/blob/main/fixtures/nesting/src/modern/App.js) in the React repository demonstrates how nested routes and layout patterns work, which directly informs how we structure protected route hierarchies.

## Building the ProtectedRoute Component

To guard specific pages, you need a wrapper component that checks authentication status before rendering child routes. This component lives in your route configuration and decides whether to show the requested page or redirect to a login screen.

### Step 1: Create the Authentication Context

First, establish the context that will hold your authentication state. This implementation mirrors the core patterns found in [`packages/react/src/ReactContext.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactContext.js):

```typescript
// src/auth/AuthContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';

type AuthState = {
  isAuthenticated: boolean;
  user?: { id: string; name: string; role?: string };
  login: (user: AuthState['user']) => void;
  logout: () => void;
};

const AuthContext = createContext<AuthState | undefined>(undefined);

export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const [user, setUser] = useState<AuthState['user']>(undefined);

  const login = (newUser: AuthState['user']) => setUser(newUser);
  const logout = () => setUser(undefined);

  const value = {
    isAuthenticated: Boolean(user),
    user,
    login,
    logout,
  };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};

export const useAuth = () => {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used within AuthProvider');
  return ctx;
};

```

### Step 2: Implement the Route Guard

Next, create the `ProtectedRoute` component that uses the `useAuth` hook and React Router's navigation primitives:

```typescript
// src/auth/ProtectedRoute.tsx
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { useAuth } from './AuthContext';

export const ProtectedRoute = () => {
  const { isAuthenticated } = useAuth();
  const location = useLocation();

  return isAuthenticated ? (
    <Outlet />
  ) : (
    <Navigate to="/login" replace state={{ from: location }} />
  );
};

```

This pattern leverages `<Outlet />` to render child routes when authentication succeeds, or `<Navigate />` to redirect unauthenticated users—exactly as recommended by the react-router documentation and compatible with the nesting patterns shown in [`fixtures/nesting/src/modern/App.js`](https://github.com/facebook/react/blob/main/fixtures/nesting/src/modern/App.js).

## Wiring Everything Together in Your React JS Application

With the context and guard components ready, assemble your application by wrapping the router with the `AuthProvider` and nesting protected routes under the `ProtectedRoute` element:

```tsx
// src/App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { AuthProvider } from './auth/AuthContext';
import { ProtectedRoute } from './auth/ProtectedRoute';
import HomePage from './pages/HomePage';
import Dashboard from './pages/Dashboard';
import LoginPage from './pages/LoginPage';
import NotFound from './pages/NotFound';

function App() {
  return (
    <AuthProvider>
      <BrowserRouter>
        <Routes>
          {/* Public routes */}
          <Route path="/" element={<HomePage />} />
          <Route path="/login" element={<LoginPage />} />

          {/* Protected layout - guards all child routes */}
          <Route element={<ProtectedRoute />}>
            <Route path="/dashboard" element={<Dashboard />} />
            {/* Add more protected routes here */}
          </Route>

          {/* Catch-all for 404s */}
          <Route path="*" element={<NotFound />} />
        </Routes>
      </BrowserRouter>
    </AuthProvider>
  );
}

export default App;

```

This structure ensures that any route nested under `<Route element={<ProtectedRoute />}>` automatically requires authentication, creating a clean separation between public and private sections of your application.

## Advanced Patterns: Role-Based Protected Routes

For applications requiring granular access control, extend the `ProtectedRoute` pattern to check user roles. This builds upon the same context mechanism found in [`packages/react/src/ReactContext.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactContext.js) but adds authorization logic:

```tsx
// src/auth/RoleProtectedRoute.tsx
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { useAuth } from './AuthContext';

type Props = {
  requiredRole: string;
};

export const RoleProtectedRoute = ({ requiredRole }: Props) => {
  const { isAuthenticated, user } = useAuth();
  const location = useLocation();

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

  if (user?.role !== requiredRole) {
    return <Navigate to="/unauthorized" replace />;
  }

  return <Outlet />;
};

```

Usage in your router:

```tsx
<Route element={<RoleProtectedRoute requiredRole="admin" />}>
  <Route path="/admin" element={<AdminPanel />} />
  <Route path="/settings" element={<SystemSettings />} />
</Route>

```

This pattern allows you to compose multiple layers of protection—first checking authentication, then verifying authorization—while maintaining the declarative routing structure recommended by the React team.

## Key React Source Files That Power This Implementation

Understanding the underlying React mechanisms helps solidify why this architecture works. The following files from the `facebook/react` repository demonstrate the core primitives used in protected route implementations:

- **[`packages/react/src/ReactContext.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactContext.js)** – Contains the core implementation of `ReactContext`, which powers the `AuthProvider` and `useAuth` hook pattern used to share authentication state across the component tree.

- **[`fixtures/nesting/src/modern/App.js`](https://github.com/facebook/react/blob/main/fixtures/nesting/src/modern/App.js)** – Demonstrates the nested routing structure and layout patterns that inform how `ProtectedRoute` wraps child routes using `<Outlet />`.

- **[`fixtures/nesting/src/modern/HomePage.js`](https://github.com/facebook/react/blob/main/fixtures/nesting/src/modern/HomePage.js)** – Provides a reference implementation for public pages that contrast with protected dashboard components.

- **[`fixtures/nesting/src/modern/AboutPage.js`](https://github.com/facebook/react/blob/main/fixtures/nesting/src/modern/AboutPage.js)** – Another example of a public route used in the React repository's routing fixtures.

- **[`README.md`](https://github.com/facebook/react/blob/main/README.md)** (repository root) – Contains high-level documentation that contextualizes how React's component model supports routing and state management patterns.

These files illustrate that protected routes in React JS are not built into the core library, but rather composed using React's fundamental building blocks—context, hooks, and component composition—as demonstrated in the official repository.

## Summary

- **Centralize authentication state** using React Context ([`packages/react/src/ReactContext.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactContext.js)) to avoid prop-drilling and ensure all components access the latest auth status.

- **Create a `ProtectedRoute` component** that consumes the auth context and conditionally renders `<Outlet />` for authenticated users or `<Navigate />` to redirect unauthenticated users to login.

- **Nest protected routes** under a layout route element in your router configuration, following the nesting patterns shown in [`fixtures/nesting/src/modern/App.js`](https://github.com/facebook/react/blob/main/fixtures/nesting/src/modern/App.js).

- **Extend the pattern** for role-based access control by checking user roles within the route guard before rendering the outlet.

- **Preserve the original URL** by passing the current location to the login page via `state`, allowing seamless redirection back to the requested page after authentication.

## Frequently Asked Questions

### How do protected routes in React JS work with server-side rendering?

Protected routes in React JS rely on client-side state by default, which means the server sends the full application bundle before authentication checks occur. For server-side rendering (SSR) with frameworks like Next.js or Remix, you should verify the session in the server loader or `getServerSideProps` function before sending HTML, redirecting at the HTTP level rather than the component level to prevent flash-of-unauthenticated-content (FOUC).

### Can I implement protected routes in React JS without React Router?

Yes, you can implement protected routes in React JS without React Router by using conditional rendering based on your auth state. Store the authentication status in a global context (as implemented in [`packages/react/src/ReactContext.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactContext.js)) and conditionally return either the protected component or a redirect/login component based on `isAuthenticated`. However, for browser-based navigation, React Router provides cleaner URL management and history integration than manual conditional rendering.

### How do I handle token expiration in protected routes?

Handle token expiration by adding a side effect in your `ProtectedRoute` component or auth context that validates the token before rendering. You can intercept the authentication check to verify token expiry (checking the `exp` claim in JWT) and automatically call the `logout` function to clear the invalid session, triggering the `<Navigate to="/login" />` behavior. For a more robust solution, implement an Axios interceptor or fetch middleware that catches 401 responses and redirects globally.

### What is the difference between ProtectedRoute and PrivateRoute?

**ProtectedRoute** and **PrivateRoute** are naming conventions for the same pattern—both guard routes requiring authentication. In the React community, **ProtectedRoute** has become the standard terminology in React Router v6 documentation, while **PrivateRoute** was more common in v5 when using the `render` prop pattern. Functionally, they perform identical checks: verifying `isAuthenticated` and either rendering the route or redirecting to a login page.