リンクとナビゲーション

Next.jsでは、以下の4つの方法でルート間をナビゲートできます:

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

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

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

import Link from 'next/link'

export default function Page() {
  return <Link href="/dashboard">ダッシュボード</Link>
}
import Link from 'next/link'

export default function Page() {
  return <Link href="/dashboard">ダッシュボード</Link>
}

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

使用例

ダイナミックセグメントへのリンク

ダイナミックセグメントにリンクする場合、テンプレートリテラルと補間を使用してリンクのリストを生成できます。例えば、ブログ記事のリストを生成する場合:

app/blog/PostList.js
import Link from 'next/link'

export default function PostList({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}

アクティブリンクの確認

usePathname()を使用してリンクがアクティブかどうかを判断できます。例えば、アクティブなリンクにクラスを追加するには、現在の pathname がリンクの href と一致するかどうかを確認します:

'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function Links() {
  const pathname = usePathname()

  return (
    <nav>
      <ul>
        <li>
          <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
            ホーム
          </Link>
        </li>
        <li>
          <Link
            className={`link ${pathname === '/about' ? 'active' : ''}`}
            href="/about"
          >
            アバウト
          </Link>
        </li>
      </ul>
    </nav>
  )
}
'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function Links() {
  const pathname = usePathname()

  return (
    <nav>
      <ul>
        <li>
          <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
            ホーム
          </Link>
        </li>
        <li>
          <Link
            className={`link ${pathname === '/about' ? 'active' : ''}`}
            href="/about"
          >
            アバウト
          </Link>
        </li>
      </ul>
    </nav>
  )
}

id へのスクロール

Next.js App Routerのデフォルト動作は、新しいルートの先頭にスクロールするか、前後のナビゲーションでスクロール位置を維持することです。

ナビゲーション時に特定の id にスクロールしたい場合は、URLに # ハッシュリンクを追加するか、href プロパティにハッシュリンクを渡します。これは <Link><a> 要素としてレンダリングされるため可能です。

<Link href="/dashboard#settings">設定</Link>

// 出力
<a href="/dashboard#settings">設定</a>

豆知識:

  • Next.jsは、ナビゲーション時にビューポート内に表示されていない場合、ページまでスクロールします。

スクロール復元の無効化

Next.js App Routerのデフォルト動作は、新しいルートの先頭にスクロールするか、前後のナビゲーションでスクロール位置を維持することです。この動作を無効にしたい場合は、<Link> コンポーネントに scroll={false} を渡すか、router.push() または router.replace()scroll: false を渡します。

// next/link
<Link href="/dashboard" scroll={false}>
  ダッシュボード
</Link>
// useRouter
import { useRouter } from 'next/navigation'

const router = useRouter()

router.push('/dashboard', { scroll: false })

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')}>
      ダッシュボード
    </button>
  )
}

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

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

redirect 関数

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

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')
  }

  // ...
}
import { redirect } from 'next/navigation'

async function fetchTeam(id) {
  const res = await fetch('https://...')
  if (!res.ok) return undefined
  return res.json()
}

export default async function Profile({ params }) {
  const team = await fetchTeam(params.id)
  if (!team) {
    redirect('/login')
  }

  // ...
}

豆知識:

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

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

ネイティブHistory APIの使用

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

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

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にはRouter Cacheと呼ばれるメモリ内のクライアントサイドキャッシュがあります。ユーザーがアプリ内を移動すると、プリフェッチされたルートセグメントと訪問済みルートのReactサーバーコンポーネントペイロードがキャッシュに保存されます。

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

Router Cacheの仕組みと設定方法について詳しく学びます。

4. 部分レンダリング

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

例えば、2つの兄弟ルート /dashboard/settings/dashboard/analytics 間を移動する場合、settingsanalytics ページがレンダリングされ、共有の dashboard レイアウトが保持されます。

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

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

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

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

6. 前後ナビゲーション

デフォルトでは、Next.jsは前後ナビゲーションのスクロール位置を維持し、Router Cache内のルートセグメントを再利用します。

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

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

または、この処理を完全に無効にして pages/app/ 間のルーティングを手動で管理したい場合は、next.config.jsexperimental.clientRouterFilter を false に設定できます。この機能が無効になっている場合、デフォルトでは、appルートと重複するpages内のダイナミックルートには適切にナビゲートされません。