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
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":
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)
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.