useRouter

アプリケーション内の任意の関数コンポーネントでrouterオブジェクトにアクセスしたい場合、useRouterフックを使用できます。以下の例をご覧ください:

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

useRouterReact Hookであり、クラスコンポーネントでは使用できません。クラスコンポーネントではwithRouterを使用するか、関数コンポーネントでラップしてください。

routerオブジェクト

以下はuseRouterwithRouterの両方から返されるrouterオブジェクトの定義です:

  • pathname: String - /pages以下の現在のルートファイルのパス。basePathlocale、末尾スラッシュ(trailingSlash: true)は含まれません。
  • query: Object - クエリ文字列をオブジェクトにパースしたもの。動的ルートパラメータを含みます。ページがサーバーサイドレンダリング (SSR)を使用していない場合、プリレンダリング中は空のオブジェクトになります。デフォルトは{}
  • asPath: String - ブラウザに表示されるパスで、検索パラメータを含み、trailingSlash設定を尊重します。basePathlocaleは含まれません。
  • isFallback: boolean - 現在のページがフォールバックモードかどうか。
  • basePath: String - 有効なbasePath(有効な場合)。
  • locale: String - 有効なロケール(有効な場合)。
  • locales: String[] - サポートされているすべてのロケール(有効な場合)。
  • defaultLocale: String - 現在のデフォルトロケール(有効な場合)。
  • domainLocales: Array<{domain, defaultLocale, locales}> - 設定されたドメインロケール。
  • isReady: boolean - ルーターフィールドがクライアントサイドで更新され、使用準備ができているかどうか。useEffect内でのみ使用し、サーバーでの条件付きレンダリングには使用しないでください。自動静的最適化ページのユースケースに関連するドキュメントを参照してください。
  • isPreview: boolean - アプリケーションが現在プレビューモードかどうか。

asPathフィールドを使用すると、ページがサーバーサイドレンダリングまたは自動静的最適化でレンダリングされている場合、クライアントとサーバーの間に不一致が生じる可能性があります。isReadyフィールドがtrueになるまでasPathの使用を避けてください。

routerには以下のメソッドが含まれています:

router.push

クライアントサイドの遷移を処理します。next/linkでは不十分な場合に便利です。

router.push(url, as, options)
  • url: UrlObject | String - ナビゲート先のURL(UrlObjectプロパティについてはNode.JS URLモジュールドキュメントを参照)。
  • as: UrlObject | String - ブラウザのURLバーに表示されるパスのオプショナルなデコレータ。Next.js 9.5.3以前では動的ルートに使用されていました。
  • options - 以下の設定オプションを持つオプショナルなオブジェクト:
    • scroll - オプショナルなブール値で、ナビゲーション後にページのトップにスクロールするかどうかを制御します。デフォルトはtrue
    • shallow: getStaticPropsgetServerSidePropsgetInitialPropsを再実行せずに現在のページのパスを更新します。デフォルトはfalse
    • locale - オプショナルな文字列で、新しいページのロケールを示します

外部URLにはrouter.pushを使用する必要はありません。window.locationの方が適しています。

定義済みルートpages/about.jsへのナビゲーション:

import { useRouter } from 'next/router'

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

  return (
    <button type="button" onClick={() => router.push('/about')}>
      クリックしてください
    </button>
  )
}

動的ルートpages/post/[pid].jsへのナビゲーション:

import { useRouter } from 'next/router'

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

  return (
    <button type="button" onClick={() => router.push('/post/abc')}>
      クリックしてください
    </button>
  )
}

認証が必要なページの場合、ユーザーをpages/login.jsにリダイレクト:

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

// ここでユーザーを取得して返す
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>リダイレクト中...</p>
}

ナビゲーション後の状態リセット

Next.jsで同じページにナビゲートする場合、親コンポーネントが変更されない限り、Reactはアンマウントしないため、ページの状態はデフォルトでリセットされません

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>ページ: {router.query.slug}</h1>
      <p>カウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>カウント増加</button>
      <Link href="/one">one</Link> <Link href="/two">two</Link>
    </div>
  )
}

上記の例では、/one/two間のナビゲーションでカウントはリセットされません。トップレベルのReactコンポーネントPageが同じであるため、useStateはレンダリング間で維持されます。

この動作を望まない場合は、以下のオプションがあります:

  • useEffectを使用して各状態を手動で更新します。上記の例では次のようになります:

    useEffect(() => {
      setCount(0)
    }, [router.query.slug])
  • Reactのkeyを使用してコンポーネントの再マウントをReactに指示します。すべてのページでこれを行うには、カスタムアプリを使用します:

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

URLオブジェクトの使用

next/linkと同様にURLオブジェクトを使用できます。urlasパラメータの両方で動作します:

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 },
        })
      }}
    >
      詳細を読む
    </button>
  )
}

router.replace

next/linkreplaceプロパティと同様に、router.replaceは新しいURLエントリをhistoryスタックに追加しません。

router.replace(url, as, options)
  • router.replaceのAPIはrouter.pushとまったく同じです。

以下の例をご覧ください:

import { useRouter } from 'next/router'

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

  return (
    <button type="button" onClick={() => router.replace('/home')}>
      クリックしてください
    </button>
  )
}

router.prefetch

クライアントサイドの遷移を高速化するためにページをプリフェッチします。このメソッドはnext/linkを使用しないナビゲーションにのみ有用です。next/linkは自動的にページをプリフェッチします。

これは本番環境のみの機能です。Next.jsは開発環境ではページをプリフェッチしません。

router.prefetch(url, as, options)
  • url - プリフェッチするURL。明示的なルート(例:/dashboard)と動的ルート(例:/product/[id])を含みます。
  • as - urlのオプショナルなデコレータ。Next.js 9.5.3以前では動的ルートのプリフェッチに使用されていました。
  • options - 以下の許可されたフィールドを持つオプショナルなオブジェクト:
    • locale - アクティブなロケールとは異なるロケールを提供できます。falseの場合、urlにはロケールを含める必要があります。

ログインページがあり、ログイン後にユーザーをダッシュボードにリダイレクトする場合を考えます。その場合、以下の例のようにダッシュボードをプリフェッチして遷移を高速化できます:

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({
        /* フォームデータ */
      }),
    }).then((res) => {
      // すでにプリフェッチされたダッシュボードページへの高速クライアントサイド遷移
      if (res.ok) router.push('/dashboard')
    })
  }, [])

  useEffect(() => {
    // ダッシュボードページをプリフェッチ
    router.prefetch('/dashboard')
  }, [router])

  return (
    <form onSubmit={handleSubmit}>
      {/* フォームフィールド */}
      <button type="submit">ログイン</button>
    </form>
  )
}

router.beforePopState

カスタムサーバーを使用している場合など、ルーターが処理する前にpopstateをリッスンして何かを行いたい場合があります。

router.beforePopState(cb)
  • cb - 受信したpopstateイベントで実行する関数。この関数はイベントの状態を以下のプロパティを持つオブジェクトとして受け取ります:
    • url: String - 新しい状態のルート。通常はpageの名前です。
    • as: String - ブラウザに表示されるURL。
    • options: Object - router.pushから送信された追加オプション。

cbfalseを返す場合、Next.jsルーターはpopstateを処理せず、その場合は自分で処理する必要があります。ファイルシステムルーティングの無効化を参照してください。

beforePopStateを使用してリクエストを操作したり、SSRのリフレッシュを強制したりできます。以下の例をご覧ください:

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

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

  useEffect(() => {
    router.beforePopState(({ url, as, options }) => {
      // 許可したいルートはこれだけ!
      if (as !== '/' && as !== '/other') {
        // SSRで不正なルートを404としてレンダリング
        window.location.href = as
        return false
      }

      return true
    })
  }, [router])

  return <p>ページへようこそ</p>
}

router.back

履歴を戻ります。ブラウザの戻るボタンをクリックするのと同じです。window.history.back()を実行します。

import { useRouter } from 'next/router'

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

  return (
    <button type="button" onClick={() => router.back()}>
      戻る
    </button>
  )
}

router.reload

現在のURLをリロードします。ブラウザの更新ボタンをクリックするのと同じです。window.location.reload()を実行します。

import { useRouter } from 'next/router'

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

  return (
    <button type="button" onClick={() => router.reload()}>
      再読み込み
    </button>
  )
}

router.events

Next.jsルーター内で発生するさまざまなイベントをリッスンできます。以下はサポートされているイベントのリストです:

  • routeChangeStart(url, { shallow }) - ルートの変更が開始されたときに発火
  • routeChangeComplete(url, { shallow }) - ルートが完全に変更されたときに発火
  • routeChangeError(err, url, { shallow }) - ルート変更中にエラーが発生した場合、またはルートの読み込みがキャンセルされたときに発火
    • err.cancelled - ナビゲーションがキャンセルされたかどうかを示します
  • beforeHistoryChange(url, { shallow }) - ブラウザの履歴を変更する前に発火
  • hashChangeStart(url, { shallow }) - ハッシュが変更されようとしているがページは変更されないときに発火
  • hashChangeComplete(url, { shallow }) - ハッシュが変更されたがページは変更されていないときに発火

知っておくと良いこと: ここでのurlbasePathを含むブラウザに表示されるURLです。

例えば、ルーターイベントrouteChangeStartをリッスンするには、pages/_app.jsを開くか作成し、以下のようにイベントを購読します:

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(
        `アプリが${url}に変更されています ${
          shallow ? 'シャロー' : 'ディープ'
        }ルーティング`
      )
    }

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

    // コンポーネントがアンマウントされたら、
    // `off`メソッドでイベントの購読を解除:
    return () => {
      router.events.off('routeChangeStart', handleRouteChange)
    }
  }, [router])

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

この例ではカスタムApppages/_app.js)を使用しています。これはページナビゲーション時にアンマウントされないためですが、アプリケーション内の任意のコンポーネントでルーターイベントを購読できます。

ルーターイベントはコンポーネントがマウントされたとき(useEffectまたはcomponentDidMount / componentWillUnmount)またはイベントが発生したときに命令的に登録する必要があります。

ルートの読み込みがキャンセルされた場合(例えば、2つのリンクを連続して素早くクリックした場合)、routeChangeErrorが発火します。渡されるerrにはcancelledプロパティがtrueに設定されます。以下の例をご覧ください:

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(`${url}へのルートがキャンセルされました!`)
      }
    }

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

    // コンポーネントがアンマウントされたら、
    // `off`メソッドでイベントの購読を解除:
    return () => {
      router.events.off('routeChangeError', handleRouteChangeError)
    }
  }, [router])

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

潜在的なESLintエラー

routerオブジェクトで利用可能な特定のメソッドはPromiseを返します。no-floating-promises ESLintルールが有効な場合、グローバルに無効にするか、影響を受ける行のみで無効にすることを検討してください。

アプリケーションでこのルールが必要な場合は、Promiseをvoidで処理するか、async関数を使用してPromiseをawaitし、関数呼び出しをvoidにする必要があります。ただし、onClickハンドラ内からメソッドが呼び出される場合は適用されません

影響を受けるメソッド:

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

解決策の例

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

// ここでユーザーデータを取得して返す
const useUser = () => ({ user: null, loading: false })

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

  useEffect(() => {
    // 次の行のリンターを無効化 - 最も簡潔な解決策
    // eslint-disable-next-line no-floating-promises
    router.push('/login')

    // router.pushが返すPromiseをvoidで処理
    if (!(user || loading)) {
      void router.push('/login')
    }
    // またはasync関数を使用し、Promiseをawaitしてから関数呼び出しをvoidにする
    async function handleRouteChange() {
      if (!(user || loading)) {
        await router.push('/login')
      }
    }
    void handleRouteChange()
  }, [user, loading])

  return <p>リダイレクト中...</p>
}

withRouter

useRouterが適さない場合、withRouterを使用して同じrouterオブジェクトを任意のコンポーネントに追加できます。

使用方法

import { withRouter } from 'next/router'

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

export default withRouter(Page)

TypeScriptの場合

クラスコンポーネントでwithRouterを使用するには、コンポーネントが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)