useRouter

Wenn Sie auf das router-Objekt in einer beliebigen Funktionskomponente Ihrer App zugreifen möchten, können Sie den useRouter-Hook verwenden. Sehen Sie sich das folgende Beispiel an:

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 ist ein React Hook, was bedeutet, dass er nicht mit Klassen verwendet werden kann. Sie können entweder withRouter verwenden oder Ihre Klasse in eine Funktionskomponente einwickeln.

router-Objekt

Im Folgenden finden Sie die Definition des router-Objekts, das sowohl von useRouter als auch von withRouter zurückgegeben wird:

  • pathname: String - Der Pfad der aktuellen Routendatei, der nach /pages kommt. Daher sind basePath, locale und der nachgestellte Schrägstrich (trailingSlash: true) nicht enthalten.
  • query: Object - Die Abfragezeichenfolge, die in ein Objekt geparst wurde, einschließlich der Parameter für dynamische Routen. Während des Prerenderings wird es ein leeres Objekt sein, wenn die Seite kein Server-seitiges Rendering (SSR) verwendet. Standardwert ist {}.
  • asPath: String - Der Pfad, wie er im Browser angezeigt wird, einschließlich der Suchparameter und unter Berücksichtigung der trailingSlash-Konfiguration. basePath und locale sind nicht enthalten.
  • isFallback: boolean - Gibt an, ob sich die aktuelle Seite im Fallback-Modus befindet.
  • basePath: String - Der aktive basePath (falls aktiviert).
  • locale: String - Die aktive Locale (falls aktiviert).
  • locales: String[] - Alle unterstützten Locales (falls aktiviert).
  • defaultLocale: String - Die aktuelle Standard-Locale (falls aktiviert).
  • domainLocales: Array<{domain, defaultLocale, locales}> - Alle konfigurierten Domain-Locales.
  • isReady: boolean - Gibt an, ob die Router-Felder clientseitig aktualisiert und einsatzbereit sind. Sollte nur innerhalb von useEffect-Methoden verwendet werden und nicht für bedingtes Rendering auf dem Server. Siehe verwandte Dokumentation für den Anwendungsfall mit automatisch statisch optimierten Seiten.
  • isPreview: boolean - Gibt an, ob sich die Anwendung aktuell im Vorschaumodus (Preview Mode) befindet.

Die Verwendung des asPath-Felds kann zu einer Diskrepanz zwischen Client und Server führen, wenn die Seite mit Server-seitigem Rendering oder automatischer statischer Optimierung gerendert wird. Vermeiden Sie die Verwendung von asPath, bis das isReady-Feld true ist.

Die folgenden Methoden sind im router-Objekt enthalten:

router.push

Behandelt clientseitige Übergänge. Diese Methode ist nützlich für Fälle, in denen next/link nicht ausreicht.

router.push(url, as, options)
  • url: UrlObject | String - Die URL, zu der navigiert werden soll (siehe Node.JS URL-Modul-Dokumentation für UrlObject-Eigenschaften).
  • as: UrlObject | String - Optionaler Dekorator für den Pfad, der in der Browser-URL-Leiste angezeigt wird. Vor Next.js 9.5.3 wurde dies für dynamische Routen verwendet.
  • options - Optionales Objekt mit den folgenden Konfigurationsoptionen:
    • scroll - Optionaler Boolean, steuert das Scrollen zum Seitenanfang nach der Navigation. Standardwert ist true.
    • shallow: Aktualisiert den Pfad der aktuellen Seite, ohne getStaticProps, getServerSideProps oder getInitialProps erneut auszuführen. Standardwert ist false.
    • locale - Optionaler String, gibt die Locale der neuen Seite an.

Sie müssen router.push nicht für externe URLs verwenden. window.location ist dafür besser geeignet.

Navigation zu pages/about.js, einer vordefinierten Route:

import { useRouter } from 'next/router'

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

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

Navigation zu pages/post/[pid].js, einer dynamischen Route:

import { useRouter } from 'next/router'

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

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

Weiterleitung des Benutzers zu pages/login.js, nützlich für Seiten hinter einer Authentifizierung:

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

// Hier würden Sie den Benutzer abrufen und zurückgeben
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>Weiterleiten...</p>
}

Zurücksetzen des Zustands nach der Navigation

Beim Navigieren zur gleichen Seite in Next.js wird der Seitenzustand standardmäßig nicht zurückgesetzt, da React die Komponente nicht aushängt, es sei denn, die übergeordnete Komponente hat sich geändert.

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>Seite: {router.query.slug}</h1>
      <p>Zähler: {count}</p>
      <button onClick={() => setCount(count + 1)}>Zähler erhöhen</button>
      <Link href="/one">eins</Link> <Link href="/two">zwei</Link>
    </div>
  )
}

Im obigen Beispiel wird beim Navigieren zwischen /one und /two der Zähler nicht zurückgesetzt. Der useState-Wert bleibt zwischen den Renderings erhalten, da die übergeordnete React-Komponente Page gleich bleibt.

Wenn Sie dieses Verhalten nicht wünschen, haben Sie mehrere Möglichkeiten:

  • Manuelles Aktualisieren jedes Zustands mit useEffect. Im obigen Beispiel könnte das so aussehen:

    useEffect(() => {
      setCount(0)
    }, [router.query.slug])
  • Verwenden Sie einen React key, um React mitzuteilen, dass die Komponente neu eingehängt werden soll. Um dies für alle Seiten zu tun, können Sie eine benutzerdefinierte App verwenden:

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

Mit URL-Objekt

Sie können ein URL-Objekt auf die gleiche Weise verwenden wie bei next/link. Funktioniert für beide Parameter url und 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 },
        })
      }}
    >
      Klicken Sie hier, um mehr zu lesen
    </button>
  )
}

router.replace

Ähnlich wie die replace-Eigenschaft in next/link, verhindert router.replace, dass ein neuer URL-Eintrag in den history-Stack hinzugefügt wird.

router.replace(url, as, options)
  • Die API für router.replace ist identisch mit der API für router.push.

Sehen Sie sich das folgende Beispiel an:

import { useRouter } from 'next/router'

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

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

router.prefetch

Seiten vorab laden für schnellere clientseitige Übergänge. Diese Methode ist nur für Navigationen ohne next/link nützlich, da next/link Seiten automatisch vorab lädt.

Dies ist eine Funktion nur für die Produktion. Next.js lädt Seiten im Entwicklungsmodus nicht vorab.

router.prefetch(url, as, options)
  • url - Die URL, die vorab geladen werden soll, einschließlich expliziter Routen (z.B. /dashboard) und dynamischer Routen (z.B. /product/[id]).
  • as - Optionaler Dekorator für url. Vor Next.js 9.5.3 wurde dies verwendet, um dynamische Routen vorab zu laden.
  • options - Optionales Objekt mit den folgenden erlaubten Feldern:
    • locale - Ermöglicht die Angabe einer anderen Locale als der aktiven. Wenn false, muss url die Locale enthalten, da die aktive Locale nicht verwendet wird.

Angenommen, Sie haben eine Login-Seite, und nach dem Login leiten Sie den Benutzer zum Dashboard weiter. Für diesen Fall können wir das Dashboard vorab laden, um einen schnelleren Übergang zu ermöglichen, wie im folgenden Beispiel:

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({
        /* Formulardaten */
      }),
    }).then((res) => {
      // Führen Sie einen schnellen clientseitigen Übergang zur bereits vorab geladenen Dashboard-Seite durch
      if (res.ok) router.push('/dashboard')
    })
  }, [])

  useEffect(() => {
    // Dashboard-Seite vorab laden
    router.prefetch('/dashboard')
  }, [router])

  return (
    <form onSubmit={handleSubmit}>
      {/* Formularfelder */}
      <button type="submit">Login</button>
    </form>
  )
}

router.beforePopState

In einigen Fällen (z.B. bei Verwendung eines Custom Servers) möchten Sie möglicherweise auf popstate hören und etwas tun, bevor der Router darauf reagiert.

router.beforePopState(cb)
  • cb - Die Funktion, die bei eingehenden popstate-Ereignissen ausgeführt werden soll. Die Funktion erhält den Zustand des Ereignisses als Objekt mit den folgenden Eigenschaften:
    • url: String - Die Route für den neuen Zustand. Dies ist normalerweise der Name einer page.
    • as: String - Die URL, die im Browser angezeigt wird.
    • options: Object - Zusätzliche Optionen, die von router.push gesendet werden.

Wenn cb false zurückgibt, wird der Next.js-Router popstate nicht behandeln, und Sie sind dafür verantwortlich, es in diesem Fall zu behandeln. Siehe Deaktivieren des Dateisystem-Routings.

Sie können beforePopState verwenden, um die Anfrage zu manipulieren oder ein SSR-Refresh zu erzwingen, wie im folgenden Beispiel:

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

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

  useEffect(() => {
    router.beforePopState(({ url, as, options }) => {
      // Ich möchte nur diese beiden Routen zulassen!
      if (as !== '/' && as !== '/other') {
        // Lassen Sie SSR ungültige Routen als 404 rendern.
        window.location.href = as
        return false
      }

      return true
    })
  }, [router])

  return <p>Willkommen auf der Seite</p>
}

router.back

Navigieren Sie in der Historie zurück. Entspricht dem Klicken auf die Zurück-Schaltfläche des Browsers. Führt window.history.back() aus.

import { useRouter } from 'next/router'

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

  return (
    <button type="button" onClick={() => router.back()}>
      Klicken Sie hier, um zurückzugehen
    </button>
  )
}

router.reload

Laden Sie die aktuelle URL neu. Entspricht dem Klicken auf die Aktualisieren-Schaltfläche des Browsers. Führt window.location.reload() aus.

import { useRouter } from 'next/router'

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

  return (
    <button type="button" onClick={() => router.reload()}>
      Klicken Sie hier, um neu zu laden
    </button>
  )
}

router.events

Sie können verschiedene Ereignisse im Next.js-Router abhören. Hier ist eine Liste der unterstützten Ereignisse:

  • routeChangeStart(url, { shallow }) - Wird ausgelöst, wenn eine Route beginnt, sich zu ändern.
  • routeChangeComplete(url, { shallow }) - Wird ausgelöst, wenn eine Route vollständig geändert wurde.
  • routeChangeError(err, url, { shallow }) - Wird ausgelöst, wenn ein Fehler beim Ändern der Routen auftritt oder ein Routenladen abgebrochen wird.
    • err.cancelled - Gibt an, ob die Navigation abgebrochen wurde.
  • beforeHistoryChange(url, { shallow }) - Wird ausgelöst, bevor die Browser-Historie geändert wird.
  • hashChangeStart(url, { shallow }) - Wird ausgelöst, wenn sich der Hash ändert, aber nicht die Seite.
  • hashChangeComplete(url, { shallow }) - Wird ausgelöst, wenn sich der Hash geändert hat, aber nicht die Seite.

Wichtig: Hier ist url die im Browser angezeigte URL, einschließlich des basePath.

Um beispielsweise auf das Router-Ereignis routeChangeStart zu hören, öffnen oder erstellen Sie pages/_app.js und abonnieren Sie das Ereignis wie folgt:

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(
        `App wechselt zu ${url} ${
          shallow ? 'mit' : 'ohne'
        } shallow routing`
      )
    }

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

    // Wenn die Komponente ausgehängt wird, abonnieren Sie
    // das Ereignis mit der `off`-Methode:
    return () => {
      router.events.off('routeChangeStart', handleRouteChange)
    }
  }, [router])

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

Wir verwenden eine Custom App (pages/_app.js) für dieses Beispiel, um das Ereignis zu abonnieren, weil sie bei Seitenwechseln nicht ausgehängt wird. Sie können jedoch Router-Ereignisse in jeder Komponente Ihrer Anwendung abonnieren.

Router-Ereignisse sollten registriert werden, wenn eine Komponente eingehängt wird (useEffect oder componentDidMount / componentWillUnmount) oder imperativ, wenn ein Ereignis eintritt.

Wenn ein Routenladen abgebrochen wird (z.B. durch schnelles Klicken auf zwei Links hintereinander), wird routeChangeError ausgelöst. Das übergebene err enthält eine Eigenschaft cancelled, die auf true gesetzt ist, wie im folgenden Beispiel:

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(`Route zu ${url} wurde abgebrochen!`)
      }
    }

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

    // Wenn die Komponente ausgehängt wird, abonnieren Sie
    // das Ereignis mit der `off`-Methode:
    return () => {
      router.events.off('routeChangeError', handleRouteChangeError)
    }
  }, [router])

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

Der next/compat/router-Export

Dies ist derselbe useRouter-Hook, kann jedoch sowohl in app- als auch pages-Verzeichnissen verwendet werden.

Er unterscheidet sich von next/router dadurch, dass er keinen Fehler auslöst, wenn der Pages-Router nicht eingebunden ist, und stattdessen einen Rückgabetyp von NextRouter | null hat.
Dies ermöglicht Entwicklern, Komponenten so anzupassen, dass sie sowohl in app als auch pages funktionieren, während sie zum app-Router migrieren.

Eine Komponente, die zuvor so aussah:

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

Wird beim Wechsel zu next/compat/router einen Fehler verursachen, da null nicht destrukturiert werden kann. Stattdessen können Entwickler neue Hooks nutzen:

import { useEffect } from 'react'
import { useRouter } from 'next/compat/router'
import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
  const router = useRouter() // kann null oder eine NextRouter-Instanz sein
  const searchParams = useSearchParams()
  useEffect(() => {
    if (router && !router.isReady) {
      return
    }
    // In `app/` sind searchParams sofort mit Werten verfügbar, in
    // `pages/` erst nachdem der Router bereit ist.
    const search = searchParams.get('search')
    // ...
  }, [router, searchParams])
  // ...
}

Diese Komponente funktioniert nun in beiden Verzeichnissen (pages und app). Wenn die Komponente nicht mehr in pages verwendet wird, können die Verweise auf den Compat-Router entfernt werden:

import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
  const searchParams = useSearchParams()
  // Da diese Komponente nur in `app/` verwendet wird, kann der Compat-Router entfernt werden.
  const search = searchParams.get('search')
  // ...
}

Verwendung von useRouter außerhalb des Next.js-Kontexts in Pages

Ein spezieller Anwendungsfall ist das Rendern von Komponenten außerhalb eines Next.js-Anwendungskontexts, z.B. innerhalb von getServerSideProps im pages-Verzeichnis. Hier kann der Compat-Router verwendet werden, um Fehler zu vermeiden:

import { renderToString } from 'react-dom/server'
import { useRouter } from 'next/compat/router'
const MyComponent = () => {
  const router = useRouter() // kann null oder eine NextRouter-Instanz sein
  // ...
}
export async function getServerSideProps() {
  const renderedComponent = renderToString(<MyComponent />)
  return {
    props: {
      renderedComponent,
    },
  }
}

Potenzielle ESLint-Fehler

Bestimmte Methoden des router-Objekts geben ein Promise zurück. Wenn die ESLint-Regel no-floating-promises aktiviert ist, sollte sie entweder global oder für die betroffene Zeile deaktiviert werden.

Falls diese Regel benötigt wird, sollte das Promise entweder mit void ignoriert werden – oder eine async-Funktion verwendet werden, die das Promise mit await behandelt und dann der Funktionsaufruf mit void ignoriert wird. Dies gilt nicht, wenn die Methode innerhalb eines onClick-Handlers aufgerufen wird.

Betroffene Methoden sind:

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

Mögliche Lösungen

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

// Hier würde der Benutzer abgefragt und zurückgegeben werden
const useUser = () => ({ user: null, loading: false })

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

  useEffect(() => {
    // Linting für die nächste Zeile deaktivieren - Dies ist die sauberste Lösung
    // eslint-disable-next-line no-floating-promises
    router.push('/login')

    // Promise von router.push mit void ignorieren
    if (!(user || loading)) {
      void router.push('/login')
    }
    // oder async-Funktion verwenden, Promise mit await behandeln, dann Funktionsaufruf mit void ignorieren
    async function handleRouteChange() {
      if (!(user || loading)) {
        await router.push('/login')
      }
    }
    void handleRouteChange()
  }, [user, loading])

  return <p>Weiterleiten...</p>
}

withRouter

Falls useRouter nicht die beste Option ist, kann withRouter dasselbe router-Objekt zu jeder Komponente hinzufügen.

Verwendung

import { withRouter } from 'next/router'

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

export default withRouter(Page)

TypeScript

Für Klassenkomponenten mit withRouter muss die Komponente eine router-Prop akzeptieren:

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)