useRouter

Si vous souhaitez accéder à l'objet router dans n'importe quel composant fonction de votre application, vous pouvez utiliser le hook useRouter. Voici un exemple :

import { useRouter } from 'next/router'

function ActiveLink({ children, href }) {
  const router = useRouter()
  const style = {
    marginRight: 10,
    color: router.asPath === href ? 'red' : 'black',
  }

  const handleClick = (e) => {
    e.preventDefault()
    router.push(href)
  }

  return (
    <a href={href} onClick={handleClick} style={style}>
      {children}
    </a>
  )
}

export default ActiveLink

useRouter est un Hook React, ce qui signifie qu'il ne peut pas être utilisé avec des classes. Vous pouvez soit utiliser withRouter, soit encapsuler votre classe dans un composant fonction.

Objet router

Voici la définition de l'objet router retourné par useRouter et withRouter :

  • pathname: String - Le chemin du fichier de route actuel qui vient après /pages. Par conséquent, basePath, locale et la barre oblique finale (trailingSlash: true) ne sont pas inclus.
  • query: Object - La chaîne de requête analysée sous forme d'objet, incluant les paramètres des routes dynamiques. Ce sera un objet vide pendant le pré-rendu si la page n'utilise pas le rendu côté serveur (SSR). Par défaut : {}
  • asPath: String - Le chemin tel qu'affiché dans le navigateur, incluant les paramètres de recherche et respectant la configuration trailingSlash. basePath et locale ne sont pas inclus.
  • isFallback: boolean - Indique si la page actuelle est en mode de repli (fallback).
  • basePath: String - Le basePath actif (si activé).
  • locale: String - La locale active (si activée).
  • locales: String[] - Toutes les locales supportées (si activées).
  • defaultLocale: String - La locale par défaut actuelle (si activée).
  • domainLocales: Array<{domain, defaultLocale, locales}> - Les locales configurées par domaine.
  • isReady: boolean - Indique si les champs du routeur sont mis à jour côté client et prêts à l'emploi. Ne doit être utilisé qu'à l'intérieur de méthodes useEffect et non pour un rendu conditionnel côté serveur. Voir la documentation associée pour les cas d'utilisation avec les pages automatiquement optimisées statiquement
  • isPreview: boolean - Indique si l'application est actuellement en mode de prévisualisation.

L'utilisation du champ asPath peut entraîner une incohérence entre le client et le serveur si la page est rendue côté serveur ou avec une optimisation statique automatique. Évitez d'utiliser asPath tant que le champ isReady n'est pas true.

Les méthodes suivantes sont incluses dans router :

router.push

Gère les transitions côté client. Cette méthode est utile lorsque next/link ne suffit pas.

router.push(url, as, options)
  • url: UrlObject | String - L'URL vers laquelle naviguer (voir la documentation du module URL de Node.JS pour les propriétés de UrlObject).
  • as: UrlObject | String - Décorateur optionnel pour le chemin qui sera affiché dans la barre d'URL du navigateur. Avant Next.js 9.5.3, cela était utilisé pour les routes dynamiques.
  • options - Objet optionnel avec les options de configuration suivantes :
    • scroll - Booléen optionnel, contrôle le défilement vers le haut de la page après la navigation. Par défaut : true
    • shallow : Met à jour le chemin de la page actuelle sans réexécuter getStaticProps, getServerSideProps ou getInitialProps. Par défaut : false
    • locale - Chaîne optionnelle, indique la locale de la nouvelle page

Vous n'avez pas besoin d'utiliser router.push pour les URL externes. window.location est plus adapté pour ces cas.

Navigation vers pages/about.js, qui est une route prédéfinie :

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.push('/about')}>
      Cliquez ici
    </button>
  )
}

Navigation vers pages/post/[pid].js, qui est une route dynamique :

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.push('/post/abc')}>
      Cliquez ici
    </button>
  )
}

Redirection de l'utilisateur vers pages/login.js, utile pour les pages protégées par une authentification :

import { useEffect } from 'react'
import { useRouter } from 'next/router'

// Ici, vous récupéreriez et retourneriez l'utilisateur
const useUser = () => ({ user: null, loading: false })

export default function Page() {
  const { user, loading } = useUser()
  const router = useRouter()

  useEffect(() => {
    if (!(user || loading)) {
      router.push('/login')
    }
  }, [user, loading])

  return <p>Redirection en cours...</p>
}

Réinitialisation de l'état après navigation

Lors de la navigation vers la même page dans Next.js, l'état de la page ne sera pas réinitialisé par défaut, car React ne démonte pas le composant sauf si le composant parent a changé.

pages/[slug].js
import Link from 'next/link'
import { useState } from 'react'
import { useRouter } from 'next/router'

export default function Page(props) {
  const router = useRouter()
  const [count, setCount] = useState(0)
  return (
    <div>
      <h1>Page : {router.query.slug}</h1>
      <p>Compteur : {count}</p>
      <button onClick={() => setCount(count + 1)}>Incrémenter</button>
      <Link href="/one">un</Link> <Link href="/two">deux</Link>
    </div>
  )
}

Dans l'exemple ci-dessus, naviguer entre /one et /two ne réinitialisera pas le compteur. Le useState est maintenu entre les rendus car le composant React de haut niveau, Page, est le même.

Si vous ne voulez pas ce comportement, vous avez plusieurs options :

  • Mettre à jour manuellement chaque état avec useEffect. Dans l'exemple ci-dessus, cela pourrait ressembler à :

    useEffect(() => {
      setCount(0)
    }, [router.query.slug])
  • Utiliser une key React pour indiquer à React de remonter le composant. Pour le faire sur toutes les pages, vous pouvez utiliser une application personnalisée :

    pages/_app.js
    import { useRouter } from 'next/router'
    
    export default function MyApp({ Component, pageProps }) {
      const router = useRouter()
      return <Component key={router.asPath} {...pageProps} />
    }

Avec un objet URL

Vous pouvez utiliser un objet URL de la même manière que pour next/link. Fonctionne pour les paramètres url et as :

import { useRouter } from 'next/router'

export default function ReadMore({ post }) {
  const router = useRouter()

  return (
    <button
      type="button"
      onClick={() => {
        router.push({
          pathname: '/post/[pid]',
          query: { pid: post.id },
        })
      }}
    >
      Cliquez ici pour en savoir plus
    </button>
  )
}

router.replace

Similaire à la prop replace de next/link, router.replace empêche l'ajout d'une nouvelle entrée d'URL dans la pile history.

router.replace(url, as, options)
  • L'API de router.replace est exactement la même que celle de router.push.

Voici un exemple :

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.replace('/home')}>
      Cliquez ici
    </button>
  )
}

router.prefetch

Précharge les pages pour des transitions côté client plus rapides. Cette méthode n'est utile que pour les navigations sans next/link, car next/link s'occupe de précharger les pages automatiquement.

Cette fonctionnalité est uniquement disponible en production. Next.js ne précharge pas les pages en développement.

router.prefetch(url, as, options)
  • url - L'URL à précharger, incluant les routes explicites (par ex. /dashboard) et dynamiques (par ex. /product/[id])
  • as - Décorateur optionnel pour url. Avant Next.js 9.5.3, cela était utilisé pour précharger les routes dynamiques.
  • options - Objet optionnel avec les champs autorisés suivants :
    • locale - permet de fournir une locale différente de celle active. Si false, url doit inclure la locale car la locale active ne sera pas utilisée.

Imaginons que vous ayez une page de connexion, et qu'après la connexion, vous redirigez l'utilisateur vers le tableau de bord. Pour ce cas, nous pouvons précharger le tableau de bord pour une transition plus rapide, comme dans l'exemple suivant :

import { useCallback, useEffect } from 'react'
import { useRouter } from 'next/router'

export default function Login() {
  const router = useRouter()
  const handleSubmit = useCallback((e) => {
    e.preventDefault()

    fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        /* Données du formulaire */
      }),
    }).then((res) => {
      // Effectue une transition côté client rapide vers la page de tableau de bord déjà préchargée
      if (res.ok) router.push('/dashboard')
    })
  }, [])

  useEffect(() => {
    // Précharge la page du tableau de bord
    router.prefetch('/dashboard')
  }, [router])

  return (
    <form onSubmit={handleSubmit}>
      {/* Champs du formulaire */}
      <button type="submit">Se connecter</button>
    </form>
  )
}

router.beforePopState

Dans certains cas (par exemple, si vous utilisez un serveur personnalisé), vous pouvez souhaiter écouter les événements popstate et effectuer une action avant que le routeur n'agisse.

router.beforePopState(cb)
  • cb - La fonction à exécuter lors des événements popstate entrants. La fonction reçoit l'état de l'événement sous forme d'objet avec les props suivantes :
    • url: String - la route pour le nouvel état. Il s'agit généralement du nom d'une page
    • as: String - l'URL qui sera affichée dans le navigateur
    • options: Object - Options supplémentaires envoyées par router.push

Si cb retourne false, le routeur Next.js ne gérera pas popstate, et vous serez responsable de le gérer dans ce cas. Voir Désactivation du routage par système de fichiers.

Vous pouvez utiliser beforePopState pour manipuler la requête ou forcer un rafraîchissement SSR, comme dans l'exemple suivant :

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  useEffect(() => {
    router.beforePopState(({ url, as, options }) => {
      // Je veux uniquement autoriser ces deux routes !
      if (as !== '/' && as !== '/other') {
        // Faire en sorte que le SSR rende les mauvaises routes en 404.
        window.location.href = as
        return false
      }

      return true
    })
  }, [router])

  return <p>Bienvenue sur la page</p>
}

router.back

Navigue en arrière dans l'historique. Équivalent à cliquer sur le bouton retour du navigateur. Exécute window.history.back().

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.back()}>
      Cliquez ici pour revenir en arrière
    </button>
  )
}

router.reload

Recharge l'URL actuelle. Équivalent à cliquer sur le bouton rafraîchir du navigateur. Exécute window.location.reload().

import { useRouter } from 'next/router'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.reload()}>
      Cliquez ici pour recharger
    </button>
  )
}

router.events

Vous pouvez écouter différents événements se produisant dans le routeur Next.js. Voici une liste des événements supportés :

  • routeChangeStart(url, { shallow }) - Se déclenche lorsqu'une route commence à changer
  • routeChangeComplete(url, { shallow }) - Se déclenche lorsqu'une route a complètement changé
  • routeChangeError(err, url, { shallow }) - Se déclenche lorsqu'il y a une erreur lors du changement de route, ou qu'un chargement de route est annulé
    • err.cancelled - Indique si la navigation a été annulée
  • beforeHistoryChange(url, { shallow }) - Se déclenche avant de changer l'historique du navigateur
  • hashChangeStart(url, { shallow }) - Se déclenche lorsque le hash va changer mais pas la page
  • hashChangeComplete(url, { shallow }) - Se déclenche lorsque le hash a changé mais pas la page

Bon à savoir : Ici, url est l'URL affichée dans le navigateur, incluant le basePath.

Par exemple, pour écouter l'événement routeChangeStart du routeur, ouvrez ou créez pages/_app.js et abonnez-vous à l'événement, comme ceci :

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function MyApp({ Component, pageProps }) {
  const router = useRouter()

  useEffect(() => {
    const handleRouteChange = (url, { shallow }) => {
      console.log(
        `L'application change vers ${url} ${
          shallow ? 'avec' : 'sans'
        } routage shallow`
      )
    }

    router.events.on('routeChangeStart', handleRouteChange)

    // Si le composant est démonté, se désabonner
    // de l'événement avec la méthode 'off' :
    return () => {
      router.events.off('routeChangeStart', handleRouteChange)
    }
  }, [router])

  return <Component {...pageProps} />
}

Nous utilisons une application personnalisée (Custom App) (pages/_app.js) pour cet exemple afin de nous abonner à l'événement car elle n'est pas démontée lors des navigations de page, mais vous pouvez vous abonner aux événements du routeur dans n'importe quel composant de votre application.

Les événements du routeur doivent être enregistrés lorsqu'un composant est monté (useEffect ou componentDidMount / componentWillUnmount) ou impérativement lorsqu'un événement se produit.

Si un chargement de route est annulé (par exemple, en cliquant rapidement sur deux liens à la suite), routeChangeError se déclenchera. Et l'err passé contiendra une propriété cancelled définie à true, comme dans l'exemple suivant :

import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function MyApp({ Component, pageProps }) {
  const router = useRouter()

  useEffect(() => {
    const handleRouteChangeError = (err, url) => {
      if (err.cancelled) {
        console.log(`La route vers ${url} a été annulée !`)
      }
    }

    router.events.on('routeChangeError', handleRouteChangeError)

    // Si le composant est démonté, se désabonner
    // de l'événement avec la méthode 'off' :
    return () => {
      router.events.off('routeChangeError', handleRouteChangeError)
    }
  }, [router])

  return <Component {...pageProps} />
}

L'export next/compat/router

Il s'agit du même hook useRouter, mais qui peut être utilisé à la fois dans les répertoires app et pages.

Il diffère de next/router en ce qu'il ne génère pas d'erreur lorsque le routeur de pages n'est pas monté, et a plutôt un type de retour NextRouter | null. Cela permet aux développeurs de convertir des composants pour qu'ils fonctionnent à la fois dans app et pages lors de la transition vers le routeur app.

Un composant qui ressemblait précédemment à ceci :

import { useRouter } from 'next/router'
const MyComponent = () => {
  const { isReady, query } = useRouter()
  // ...
}

Générera une erreur lors de la conversion vers next/compat/router, car null ne peut pas être déstructuré. À la place, les développeurs pourront utiliser de nouveaux hooks :

import { useEffect } from 'react'
import { useRouter } from 'next/compat/router'
import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
  const router = useRouter() // peut être null ou une instance de NextRouter
  const searchParams = useSearchParams()
  useEffect(() => {
    if (router && !router.isReady) {
      return
    }
    // Dans `app/`, searchParams sera immédiatement prêt avec les valeurs, dans
    // `pages/` il sera disponible après que le routeur soit prêt.
    const search = searchParams.get('search')
    // ...
  }, [router, searchParams])
  // ...
}

Ce composant fonctionnera maintenant dans les répertoires pages et app. Lorsque le composant n'est plus utilisé dans pages, vous pouvez supprimer les références au routeur de compatibilité :

import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
  const searchParams = useSearchParams()
  // Comme ce composant est uniquement utilisé dans `app/`, le routeur de compatibilité peut être supprimé.
  const search = searchParams.get('search')
  // ...
}

Utilisation de useRouter en dehors du contexte Next.js dans les pages

Un autre cas d'usage spécifique est le rendu de composants en dehors du contexte d'une application Next.js, comme dans getServerSideProps du répertoire pages. Dans ce cas, le routeur de compatibilité peut être utilisé pour éviter les erreurs :

import { renderToString } from 'react-dom/server'
import { useRouter } from 'next/compat/router'
const MyComponent = () => {
  const router = useRouter() // peut être null ou une instance de NextRouter
  // ...
}
export async function getServerSideProps() {
  const renderedComponent = renderToString(<MyComponent />)
  return {
    props: {
      renderedComponent,
    },
  }
}

Erreurs ESLint potentielles

Certaines méthodes accessibles sur l'objet router retournent une Promise. Si vous avez la règle ESLint no-floating-promises activée, envisagez de la désactiver soit globalement, soit pour la ligne concernée.

Si votre application a besoin de cette règle, vous devriez soit utiliser void avec la Promise - soit utiliser une fonction async, await la Promise, puis utiliser void sur l'appel de fonction. Ceci n'est pas applicable lorsque la méthode est appelée depuis un gestionnaire onClick.

Les méthodes concernées sont :

  • router.push
  • router.replace
  • router.prefetch

Solutions potentielles

import { useEffect } from 'react'
import { useRouter } from 'next/router'

// Ici vous récupéreriez et retourneriez l'utilisateur
const useUser = () => ({ user: null, loading: false })

export default function Page() {
  const { user, loading } = useUser()
  const router = useRouter()

  useEffect(() => {
    // désactive le linting sur la ligne suivante - C'est la solution la plus propre
    // eslint-disable-next-line no-floating-promises
    router.push('/login')

    // utilise void avec la Promise retournée par router.push
    if (!(user || loading)) {
      void router.push('/login')
    }
    // ou utilise une fonction async, await la Promise, puis void l'appel de fonction
    async function handleRouteChange() {
      if (!(user || loading)) {
        await router.push('/login')
      }
    }
    void handleRouteChange()
  }, [user, loading])

  return <p>Redirection en cours...</p>
}

withRouter

Si useRouter ne vous convient pas, withRouter peut aussi ajouter le même objet router à n'importe quel composant.

Utilisation

import { withRouter } from 'next/router'

function Page({ router }) {
  return <p>{router.pathname}</p>
}

export default withRouter(Page)

TypeScript

Pour utiliser des composants de classe avec withRouter, le composant doit accepter une prop router :

import React from 'react'
import { withRouter, NextRouter } from 'next/router'

interface WithRouterProps {
  router: NextRouter
}

interface MyComponentProps extends WithRouterProps {}

class MyComponent extends React.Component<MyComponentProps> {
  render() {
    return <p>{this.props.router.pathname}</p>
  }
}

export default withRouter(MyComponent)