Système de routage
Maîtrisez la navigation et les routes dans Next.js
Système de routage Next.js
Le système de routage de Next.js est l’une de ses fonctionnalités les plus puissantes. Il est basé sur le système de fichiers, ce qui rend la création de routes intuitive et simple.
Routage basé sur les fichiers
Structure de base
Dans Next.js, chaque fichier dans le dossier pages/
devient automatiquement une route :
pages/
├── index.js → /
├── about.js → /about
├── contact.js → /contact
└── blog/
├── index.js → /blog
└── post.js → /blog/post
Exemple de page simple
// pages/about.js
export default function About() {
return (
<div>
<h1>À propos de nous</h1>
<p>Bienvenue sur notre page à propos.</p>
</div>
)
}
Routes dynamiques
Routes avec paramètres
Utilisez les crochets []
pour créer des routes dynamiques :
// pages/blog/[slug].js
import { useRouter } from 'next/router'
export default function BlogPost() {
const router = useRouter()
const { slug } = router.query
return (
<div>
<h1>Article : {slug}</h1>
<p>Contenu de l'article...</p>
</div>
)
}
Routes avec plusieurs paramètres
// pages/blog/[year]/[month]/[slug].js
import { useRouter } from 'next/router'
export default function BlogPost() {
const router = useRouter()
const { year, month, slug } = router.query
return (
<div>
<h1>Article de {month}/{year}</h1>
<h2>{slug}</h2>
</div>
)
}
Catch-all routes
Pour capturer tous les segments d’URL :
// pages/docs/[...slug].js
import { useRouter } from 'next/router'
export default function Docs() {
const router = useRouter()
const { slug } = router.query
// slug sera un tableau : ['getting-started', 'installation']
// pour /docs/getting-started/installation
return (
<div>
<h1>Documentation</h1>
<p>Chemin : {slug?.join('/')}</p>
</div>
)
}
Optional catch-all routes
// pages/shop/[[...slug]].js
// Correspond à /shop, /shop/clothes, /shop/clothes/shirts, etc.
export default function Shop() {
const router = useRouter()
const { slug } = router.query
if (!slug) {
return <div>Page d'accueil du shop</div>
}
return (
<div>
<h1>Catégorie : {slug.join(' > ')}</h1>
</div>
)
}
Navigation
Composant Link
Le composant Link
de Next.js optimise la navigation :
import Link from 'next/link'
export default function Navigation() {
return (
<nav>
<Link href="/">
<a>Accueil</a>
</Link>
<Link href="/about">
<a>À propos</a>
</Link>
<Link href="/blog/mon-premier-article">
<a>Mon premier article</a>
</Link>
</nav>
)
}
Navigation programmatique
Utilisez le hook useRouter
pour naviguer par code :
import { useRouter } from 'next/router'
export default function LoginForm() {
const router = useRouter()
const handleSubmit = async (e) => {
e.preventDefault()
// Logique de connexion...
const success = await login(credentials)
if (success) {
// Redirection après connexion
router.push('/dashboard')
// ou avec remplacement dans l'historique
// router.replace('/dashboard')
}
}
return (
<form onSubmit={handleSubmit}>
{/* Formulaire de connexion */}
</form>
)
}
Préchargement des pages
Next.js précharge automatiquement les pages liées :
import Link from 'next/link'
export default function ProductList({ products }) {
return (
<div>
{products.map(product => (
<Link
key={product.id}
href={`/products/${product.slug}`}
prefetch={true} // Préchargement explicite
>
<a>{product.name}</a>
</Link>
))}
</div>
)
}
Routes imbriquées
Structure complexe
pages/
├── dashboard/
│ ├── index.js → /dashboard
│ ├── settings/
│ │ ├── index.js → /dashboard/settings
│ │ ├── profile.js → /dashboard/settings/profile
│ │ └── billing.js → /dashboard/settings/billing
│ └── analytics/
│ ├── index.js → /dashboard/analytics
│ └── reports.js → /dashboard/analytics/reports
Layout partagé
// components/DashboardLayout.js
import Link from 'next/link'
export default function DashboardLayout({ children }) {
return (
<div className="dashboard">
<nav className="sidebar">
<Link href="/dashboard">
<a>Tableau de bord</a>
</Link>
<Link href="/dashboard/settings">
<a>Paramètres</a>
</Link>
<Link href="/dashboard/analytics">
<a>Analytics</a>
</Link>
</nav>
<main className="content">
{children}
</main>
</div>
)
}
// pages/dashboard/index.js
import DashboardLayout from '../../components/DashboardLayout'
export default function Dashboard() {
return (
<DashboardLayout>
<h1>Tableau de bord</h1>
<p>Bienvenue dans votre espace personnel.</p>
</DashboardLayout>
)
}
Gestion des paramètres de requête
Lecture des query parameters
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
export default function SearchPage() {
const router = useRouter()
const [results, setResults] = useState([])
useEffect(() => {
const { q, category, sort } = router.query
if (q) {
// Effectuer la recherche
searchProducts({ query: q, category, sort })
.then(setResults)
}
}, [router.query])
return (
<div>
<h1>Résultats de recherche</h1>
<p>Recherche : {router.query.q}</p>
<p>Catégorie : {router.query.category}</p>
{/* Affichage des résultats */}
</div>
)
}
Modification des paramètres
import { useRouter } from 'next/router'
export default function ProductFilter() {
const router = useRouter()
const updateFilter = (key, value) => {
const query = { ...router.query }
if (value) {
query[key] = value
} else {
delete query[key]
}
router.push({
pathname: router.pathname,
query,
})
}
return (
<div>
<select
value={router.query.category || ''}
onChange={(e) => updateFilter('category', e.target.value)}
>
<option value="">Toutes les catégories</option>
<option value="electronics">Électronique</option>
<option value="clothing">Vêtements</option>
</select>
<select
value={router.query.sort || ''}
onChange={(e) => updateFilter('sort', e.target.value)}
>
<option value="">Tri par défaut</option>
<option value="price-asc">Prix croissant</option>
<option value="price-desc">Prix décroissant</option>
</select>
</div>
)
}
Middleware et protection de routes
Middleware de base
// middleware.js (à la racine du projet)
import { NextResponse } from 'next/server'
export function middleware(request) {
// Vérifier l'authentification pour les routes protégées
if (request.nextUrl.pathname.startsWith('/dashboard')) {
const token = request.cookies.get('auth-token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*']
}
HOC pour protection de routes
// hoc/withAuth.js
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useAuth } from '../hooks/useAuth'
export default function withAuth(WrappedComponent) {
return function AuthenticatedComponent(props) {
const router = useRouter()
const { user, loading } = useAuth()
useEffect(() => {
if (!loading && !user) {
router.replace('/login')
}
}, [user, loading, router])
if (loading) {
return <div>Chargement...</div>
}
if (!user) {
return null
}
return <WrappedComponent {...props} />
}
}
Usage :
// pages/dashboard/index.js
import withAuth from '../../hoc/withAuth'
function Dashboard() {
return (
<div>
<h1>Tableau de bord privé</h1>
</div>
)
}
export default withAuth(Dashboard)
Gestion des erreurs de routage
Page 404 personnalisée
// pages/404.js
import Link from 'next/link'
export default function Custom404() {
return (
<div className="error-page">
<h1>404 - Page non trouvée</h1>
<p>Désolé, la page que vous cherchez n'existe pas.</p>
<Link href="/">
<a>Retour à l'accueil</a>
</Link>
</div>
)
}
Page d’erreur générale
// pages/_error.js
function Error({ statusCode }) {
return (
<p>
{statusCode
? `Une erreur ${statusCode} s'est produite sur le serveur`
: 'Une erreur s\'est produite côté client'}
</p>
)
}
Error.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
return { statusCode }
}
export default Error
Optimisations de performance
Lazy loading des composants
import dynamic from 'next/dynamic'
const DynamicChart = dynamic(() => import('../components/Chart'), {
loading: () => <p>Chargement du graphique...</p>,
ssr: false // Désactiver le SSR pour ce composant
})
export default function Analytics() {
return (
<div>
<h1>Analytics</h1>
<DynamicChart />
</div>
)
}
Préchargement conditionnel
import Link from 'next/link'
import { useAuth } from '../hooks/useAuth'
export default function Navigation() {
const { user } = useAuth()
return (
<nav>
<Link href="/">
<a>Accueil</a>
</Link>
{user && (
<Link
href="/dashboard"
prefetch={true} // Précharger seulement si connecté
>
<a>Dashboard</a>
</Link>
)}
</nav>
)
}
Bonnes pratiques
1. Organisation des fichiers
pages/
├── api/ # API Routes
├── auth/ # Pages d'authentification
├── dashboard/ # Pages privées
├── blog/ # Blog
└── shop/ # E-commerce
2. Nommage des routes
- Utilisez des noms descriptifs :
/products/[slug].js
plutôt que/p/[id].js
- Respectez les conventions REST pour les APIs
- Utilisez des tirets pour les URLs :
mon-article
plutôt quemonArticle
3. Gestion des états de chargement
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
export default function MyComponent() {
const router = useRouter()
const [loading, setLoading] = useState(false)
useEffect(() => {
const handleStart = () => setLoading(true)
const handleComplete = () => setLoading(false)
router.events.on('routeChangeStart', handleStart)
router.events.on('routeChangeComplete', handleComplete)
router.events.on('routeChangeError', handleComplete)
return () => {
router.events.off('routeChangeStart', handleStart)
router.events.off('routeChangeComplete', handleComplete)
router.events.off('routeChangeError', handleComplete)
}
}, [router])
return (
<div>
{loading && <div>Navigation en cours...</div>}
{/* Contenu de la page */}
</div>
)
}
Le système de routage de Next.js offre une flexibilité exceptionnelle tout en restant simple à utiliser. Dans le prochain tutoriel, nous explorerons les API Routes pour créer des backends complets !
Commentaires
Les commentaires sont alimentés par GitHub Discussions
Connectez-vous avec GitHub pour participer à la discussion