Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Protected Routes: Login-Gated Pages in React

Protected Routes in React Router

~12 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

Our login button from chapter 11 has had no real consequence so far. Now let's build a proper login page AND an account area only reachable for logged-in users – a "protected route".

The principle: a guard component

React Router has no built-in "protected route" concept – the pattern is instead a completely normal, self-built component that checks whether the user is logged in and, depending on the result, renders either the actual page OR a redirect to the login page.

Creating components/ProtectedRoute.jsx

src/components/ProtectedRoute.jsx
import { Navigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';

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

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

  return children;
}

export default ProtectedRoute;

<Navigate to="/login" replace /> is the declarative way to trigger a redirect in JSX (the counterpart to useNavigate() from chapter 18, but as a component instead of a function). replace replaces the current browser history entry instead of adding a new one – without replace, the "back" button would take the user right back to the protected (and instantly re-denied) page.

Creating pages/LoginPage.jsx

A real login form that redirects to the account page after a successful "login":

src/pages/LoginPage.jsx
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';

function LoginPage() {
  const [username, setUsername] = useState('');
  const { login } = useAuth();
  const navigate = useNavigate();

  function handleSubmit(event) {
    event.preventDefault();
    if (username.trim() === '') {
      return;
    }
    login(username);
    navigate('/account');
  }

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="username">Username</label>
      <input
        id="username"
        value={username}
        onChange={(event) => setUsername(event.target.value)}
      />
      <button type="submit">Log in</button>
    </form>
  );
}

export default LoginPage;

Creating pages/AccountPage.jsx (the protected page)

src/pages/AccountPage.jsx
import { useAuth } from '../context/AuthContext';

function AccountPage() {
  const { user, logout } = useAuth();

  return (
    <div>
      <h2>Welcome back, {user.username}!</h2>
      <p>This is your personal account area.</p>
      <button onClick={logout}>Log out</button>
    </div>
  );
}

export default AccountPage;

App.jsx: adding the new routes

// New imports:
import LoginPage from './pages/LoginPage';
import AccountPage from './pages/AccountPage';
import ProtectedRoute from './components/ProtectedRoute';

// New routes inside <Routes>:
<Route path="/login" element={<LoginPage />} />
<Route
  path="/account"
  element={
    <ProtectedRoute>
      <AccountPage />
    </ProtectedRoute>
  }
/>

Visit /account WITHOUT being logged in: you're automatically redirected to /login. Log in: you land on /account and see the protected content. ProtectedRoute is deliberately written generically – you could wrap any number of further protected pages with it without repeating the check logic.

Achtung: This kind of protection runs entirely in the browser and is NOT security-critical – a technically savvy user could bypass the JavaScript code and still have the page in memory. Real protection of data (not just of the display) must ALWAYS happen server-side – the React page may decide WHAT it shows, but must never be the only safeguard for genuinely sensitive data.