0%
Système de routage Next.js - Pages et navigation

Système de routage

Maîtrisez la navigation et les routes dans Next.js

10-15 min

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>
  )
}

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>
  )
}

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 que monArticle

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 !

Ressources utiles

Commentaires

Les commentaires sont alimentés par GitHub Discussions

Connectez-vous avec GitHub pour participer à la discussion

Lien copié !