Menu

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フックであるため、クラスでは使用できません。withRouterを使用するか、クラスを関数コンポーネントでラップできます。

routerオブジェクト

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

  • pathname: String - /pagesの後に続く現在のルートファイルのパス。したがって、basePathlocale、末尾のスラッシュ(trailingSlash: true)は含まれません。
  • query: Object - オブジェクトに解析されたクエリ文字列。動的ルートのパラメーターを含みます。ページがサーバーサイドレンダリングを使用しない場合、プリレンダリング中は空のオブジェクトになります。デフォルトは{}
  • 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')}>
      Click me
    </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')}>
      Click me
    </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>Redirecting...</p>
}

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

Next.jsで同じページに遷移する場合、親コンポーネントが変更されていないため、デフォルトでページの状態はリセットされません。

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>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increase count</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 },
        })
      }}
    >
      Click here to read more
    </button>
  )
}

router.replace

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

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

次の例を参照してください:

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 - 明示的なルート(例: /dashboard)と動的ルート(例: /product/[id])を含む、先読みするURL
  • 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 }) => {
      // これら2つのルートのみを許可したい!
      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 }) - ハッシュが変更されたが、ページは変更されない場合に発生

補足: ここで url はブラウザに表示されるURL であり、basePath を含みます。

例えば、ルーターイベント 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} />
}

next/compat/router のエクスポート

これは同じ useRouter フックですが、apppages の両方のディレクトリで使用できます。

next/router とは異なり、pagesルーターがマウントされていない場合にエラーをスローせず、代わりに戻り値の型が NextRouter | null になります。 これにより、開発者は app ルーターに移行する際に、両方の apppages で実行できるようにコンポーネントを変換できます。

以前は次のようだったコンポーネントが:

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

next/compat/routerに変換する際にエラーが発生します。nullは分割代入できないためです。代わりに、開発者は新しいフックを活用できます:

import { useEffect } from 'react'
import { useRouter } from 'next/compat/router'
import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
  const router = useRouter() // null または NextRouter インスタンス
  const searchParams = useSearchParams()
  useEffect(() => {
    if (router && !router.isReady) {
      return
    }
    // `app/`では、searchParamsは直ちに値と共に準備され、
    // `pages/`ではルーターの準備が整った後に利用可能になります。
    const search = searchParams.get('search')
    // ...
  }, [router, searchParams])
  // ...
}

このコンポーネントは、現在pagesappディレクトリの両方で動作します。コンポーネントがpagesで使用されなくなった場合、互換性のあるルーターへの参照を削除できます:

import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
  const searchParams = useSearchParams()
  // このコンポーネントは`app/`でのみ使用されるため、互換性のあるルーターを削除できます。
  const search = searchParams.get('search')
  // ...
}

Next.jsコンテキスト外でのuseRouterの使用(pagesディレクトリ)

もう1つの特定のユースケースは、pagesディレクトリのgetServerSideProps内など、Next.jsアプリケーションコンテキスト外でコンポーネントをレンダリングする場合です。この場合、エラーを回避するために互換性のあるルーターを使用できます:

import { renderToString } from 'react-dom/server'
import { useRouter } from 'next/compat/router'
const MyComponent = () => {
  const router = useRouter() // null または NextRouter インスタンス
  // ...
}
export async function getServerSideProps() {
  const renderedComponent = renderToString(<MyComponent />)
  return {
    props: {
      renderedComponent,
    },
  }
}

潜在的なESLintエラー

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

アプリケーションにこのルールが必要な場合、Promiseをvoidにするか、async関数でawaitしてから関数呼び出しを無効にする必要があります。これは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を待ってから関数呼び出しを無効にする
    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を使用するには、コンポーネントがルーターのプロパティを受け入れる必要があります:

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)