コンテンツにスキップ

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 - 動的ルートパラメータを含む、オブジェクトに解析されたクエリ文字列。ページがサーバーサイドレンダリングを使用しない場合、プリレンダリング中は空のオブジェクトになります。デフォルトは{}です。
  • 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: getStaticPropsgetServerSideProps、またはgetInitialPropsを再実行せずに、現在のページのパスを更新します。デフォルトは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'
 
// Here you would fetch and return the user
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で同じページに移動する場合、親コンポーネントが変更されない限り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>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.replacehistoryスタックに新しいURLエントリが追加されるのを防ぎます。

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')}>
      Click me
    </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({
        /* Form data */
      }),
    }).then((res) => {
      // Do a fast client-side transition to the already prefetched dashboard page
      if (res.ok) router.push('/dashboard')
    })
  }, [])
 
  useEffect(() => {
    // Prefetch the dashboard page
    router.prefetch('/dashboard')
  }, [router])
 
  return (
    <form onSubmit={handleSubmit}>
      {/* Form fields */}
      <button type="submit">Login</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 }) => {
      // I only want to allow these two routes!
      if (as !== '/' && as !== '/other') {
        // Have SSR render bad routes as a 404.
        window.location.href = as
        return false
      }
 
      return true
    })
  }, [router])
 
  return <p>Welcome to the page</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()}>
      Click here to go back
    </button>
  )
}

router.reload
import { useRouter } from 'next/router'
 
export default function Page() {
  const router = useRouter()
 
  return (
    <button type="button" onClick={() => router.reload()}>
      Click here to reload
    </button>
  )
}

router.events

**知っておくと良いこと**: ここでのurlは、basePathを含む、ブラウザに表示される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(
        `App is changing to ${url} ${
          shallow ? 'with' : 'without'
        } shallow routing`
      )
    }
 
    router.events.on('routeChangeStart', handleRouteChange)
 
    // If the component is unmounted, unsubscribe
    // from the event with the `off` method:
    return () => {
      router.events.off('routeChangeStart', handleRouteChange)
    }
  }, [router])
 
  return <Component {...pageProps} />
}

この例では、ページナビゲーションでアンマウントされないため、カスタムアプリpages/_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(`Route to ${url} was cancelled!`)
      }
    }
 
    router.events.on('routeChangeError', handleRouteChangeError)
 
    // If the component is unmounted, unsubscribe
    // from the event with the `off` method:
    return () => {
      router.events.off('routeChangeError', handleRouteChangeError)
    }
  }, [router])
 
  return <Component {...pageProps} />
}

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

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

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

以前はこのように見えたコンポーネント

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() // may be null or a NextRouter instance
  const searchParams = useSearchParams()
  useEffect(() => {
    if (router && !router.isReady) {
      return
    }
    // In `app/`, searchParams will be ready immediately with the values, in
    // `pages/` it will be available after the router is ready.
    const search = searchParams.get('search')
    // ...
  }, [router, searchParams])
  // ...
}

このコンポーネントは、`pages` ディレクトリと `app` ディレクトリの両方で動作するようになります。コンポーネントが `pages` で使用されなくなったら、互換ルーターへの参照を削除できます。

import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
  const searchParams = useSearchParams()
  // As this component is only used in `app/`, the compat router can be removed.
  const search = searchParams.get('search')
  // ...
}

pages で Next.js コンテキスト外で useRouter を使用する

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

import { renderToString } from 'react-dom/server'
import { useRouter } from 'next/compat/router'
const MyComponent = () => {
  const router = useRouter() // may be null or a NextRouter instance
  // ...
}
export async function getServerSideProps() {
  const renderedComponent = renderToString(<MyComponent />)
  return {
    props: {
      renderedComponent,
    },
  }
}

潜在的な ESLint エラー

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

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

影響を受けるメソッドは次のとおりです。

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

潜在的な解決策

import { useEffect } from 'react'
import { useRouter } from 'next/router'
 
// Here you would fetch and return the user
const useUser = () => ({ user: null, loading: false })
 
export default function Page() {
  const { user, loading } = useUser()
  const router = useRouter()
 
  useEffect(() => {
    // disable the linting on the next line - This is the cleanest solution
    // eslint-disable-next-line no-floating-promises
    router.push('/login')
 
    // void the Promise returned by router.push
    if (!(user || loading)) {
      void router.push('/login')
    }
    // or use an async function, await the Promise, then void the function call
    async function handleRouteChange() {
      if (!(user || loading)) {
        await router.push('/login')
      }
    }
    void handleRouteChange()
  }, [user, loading])
 
  return <p>Redirecting...</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)