Skip to content

Routing & Navigation

Routing is essential for creating navigable React applications. This guide covers React Router fundamentals, private route protection, and navigation patterns.

Terminal window
npm install react-router-dom
// App.js
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import AboutPage from './pages/AboutPage';
import ProductPage from './pages/ProductPage';
import NotFoundPage from './pages/NotFoundPage';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/products/:id" element={<ProductPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
);
}
export default App;

Navigation in React Router uses special components that prevent full page reloads and provide better user experience.

// components/Navigation.jsx
import { Link, NavLink } from 'react-router-dom';
const Navigation = () => {
return (
<nav>
{/* Link: Basic navigation without active state */}
<Link to="/">Home</Link>
{/* NavLink: Automatically adds 'active' class when current */}
<NavLink
to="/about"
className={({ isActive }) => isActive ? 'active' : ''}
>
About
</NavLink>
<NavLink to="/products">Products</NavLink>
</nav>
);
};

Key Differences:

  • Link: Basic navigation, no active state indication
  • NavLink: Automatically knows when it’s the current page, useful for navigation menus

Sometimes you need to navigate based on user actions like form submissions or button clicks.

// components/LoginForm.jsx
import { useNavigate } from 'react-router-dom';
const LoginForm = () => {
const navigate = useNavigate();
const handleLogin = async (credentials) => {
try {
await authService.login(credentials);
navigate('/dashboard'); // Redirect after successful login
} catch (error) {
console.error('Login failed:', error);
}
};
const goBack = () => {
navigate(-1); // Go back one page in browser history
};
return (
<form onSubmit={handleLogin}>
{/* form content */}
<button type="button" onClick={goBack}>
Back
</button>
</form>
);
};

Key Points:

  • useNavigate() returns a function for programmatic navigation
  • navigate('/path') goes to a specific route
  • navigate(-1) goes back in history
  • navigate(1) goes forward in history

For implementing authentication and protecting routes, see the Authentication & Security guide which covers:

  • Setting up authentication context
  • Protected route components with role-based access control
  • Login/logout flows
  • JWT token management

Dynamic routes allow you to create flexible URLs that can handle variable segments. The useParams hook extracts these parameters from the URL.

// pages/ProductPage.jsx
import { useParams, useNavigate } from 'react-router-dom';
import { useState, useEffect } from 'react';
const ProductPage = () => {
const { id } = useParams(); // Extract 'id' from /products/:id
const navigate = useNavigate();
const [product, setProduct] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchProduct = async () => {
try {
const data = await productService.getById(id);
setProduct(data);
} catch (error) {
console.error('Product not found:', error);
navigate('/products'); // Redirect if not found
} finally {
setLoading(false);
}
};
fetchProduct();
}, [id, navigate]); // Re-run when ID changes
if (loading) return <div>Loading...</div>;
if (!product) return <div>Product not found</div>;
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</div>
);
};

Key Points:

  • :id in the route becomes accessible via useParams()
  • Always handle loading and error states
  • Use useEffect with the parameter as a dependency

Query parameters handle optional data like filters, search terms, and pagination. For complex state management with search parameters, see our State Management guide.

// App.js - Nested route structure
<Routes>
<Route path="/products" element={<ProductLayout />}>
<Route index element={<ProductList />} />
<Route path=":id" element={<ProductDetail />} />
<Route path=":id/edit" element={<ProductEdit />} />
</Route>
</Routes>
// components/ProductLayout.jsx
import { Outlet } from 'react-router-dom';
const ProductLayout = () => {
return (
<div>
<h1>Products</h1>
<nav>
<Link to="/products">All Products</Link>
</nav>
<main>
<Outlet /> {/* Child routes render here */}
</main>
</div>
);
};
  1. Keep Routes Flat: Avoid deeply nested route structures
  2. Use Layout Components: Share common UI with Outlet
  3. Lazy Loading: Use React.lazy for code splitting
  4. Error Handling: Always handle route errors gracefully
  1. Use NavLink for Active States: Better UX with visual feedback
  2. Preserve Query Parameters: Maintain filters and pagination
  3. Handle Loading States: Show loading indicators during navigation
  4. Breadcrumbs: Help users understand their location
  1. Client-Side Only: Remember that React Router is client-side
  2. Server Validation: Always validate permissions on the server
  3. Redirect Safely: Avoid open redirects with user input
  4. Clean URLs: Use meaningful, SEO-friendly routes