useRouter

Si desea acceder al objeto router dentro de cualquier componente de función en su aplicación, puede usar el hook useRouter. Observe el siguiente ejemplo:

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 es un Hook de React, lo que significa que no puede usarse con clases. Puede usar withRouter o envolver su clase en un componente de función.

Objeto router

A continuación se muestra la definición del objeto router devuelto tanto por useRouter como por withRouter:

  • pathname: String - La ruta del archivo de ruta actual que viene después de /pages. Por lo tanto, no incluye basePath, locale ni la barra diagonal final (trailingSlash: true).
  • query: Object - La cadena de consulta analizada como un objeto, incluyendo parámetros de rutas dinámicas. Será un objeto vacío durante la prerenderización si la página no usa Renderizado del lado del servidor (SSR). Por defecto es {}.
  • asPath: String - La ruta como se muestra en el navegador, incluyendo los parámetros de búsqueda y respetando la configuración trailingSlash. No incluye basePath ni locale.
  • isFallback: boolean - Indica si la página actual está en modo de fallback.
  • basePath: String - El basePath activo (si está habilitado).
  • locale: String - La configuración regional activa (si está habilitada).
  • locales: String[] - Todas las configuraciones regionales soportadas (si están habilitadas).
  • defaultLocale: String - La configuración regional predeterminada actual (si está habilitada).
  • domainLocales: Array<{domain, defaultLocale, locales}> - Cualquier configuración regional de dominio configurada.
  • isReady: boolean - Indica si los campos del enrutador se han actualizado en el lado del cliente y están listos para usar. Solo debe usarse dentro de métodos useEffect y no para renderizar condicionalmente en el servidor. Consulte la documentación relacionada para casos de uso con páginas estáticamente optimizadas automáticamente.
  • isPreview: boolean - Indica si la aplicación está actualmente en modo de vista previa.

Usar el campo asPath puede provocar una discrepancia entre el cliente y el servidor si la página se renderiza usando renderizado del lado del servidor o optimización estática automática. Evite usar asPath hasta que el campo isReady sea true.

Los siguientes métodos están incluidos dentro de router:

router.push

Maneja transiciones del lado del cliente. Este método es útil para casos donde next/link no es suficiente.

router.push(url, as, options)
  • url: UrlObject | String - La URL a la que navegar (consulte la documentación del módulo URL de Node.JS para las propiedades de UrlObject).
  • as: UrlObject | String - Decorador opcional para la ruta que se mostrará en la barra de URL del navegador. Antes de Next.js 9.5.3, esto se usaba para rutas dinámicas.
  • options - Objeto opcional con las siguientes opciones de configuración:
    • scroll - Booleano opcional, controla el desplazamiento a la parte superior de la página después de la navegación. Por defecto es true.
    • shallow: Actualiza la ruta de la página actual sin volver a ejecutar getStaticProps, getServerSideProps o getInitialProps. Por defecto es false.
    • locale - Cadena opcional, indica la configuración regional de la nueva página.

No necesita usar router.push para URLs externas. window.location es más adecuado para esos casos.

Navegando a pages/about.js, que es una ruta predefinida:

import { useRouter } from 'next/router'

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

  return (
    <button type="button" onClick={() => router.push('/about')}>
      Haz clic aquí
    </button>
  )
}

Navegando a pages/post/[pid].js, que es una ruta dinámica:

import { useRouter } from 'next/router'

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

  return (
    <button type="button" onClick={() => router.push('/post/abc')}>
      Haz clic aquí
    </button>
  )
}

Redirigiendo al usuario a pages/login.js, útil para páginas detrás de autenticación:

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

// Aquí obtendrías y devolverías el usuario
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>Redirigiendo...</p>
}

Restableciendo el estado después de la navegación

Al navegar a la misma página en Next.js, el estado de la página no se restablecerá por defecto, ya que React no desmonta a menos que el componente padre haya cambiado.

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>Página: {router.query.slug}</h1>
      <p>Contador: {count}</p>
      <button onClick={() => setCount(count + 1)}>Incrementar contador</button>
      <Link href="/one">one</Link> <Link href="/two">two</Link>
    </div>
  )
}

En el ejemplo anterior, navegar entre /one y /two no restablecerá el contador. El useState se mantiene entre renderizados porque el componente de nivel superior de React, Page, es el mismo.

Si no desea este comportamiento, tiene un par de opciones:

  • Actualizar manualmente cada estado usando useEffect. En el ejemplo anterior, podría verse así:

    useEffect(() => {
      setCount(0)
    }, [router.query.slug])
  • Usar una key de React para indicarle a React que vuelva a montar el componente. Para hacer esto en todas las páginas, puede usar una aplicación personalizada:

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

Con objeto URL

Puede usar un objeto URL de la misma manera que lo hace con next/link. Funciona tanto para el parámetro url como para 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 },
        })
      }}
    >
      Haz clic aquí para leer más
    </button>
  )
}

router.replace

Similar a la propiedad replace en next/link, router.replace evitará agregar una nueva entrada de URL a la pila del history.

router.replace(url, as, options)
  • La API para router.replace es exactamente la misma que la API para router.push.

Observe el siguiente ejemplo:

import { useRouter } from 'next/router'

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

  return (
    <button type="button" onClick={() => router.replace('/home')}>
      Haz clic aquí
    </button>
  )
}

router.prefetch

Prefetch de páginas para transiciones más rápidas del lado del cliente. Este método solo es útil para navegaciones sin next/link, ya que next/link se encarga del prefetch de páginas automáticamente.

Esta es una característica solo para producción. Next.js no hace prefetch de páginas en desarrollo.

router.prefetch(url, as, options)
  • url - La URL para hacer prefetch, incluyendo rutas explícitas (ej. /dashboard) y rutas dinámicas (ej. /product/[id]).
  • as - Decorador opcional para url. Antes de Next.js 9.5.3, esto se usaba para hacer prefetch de rutas dinámicas.
  • options - Objeto opcional con los siguientes campos permitidos:
    • locale - Permite proporcionar una configuración regional diferente a la activa. Si es false, url debe incluir la configuración regional ya que no se usará la activa.

Supongamos que tiene una página de inicio de sesión y, después del inicio de sesión, redirige al usuario al panel de control. Para ese caso, podemos hacer prefetch del panel de control para hacer una transición más rápida, como en el siguiente ejemplo:

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({
        /* Datos del formulario */
      }),
    }).then((res) => {
      // Hacer una transición rápida del lado del cliente a la página del panel ya prefetcheada
      if (res.ok) router.push('/dashboard')
    })
  }, [])

  useEffect(() => {
    // Hacer prefetch de la página del panel
    router.prefetch('/dashboard')
  }, [router])

  return (
    <form onSubmit={handleSubmit}>
      {/* Campos del formulario */}
      <button type="submit">Iniciar sesión</button>
    </form>
  )
}

router.beforePopState

En algunos casos (por ejemplo, si usa un Servidor Personalizado), es posible que desee escuchar el evento popstate y hacer algo antes de que el enrutador actúe sobre él.

router.beforePopState(cb)
  • cb - La función a ejecutar en eventos popstate entrantes. La función recibe el estado del evento como un objeto con las siguientes propiedades:
    • url: String - la ruta para el nuevo estado. Esto suele ser el nombre de una page.
    • as: String - la URL que se mostrará en el navegador.
    • options: Object - Opciones adicionales enviadas por router.push.

Si cb devuelve false, el enrutador de Next.js no manejará el popstate, y usted será responsable de manejarlo en ese caso. Consulte Deshabilitar el enrutamiento del sistema de archivos.

Podría usar beforePopState para manipular la solicitud o forzar una actualización SSR, como en el siguiente ejemplo:

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

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

  useEffect(() => {
    router.beforePopState(({ url, as, options }) => {
      // ¡Solo quiero permitir estas dos rutas!
      if (as !== '/' && as !== '/other') {
        // Hacer que SSR renderice rutas incorrectas como 404.
        window.location.href = as
        return false
      }

      return true
    })
  }, [router])

  return <p>Bienvenido a la página</p>
}

router.back

Navega hacia atrás en el historial. Equivalente a hacer clic en el botón "Atrás" del navegador. Ejecuta window.history.back().

import { useRouter } from 'next/router'

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

  return (
    <button type="button" onClick={() => router.back()}>
      Haz clic aquí para ir atrás
    </button>
  )
}

router.reload

Recarga la URL actual. Equivalente a hacer clic en el botón "Recargar" del navegador. Ejecuta window.location.reload().

import { useRouter } from 'next/router'

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

  return (
    <button type="button" onClick={() => router.reload()}>
      Haz clic aquí para recargar
    </button>
  )
}

router.events

Puede escuchar diferentes eventos que ocurren dentro del Enrutador de Next.js. Aquí hay una lista de eventos soportados:

  • routeChangeStart(url, { shallow }) - Se dispara cuando una ruta comienza a cambiar.
  • routeChangeComplete(url, { shallow }) - Se dispara cuando una ruta ha cambiado completamente.
  • routeChangeError(err, url, { shallow }) - Se dispara cuando hay un error al cambiar de ruta o se cancela la carga de una ruta.
    • err.cancelled - Indica si la navegación fue cancelada.
  • beforeHistoryChange(url, { shallow }) - Se dispara antes de cambiar el historial del navegador.
  • hashChangeStart(url, { shallow }) - Se dispara cuando el hash cambiará pero no la página.
  • hashChangeComplete(url, { shallow }) - Se dispara cuando el hash ha cambiado pero no la página.

Nota importante: Aquí url es la URL mostrada en el navegador, incluyendo el basePath.

Por ejemplo, para escuchar el evento del enrutador routeChangeStart, abra o cree pages/_app.js y suscríbase al evento, así:

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(
        `La aplicación está cambiando a ${url} ${
          shallow ? 'con' : 'sin'
        } enrutamiento superficial`
      )
    }

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

    // Si el componente se desmonta, cancela la suscripción
    // del evento con el método 'off':
    return () => {
      router.events.off('routeChangeStart', handleRouteChange)
    }
  }, [router])

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

Usamos una Aplicación Personalizada (pages/_app.js) para este ejemplo para suscribirnos al evento porque no se desmonta en navegaciones de página, pero puede suscribirse a eventos del enrutador en cualquier componente de su aplicación.

Los eventos del enrutador deben registrarse cuando un componente se monta (useEffect o componentDidMount / componentWillUnmount) o imperativamente cuando ocurre un evento.

Si se cancela la carga de una ruta (por ejemplo, al hacer clic en dos enlaces rápidamente en sucesión), se disparará routeChangeError. Y el err pasado contendrá una propiedad cancelled establecida en true, como en el siguiente ejemplo:

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 ruta a ${url} fue cancelada!`)
      }
    }

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

    // Si el componente se desmonta, cancela la suscripción
    // del evento con el método 'off':
    return () => {
      router.events.off('routeChangeError', handleRouteChangeError)
    }
  }, [router])

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

La exportación next/compat/router

Este es el mismo hook useRouter, pero puede usarse tanto en los directorios app como pages.

Se diferencia de next/router en que no lanza un error cuando el enrutador de páginas no está montado, y en su lugar tiene un tipo de retorno NextRouter | null. Esto permite a los desarrolladores convertir componentes para que funcionen tanto en app como en pages mientras realizan la transición al enrutador app.

Un componente que anteriormente se veía así:

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

Generará un error al convertirse a next/compat/router, ya que null no puede desestructurarse. En su lugar, los desarrolladores podrán aprovechar nuevos hooks:

import { useEffect } from 'react'
import { useRouter } from 'next/compat/router'
import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
  const router = useRouter() // puede ser null o una instancia de NextRouter
  const searchParams = useSearchParams()
  useEffect(() => {
    if (router && !router.isReady) {
      return
    }
    // En `app/`, searchParams estará listo inmediatamente con los valores, en
    // `pages/` estará disponible después de que el enrutador esté listo.
    const search = searchParams.get('search')
    // ...
  }, [router, searchParams])
  // ...
}

Este componente ahora funcionará tanto en los directorios pages como app. Cuando el componente ya no se use en pages, puedes eliminar las referencias al enrutador de compatibilidad:

import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
  const searchParams = useSearchParams()
  // Como este componente solo se usa en `app/`, se puede eliminar el enrutador de compatibilidad.
  const search = searchParams.get('search')
  // ...
}

Usando useRouter fuera del contexto de Next.js en páginas

Otro caso de uso específico es cuando se renderizan componentes fuera del contexto de una aplicación Next.js, como dentro de getServerSideProps en el directorio pages. En este caso, se puede usar el enrutador de compatibilidad para evitar errores:

import { renderToString } from 'react-dom/server'
import { useRouter } from 'next/compat/router'
const MyComponent = () => {
  const router = useRouter() // puede ser null o una instancia de NextRouter
  // ...
}
export async function getServerSideProps() {
  const renderedComponent = renderToString(<MyComponent />)
  return {
    props: {
      renderedComponent,
    },
  }
}

Errores potenciales de ESLint

Ciertos métodos accesibles en el objeto router devuelven una Promesa. Si tienes habilitada la regla de ESLint no-floating-promises, considera desactivarla globalmente o para la línea afectada.

Si tu aplicación necesita esta regla, deberías usar void con la promesa - o usar una función async, await la Promesa y luego usar void en la llamada a la función. Esto no aplica cuando el método se llama desde dentro de un manejador onClick.

Los métodos afectados son:

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

Soluciones potenciales

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

// Aquí obtendrías y devolverías el usuario
const useUser = () => ({ user: null, loading: false })

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

  useEffect(() => {
    // deshabilitar el linting en la siguiente línea - Esta es la solución más limpia
    // eslint-disable-next-line no-floating-promises
    router.push('/login')

    // usar void con la Promesa devuelta por router.push
    if (!(user || loading)) {
      void router.push('/login')
    }
    // o usar una función async, await la Promesa, luego usar void en la llamada a la función
    async function handleRouteChange() {
      if (!(user || loading)) {
        await router.push('/login')
      }
    }
    void handleRouteChange()
  }, [user, loading])

  return <p>Redirigiendo...</p>
}

withRouter

Si useRouter no es la mejor opción para ti, withRouter también puede añadir el mismo objeto router a cualquier componente.

Uso

import { withRouter } from 'next/router'

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

export default withRouter(Page)

TypeScript

Para usar componentes de clase con withRouter, el componente necesita aceptar una 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)