Part 7.2 — Advanced Routing Patterns: Protected Routes & URL State
Once routing fundamentals are in place, the next challenges are control and intent.
Who can access this route?
Where should users be redirected?
Which UI state belongs in the URL?
These are architectural questions—not just routing tricks. This post shows how to answer them cleanly with React Router.
1. Protected Routes Are About Boundaries
Protected routes exist to enforce access rules, not to hide UI.
Examples:
authenticated-only pages
admin-only screens
onboarding steps
The router should express these boundaries explicitly.
2. Avoid Guard Logic Inside Pages
Bad:
if (!user) {
return <Navigate to="/login" />;
}Scattered across components, this becomes brittle and inconsistent.
3. Create a Route Guard Component
Centralize access control:
function RequireAuth({ children }: { children: JSX.Element }) {
const { user } = useAuthState();
if (!user) {
return <Navigate to="/login" replace />;
}
return children;
}This keeps rules in one place.
4. Use Guards in Route Definitions
<Route
path="/dashboard"
element={
<RequireAuth>
<Dashboard />
</RequireAuth>
}
/>Now:
access rules are explicit
routing structure reflects security boundaries
5. Redirect Back After Login
Preserve intent using location state:
function RequireAuth({ children }: { children: JSX.Element }) {
const { user } = useAuthState();
const location = useLocation();
if (!user) {
return (
<Navigate
to="/login"
state={{ from: location }}
replace
/>
);
}
return children;
}After login:
const from = location.state?.from?.pathname || '/';
navigate(from, { replace: true });Users land where they originally intended.
6. Role-Based Access Control
Extend guards:
function RequireRole({
role,
children,
}: {
role: 'admin' | 'user';
children: JSX.Element;
}) {
const { user } = useAuthState();
if (!user || user.role !== role) {
return <Navigate to="/403" replace />;
}
return children;
}Routes communicate authorization clearly.
7. URL State: When State Belongs in the URL
Some UI state should live in the URL:
pagination
filters
search queries
sorting
selected tabs
If users expect:
shareable links
browser back/forward
refresh persistence
It belongs in the URL.
8. Managing Query Parameters Safely
const [params, setParams] = useSearchParams();
const page = Number(params.get('page') ?? 1);Update URL state:
setParams({ page: String(page + 1) });Treat query params like untrusted input:
parse
validate
provide defaults
9. Avoid Duplicating URL State in React State
Bad:
const [page, setPage] = useState(1);Good:
const page = Number(searchParams.get('page') ?? 1);The URL is the source of truth.
10. Combining URL State with Data Fetching
useEffect(() => {
fetch(`/api/users?page=${page}`);
}, [page]);This gives you:
bookmarkable pages
natural refetching
predictable lifecycle
Routing drives data—not the other way around.
11. Redirects as Architecture, Not Fixes
Redirects should be intentional:
/→/dashboard/profile→/settings/profile
Avoid using redirects to “fix” broken navigation.
12. Handling 404 and 403 Routes
Always define explicit routes:
<Route path="/403" element={<Forbidden />} />
<Route path="*" element={<NotFound />} />Errors are part of UX—not afterthoughts.
13. Common Advanced Routing Mistakes
Watch for:
auth logic duplicated across components
UI state mirrored in URL and state
redirects hiding architectural issues
deeply nested guards
Routing should clarify intent—not obscure it.
14. Summary
You now know how to:
implement protected routes cleanly
centralize auth and role guards
preserve user intent on redirects
use URLs as state
manage query parameters safely
align routing with data fetching
Related
Part 7.5 — Build Optimization with Vite: Code Splitting & Env Handling
Vite makes development fast by default. This post shows how to make production builds just as intentional—lean bundles, predictable envs, and code that ships only when needed.
Part 7.4 — Performance Hooks: useMemo, useCallback & useDeferredValue
Performance hooks are scalpels, not band-aids. This post teaches you how to use them intentionally—only where they solve real problems.
Part 7.3 — Custom Hooks as Architecture: Patterns & Pitfalls
Custom hooks aren’t just helpers. Used well, they define architectural seams in your React app. Used poorly, they hide complexity and make refactoring painful.
Comments