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é à la fois 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}> - Toutes 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 des méthodes useEffect et pas 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 prévisualisation.

L'utilisation du champ asPath peut entraîner une incohérence entre le client et le serveur si la page est rendue en utilisant le rendu côté serveur ou l'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

Exemples

Gère les transitions côté client. Cette méthode est utile pour les cas où 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, ceci é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 relancer 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 derrière 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 niveau supérieur, Page, est le même.

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

  • Mettre à jour manuellement chaque état en utilisant 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 pour 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 dans next/link, router.replace empêchera l'ajout d'une nouvelle entrée d'URL dans la pile history.

router.replace(url, as, options)
  • L'API pour router.replace est exactement la même que pour 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 exemple /dashboard) et les routes dynamiques (par exemple /product/[id])
  • as - Décorateur optionnel pour url. Avant Next.js 9.5.3, ceci é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 accélérer la transition, 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 faire quelque chose avant que le routeur n'agisse dessus.

router.beforePopState(cb)
  • cb - La fonction à exécuter sur les é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. C'est généralement le 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 le 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 seulement 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

Naviguer 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

Recharger 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

Exemples

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

Erreurs ESLint potentielles

Certaines méthodes accessibles sur l'objet router retournent une Promise. Si vous avez activé la règle ESLint no-floating-promises, 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 passer la fonction en void. 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ésactiver le linting sur la ligne suivante - C'est la solution la plus propre
    // eslint-disable-next-line no-floating-promises
    router.push('/login')

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

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

withRouter

Si useRouter ne convient pas à votre cas, 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)