Menu

リンクとナビゲーション

Next.jsでルート間を移動する方法は4つあります:

このページでは、これらの各オプションの使用方法と、ナビゲーションの仕組みについて詳しく説明します。

<Link>は、HTML <a> タグを拡張し、ルート間のプリフェッチとクライアントサイドのナビゲーションを提供する組み込みコンポーネントです。Next.jsでルート間を移動する主要かつ推奨される方法です。

next/linkからインポートし、コンポーネントにhrefプロパティを渡すことで使用できます:

app/page.tsx
TypeScript
import Link from 'next/link'
 
export default function Page() {
  return <Link href="/dashboard">Dashboard</Link>
}

<Link>に渡せる他のオプションのプロパティもあります。詳細はAPIリファレンスを参照してください。

useRouter() フック

useRouterフックにより、クライアントコンポーネントからプログラム的にルートを変更できます。

app/page.js
'use client'
 
import { useRouter } from 'next/navigation'
 
export default function Page() {
  const router = useRouter()
 
  return (
    <button type="button" onClick={() => router.push('/dashboard')}>
      Dashboard
    </button>
  )
}

useRouterメソッドの完全なリストについては、APIリファレンスを参照してください。

推奨: 特別な要件がない限り、ルート間の移動には<Link>コンポーネントを使用してください。

redirect関数

サーバーコンポーネントでは、代わりにredirect関数を使用します。

app/team/[id]/page.tsx
TypeScript
import { redirect } from 'next/navigation'
 
async function fetchTeam(id: string) {
  const res = await fetch('https://...')
  if (!res.ok) return undefined
  return res.json()
}
 
export default async function Profile({ params }: { params: { id: string } }) {
  const team = await fetchTeam(params.id)
  if (!team) {
    redirect('/login')
  }
 
  // ...
}

補足

  • redirectはデフォルトで307(一時的リダイレクト)ステータスコードを返します。サーバーアクション内で使用する場合、303(他を参照)を返します。これは通常、POSTリクエストの結果として成功ページにリダイレクトする際に使用されます。
  • redirectは内部的にエラーをスローするため、try/catchブロック外で呼び出す必要があります。
  • redirectはクライアントコンポーネントのレンダリングプロセス中に呼び出せますが、イベントハンドラー内では呼び出せません。代わりにuseRouterフックを使用してください。
  • redirectは絶対URLも受け入れ、外部リンクにリダイレクトするために使用できます。
  • レンダリングプロセスの前にリダイレクトする場合は、next.config.jsまたはミドルウェアを使用してください。

詳細はredirect APIリファレンスを参照してください。

ネイティブHistory APIの使用

Next.jsでは、ネイティブのwindow.history.pushStatewindow.history.replaceStateメソッドを使用して、ページをリロードせずにブラウザの履歴スタックを更新できます。

pushStatereplaceStateの呼び出しは、Next.jsルーターと統合されており、usePathnameuseSearchParamsと同期できます。

window.history.pushState

ブラウザの履歴スタックに新しいエントリを追加するために使用します。ユーザーは前の状態に戻ることができます。例えば、製品のリストをソートする場合:

'use client'
 
import { useSearchParams } from 'next/navigation'
 
export default function SortProducts() {
  const searchParams = useSearchParams()
 
  function updateSorting(sortOrder: string) {
    const params = new URLSearchParams(searchParams.toString())
    params.set('sort', sortOrder)
    window.history.pushState(null, '', `?${params.toString()}`)
  }
 
  return (
    <>
      <button onClick={() => updateSorting('asc')}>昇順でソート</button>
      <button onClick={() => updateSorting('desc')}>降順でソート</button>
    </>
  )
}
'use client'
 
import { useSearchParams } from 'next/navigation'
 
export default function SortProducts() {
  const searchParams = useSearchParams()
 
  function updateSorting(sortOrder) {
    const params = new URLSearchParams(searchParams.toString())
    params.set('sort', sortOrder)
    window.history.pushState(null, '', `?${params.toString()}`)
  }
 
  return (
    <>
      <button onClick={() => updateSorting('asc')}>昇順でソート</button>
      <button onClick={() => updateSorting('desc')}>降順でソート</button>
    </>
  )
}

window.history.replaceState

ブラウザの履歴スタックの現在のエントリを置き換えるために使用します。ユーザーは前の状態に戻ることができません。例えば、アプリケーションのロケールを切り替える場合:

'use client'
 
import { usePathname } from 'next/navigation'
 
export function LocaleSwitcher() {
  const pathname = usePathname()
 
  function switchLocale(locale: string) {
    // 例:'/en/about' または '/fr/contact'
    const newPath = `/${locale}${pathname}`
    window.history.replaceState(null, '', newPath)
  }
 
  return (
    <>
      <button onClick={() => switchLocale('en')}>英語</button>
      <button onClick={() => switchLocale('fr')}>フランス語</button>
    </>
  )
}
'use client'
 
import { usePathname } from 'next/navigation'
 
export function LocaleSwitcher() {
  const pathname = usePathname()
 
  function switchLocale(locale) {
    // 例:'/en/about' または '/fr/contact'
    const newPath = `/${locale}${pathname}`
    window.history.replaceState(null, '', newPath)
  }
 
  return (
    <>
      <button onClick={() => switchLocale('en')}>英語</button>
      <button onClick={() => switchLocale('fr')}>フランス語</button>
    </>
  )
}

ルーティングとナビゲーションの仕組み

App Routerは、ルーティングとナビゲーションにハイブリッドアプローチを使用します。サーバー側では、アプリケーションコードがルートセグメントごとに自動的にコード分割されます。クライアント側では、Next.jsがルートセグメントをプリフェッチし、キャッシュします。つまり、ユーザーが新しいルートに移動しても、ブラウザはページをリロードせず、変更されたルートセグメントのみが再レンダリングされます - これにより、ナビゲーション体験とパフォーマンスが向上します。

1. コード分割

コード分割により、アプリケーションコードをブラウザでダウンロードおよび実行される小さなバンドルに分割できます。これにより、各リクエストで転送されるデータ量と実行時間が削減され、パフォーマンスが向上します。

サーバーコンポーネントにより、アプリケーションコードはルートセグメント別に自動的にコード分割されます。つまり、現在のルートに必要なコードのみがナビゲーション時にロードされます。

2. プリフェッチ

プリフェッチは、ユーザーが訪れる前にバックグラウンドでルートを事前に読み込む方法です。

Next.jsでは、ルートは2つの方法でプリフェッチされます:

  • <Link> コンポーネント:ルートは、ユーザーのビューポート内に表示されると自動的にプリフェッチされます。プリフェッチは、ページが最初にロードされたとき、またはスクロールによって表示されたときに行われます。
  • router.prefetch()useRouterフックを使用して、プログラム的にルートをプリフェッチできます。

<Link>のデフォルトのプリフェッチ動作(prefetchプロパティが未指定またはnullに設定されている場合)は、loading.jsの使用方法によって異なります。最初のloading.jsファイルまでのコンポーネントの「ツリー」に沿って、共有レイアウトのみが30秒間プリフェッチおよびキャッシュされます。これにより、動的ルート全体をフェッチするコストが削減され、ユーザーにより良いビジュアルフィードバックを提供するためにインスタントローディング状態を表示できます。

prefetchプロパティをfalseに設定することで、プリフェッチを無効にできます。または、prefetchプロパティをtrueに設定することで、ロード境界を超えたページ全体のデータをプリフェッチできます。

詳細については、<Link> APIリファレンスを参照してください。

補足

  • プリフェッチは開発モードでは無効で、本番モードでのみ有効です。

3. キャッシュ

Next.jsには、ルーターキャッシュと呼ばれるインメモリのクライアント側キャッシュがあります。ユーザーがアプリ内を移動する際、プリフェッチされたルートセグメントと訪問されたルートのReactサーバーコンポーネントペイロードがキャッシュに保存されます。

これは、ナビゲーション時にキャッシュが可能な限り再利用され、サーバーへの新しいリクエストが行われないことを意味し、リクエスト数と転送されるデータを削減することでパフォーマンスが向上します。

ルーターキャッシュの仕組みとその設定方法について詳しく学びましょう。

4. 部分的レンダリング

部分的レンダリングとは、ナビゲーション時に変更されるルートセグメントのみがクライアント上で再レンダリングされ、共有セグメントが保持されることを意味します。

例えば、/dashboard/settings/dashboard/analyticsのような2つの兄弟ルート間を移動する場合、settingsページはアンマウントされ、analyticsページは新しい状態でマウントされ、共有されるdashboardレイアウトは保持されます。この動作は、/blog/[slug]/pageのような同じ動的セグメント上の2つのルート(/blog/firstから/blog/secondへ移動)の間にも存在します。

部分的レンダリングの仕組み

部分的レンダリングがない場合、各ナビゲーションでクライアント上でページ全体が再レンダリングされます。変更されるセグメントのみをレンダリングすることで、転送されるデータ量と実行時間が削減され、パフォーマンスが向上します。

5. ソフトナビゲーション

ブラウザはページ間を移動する際に「ハードナビゲーション」を実行します。Next.jsのAppルーターは、ページ間の「ソフトナビゲーション」を可能にし、変更されたルートセグメントのみが再レンダリングされます(部分的レンダリング)。これにより、ナビゲーション中にクライアントのReactステートが保持されます。

6. 戻る・進むナビゲーション

デフォルトで、Next.jsは戻る・進むナビゲーションのスクロール位置を維持し、ルーターキャッシュのルートセグメントを再利用します。

7. pages/app/間のルーティング

pages/からapp/に段階的に移行する場合、Next.jsルーターはpages/app/間のハードナビゲーションを自動的に処理します。pages/からapp/への遷移を検出するために、アプリルートの確率的チェックを活用するクライアントルーターフィルターがあり、これは時折偽陽性を生じる可能性があります。デフォルトでは、このような発生は非常にまれで、偽陽性の可能性を0.01%に設定しています。この可能性は、next.config.jsexperimental.clientRouterFilterAllowedRateオプションでカスタマイズできます。偽陽性率を下げると、クライアントバンドルに生成されるフィルターのサイズが増加することに注意してください。

代替として、このハンドリングを完全に無効にしてpages/app/間のルーティングを手動で管理する場合は、next.config.jsexperimental.clientRouterFilterfalseに設定できます。この機能が無効の場合、pages内の動的ルートと重複するアプリルートには、デフォルトで適切にナビゲーションできません。