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 en 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 secours (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 à être utilisés. Ne doit être utilisé qu'à l'intérieur de 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 optimisées statiquement automatiquement
  • 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 côté serveur ou avec 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

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 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 protégées par 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)}>Augmenter le compteur</button>
      <Link href="/one">one</Link> <Link href="/two">two</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 d'historique.

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 exemple /dashboard) et les routes dynamiques (par exemple /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 faire quelque chose 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. 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 ne veux autoriser que 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é, désabonnez-vous
    // 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 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é, désabonnez-vous
    // de l'événement avec la méthode 'off' :
    return () => {
      router.events.off('routeChangeError', handleRouteChangeError)
    }
  }, [router])

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

Erreurs potentielles avec ESLint

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 nécessite 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 ne s'applique pas 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')

    // passe en void la Promise retournée par router.push
    if (!(user || loading)) {
      void router.push('/login')
    }
    // ou utilise une fonction async, await la Promise, puis passe 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 également 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)