Maîtrisez le routing dans React.js avec React Router v6 : routes imbriquées, navigation programmatique, lazy loading et protection de routes.
Pourquoi React Router v6 ?
React ne propose pas de système de routage natif. C'est React Router, la bibliothèque de référence, qui prend en charge la navigation côté client dans les SPA (Single Page Applications). La version 6, sortie fin 2021, apporte une API simplifiée, des routes imbriquées plus intuitives et un meilleur support de TypeScript.
Comparé à la v5, React Router v6 supprime <Switch> au profit de <Routes>, rend le matching exact par défaut, et introduit le hook useNavigate à la place de useHistory.
Installation et configuration
Installez React Router avec npm ou yarn :
# npm
npm install react-router-dom
# yarn
yarn add react-router-dom
Ensuite, enveloppez votre application avec le composant BrowserRouter dans votre fichier d'entrée :
// main.jsx (ou index.jsx)
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>
);
BrowserRouter utilise l'API History du navigateur (URLs propres sans #). Pour des environnements sans serveur configuré, préférez HashRouter.
Routes de base : Routes et Route
<Routes> remplace <Switch> et sélectionne automatiquement la route la plus précise. Chaque <Route> associe un chemin à un composant via la prop element :
// App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';
function App() {
return (
<Routes>
{/* Route exacte par défaut en v6 */}
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* Page 404 — correspond à tout chemin non trouvé */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}
Le composant <Link> remplace les balises <a> pour la navigation interne sans rechargement :
import { Link } from 'react-router-dom';
function Navbar() {
return (
<nav>
<Link to="/">Accueil</Link>
<Link to="/about">À propos</Link>
</nav>
);
}
exact de la v5 a disparu.
Routes imbriquées et Outlet
Les routes imbriquées permettent de partager un layout commun (navbar, sidebar) entre plusieurs pages. Le composant parent déclare ses enfants via <Route> imbriquées, et utilise <Outlet /> pour indiquer où le contenu enfant sera rendu :
// App.jsx — définition des routes imbriquées
import { Routes, Route } from 'react-router-dom';
import DashboardLayout from './layouts/DashboardLayout';
import Overview from './pages/Overview';
import Stats from './pages/Stats';
import Settings from './pages/Settings';
function App() {
return (
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
{/* Route index : rendue sur /dashboard exactement */}
<Route index element={<Overview />} />
<Route path="stats" element={<Stats />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
);
}
// layouts/DashboardLayout.jsx
import { Outlet, NavLink } from 'react-router-dom';
function DashboardLayout() {
return (
<div className="dashboard">
<aside>
<NavLink to="/dashboard" end>Vue d'ensemble</NavLink>
<NavLink to="/dashboard/stats" >Statistiques</NavLink>
<NavLink to="/dashboard/settings" >Paramètres</NavLink>
</aside>
<main>
{/* Le composant enfant actif est rendu ici */}
<Outlet />
</main>
</div>
);
}
<NavLink> ajoute automatiquement la classe active sur le lien correspondant à la route courante. La prop end empêche la route parente de rester active sur les routes enfants.
Paramètres de route et query strings
Les paramètres dynamiques s'ajoutent avec : dans le chemin, et se récupèrent avec useParams :
// Déclaration dans App.jsx
<Route path="/articles/:id" element={<ArticleDetail />} />
<Route path="/users/:userId/posts/:postId" element={<UserPost />} />
// ArticleDetail.jsx
import { useParams } from 'react-router-dom';
function ArticleDetail() {
// Correspondance avec :id dans le path
const { id } = useParams();
return <h1>Article #{id}</h1>;
}
Pour lire les query strings (?page=2&sort=date), utilisez useSearchParams :
import { useSearchParams } from 'react-router-dom';
function ArticleList() {
const [searchParams, setSearchParams] = useSearchParams();
const page = parseInt(searchParams.get('page') || '1', 10);
const sort = searchParams.get('sort') || 'date';
const goToNextPage = () => {
setSearchParams({ page: page + 1, sort });
};
return (
<div>
<p>Page {page} — Tri : {sort}</p>
<button onClick={goToNextPage}>Page suivante</button>
</div>
);
}
Routes protégées (Private Routes)
Une route protégée redirige l'utilisateur non authentifié vers la page de connexion. En React Router v6, ce pattern s'implémente avec un composant wrapper :
// components/PrivateRoute.jsx
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
function PrivateRoute() {
const { isAuthenticated } = useAuth();
// Si non authentifié, redirection vers /login
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
// Sinon, rendu du composant enfant via Outlet
return <Outlet />;
}
// App.jsx — utilisation de PrivateRoute
function App() {
return (
<Routes>
<Route path="/login" element={<Login />} />
{/* Toutes ces routes nécessitent une authentification */}
<Route element={<PrivateRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/settings" element={<Settings />} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
);
}
replace sur <Navigate> remplace l'entrée courante dans l'historique, évitant que l'utilisateur revienne à la page protégée avec le bouton "précédent".
Lazy loading des routes
Le lazy loading divise votre application en plusieurs bundles JS, chargés à la demande. Cela réduit le temps de chargement initial. Combinez React.lazy(), Suspense et les imports dynamiques :
// App.jsx — lazy loading de toutes les pages
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
// Chaque page est un chunk JS séparé
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
function App() {
return (
// Suspense affiche un loader pendant le chargement du chunk
<Suspense fallback={<div>Chargement...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
);
}
Vite (le bundler recommandé avec React) divise automatiquement les imports dynamiques en chunks distincts, optimisant ainsi le cache navigateur.
Conclusion
React Router v6 propose une API cohérente et puissante pour gérer la navigation dans vos SPA React. Les points essentiels à retenir :
BrowserRouter+Routes+Route: la base du routageOutlet: rendu des composants enfants dans les layouts imbriquésuseNavigate: navigation programmatique sans rechargementuseParams+useSearchParams: lecture des paramètres d'URLPrivateRouteavecNavigate: protection des routes authentifiéesReact.lazy+Suspense: découpage et chargement à la demande
createBrowserRouter (API Data Router de React Router 6.4+) qui ajoute le chargement de données colocalisé avec les routes via les fonctions loader et action.