リンクとナビゲーション
Next.jsでは、ルート間をナビゲートする方法は4つあります:
<Link>
コンポーネントを使用するuseRouter
フックを使用する(Client Components)redirect
関数を使用する(Server Components)- ネイティブなHistory APIを使用する
このページでは、それぞれのオプションの使用方法について説明し、ナビゲーションの仕組みを詳しく見ていきます。
<Link>
コンポーネント
<Link>
は、HTMLの<a>
タグを拡張し、プリフェッチとクライアントサイドでのルート間ナビゲーションを提供する組み込みのコンポーネントです。Next.jsでルート間をナビゲートする主な推奨方法です。
next/link
からインポートし、href
プロップをコンポーネントに渡すことで使用できます:
- TypeScript
- JavaScript
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
<Link>
に渡す他のオプションのprops
もあります。詳細はAPIリファレンスをご覧ください。
useRouter()
フック
useRouter
フックを使うと、Client Componentsからプログラム的にルートを変更できます。
'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
関数
Server Componentsの場合、代わりにredirect
関数を使用してください。
- TypeScript
- JavaScript
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(Temporary Redirect)ステータスコードを返します。サーバーアクションで使用した場合、303(See Other)を返します。これは、POSTリクエストの結果として成功ページにリダイレクトする際によく使用されます。redirect
は内部的にエラーを投げるので、try/catch
ブロックの外で呼び出されるべきです。redirect
はクライアントコンポーネントでのレンダリングプロセス中に呼び出せますが、イベントハンドラ内では使用できません。代わりにuseRouter
フックを使用できます。redirect
は絶対URLも受け入れ、外部リンクにリダイレクトするために使用できます。- レンダープロセスの前にリダイレクトしたい場合は、
next.config.js
やミドルウェアを使用してください。
より詳しい情報は、redirect
APIリファレンスを参照してください。
ネイティブなHistory APIを使用する
Next.jsでは、ネイティブのwindow.history.pushState
とwindow.history.replaceState
メソッドを使用して、ページをリロードせずにブラウザの履歴スタックを更新することができます。
pushState
とreplaceState
の呼び出しはNext.js Routerと統合され、usePathname
とuseSearchParams
と同期できます。
window.history.pushState
ブラウザの履歴スタックに新しいエントリを追加するために使用します。ユーザーは前の状態に戻ることができます。例えば、製品リストをソートするには:
- TypeScript
- JavaScript
'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')}>Sort Ascending</button>
<button onClick={() => updateSorting('desc')}>Sort Descending</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')}>Sort Ascending</button>
<button onClick={() => updateSorting('desc')}>Sort Descending</button>
</>
)
}
window.history.replaceState
ブラウザの履歴スタックの現在のエントリを置き換えるために使用します。ユーザーは前の状態に戻ることができません。例えば、アプリケーションのロケールを切り替えるには:
- TypeScript
- JavaScript
'use client'
import { usePathname } from 'next/navigation'
export function LocaleSwitcher() {
const pathname = usePathname()
function switchLocale(locale: string) {
// e.g. '/en/about' or '/fr/contact'
const newPath = `/${locale}${pathname}`
window.history.replaceState(null, '', newPath)
}
return (
<>
<button onClick={() => switchLocale('en')}>English</button>
<button onClick={() => switchLocale('fr')}>French</button>
</>
)
}
'use client'
import { usePathname } from 'next/navigation'
export function LocaleSwitcher() {
const pathname = usePathname()
function switchLocale(locale) {
// e.g. '/en/about' or '/fr/contact'
const newPath = `/${locale}${pathname}`
window.history.replaceState(null, '', newPath)
}
return (
<>
<button onClick={() => switchLocale('en')}>English</button>
<button onClick={() => switchLocale('fr')}>French</button>
</>
)
}
ルーティングとナビゲーションの仕組み
App Routerは、ルーティングとナビゲーションにハイブリッドアプローチを採用しています。サーバー上では、アプリケーションコードがルートセグメントによって自動的にコード分割され、クライアント側では、Next.jsがルートセグメントをプリフェッチしキャッシュします。つまり、ユーザーが新しいルートに移動しても、ブラウザはページをリロードせず、変更されたルートセグメントだけが再レンダリングされます。これにより、ナビゲーション体験とパフォーマンスが向上します。
1. コード分割
コード分割により、アプリケーションコードを小さなバンドルに分割し、ブラウザでダウンロードおよび実行できるようになります。これにより、データ転送量と各リクエストの実行時間が短縮され、パフォーマンスが向上します。
Server Componentsにより、アプリケーションコードはルートセグメントによって自動的にコード分割されます。これにより、ナビゲーション時には現在のルートに必要なコードのみが読み込まれます。
2. プリフェッチ
プリフェッチは、ユーザーが訪問する前にバックグラウンドでルートを事前にロードする方法です。
Next.jsではルートがプリフェッチされる方法は2つあります:
<Link>
コンポーネント:ユーザーのビューポートに表示されると、ルートは自動的にプリフェッチされます。プリフェッチは、ページが最初にロードされるときや、スクロールによってビューに入るときに行われます。router.prefetch()
:useRouter
フックを使用して、プログラム的にルートをプリフェッチできます。
<Link>
のデフォルトのプリフェッチ動作(つまり、prefetch
プロップが未指定またはnull
に設定されている場合)は、loading.js
の使用に応じて異なります。共有レイアウトのみがプリフェッチされ、「コンポーネントのツリー」がレンダリングされる最初のloading.js
ファイルまでキャッシュされます。これにより、動的ルート全体をフェッチするコストが削減され、即時のローディング状態がユーザーにより良い視覚的フィードバックを提供します。
プリフェッチを無効にするには、prefetch
プロップをfalse
に設定します。あるいは、prefetch
プロップをtrue
に設定して、ローディングの境界を超えて完全なページデータをプリフェッチすることができます。
詳細は<Link>
APIリファレンスを参照してください。
知っておいてよい点:
- プリフェッチは開発環境では有効ではなく、本番環境でのみ有効です。
3. キャッシュ
Next.jsにはインメモリーのクライアントサイドキャッシュがあり、Router Cacheと呼ばれます。ユーザーがアプリをナビゲートすると、プリフェッチされたルートセグメントと訪問したルートのReactサーバーコンポーネントペイロードがキャッシュに保存されます。
つまり、ナビゲーション時には、可能な限りキャッシュが再利用され、新たなサーバーへのリクエストを行う代わりに、キャッシュが使用されます。これにより、リクエスト数とデータ転送量が削減され、パフォーマンスが向上します。
Router Cacheの仕組みと構成方法についての詳細をご覧ください。
4. 部分的なレンダリング
部分的なレンダリングとは、クライアント上のナビゲーションで変更されるルートセグメントのみが再レンダリングされ、共有セグメントは保持されることを意味します。
たとえば、2つの兄弟ルート/dashboard/settings
と/dashboard/analytics
間を移動する際には、settings
ページがアンマウントされ、analytics
ページが新しい状態でマウントされ、共有のdashboard
レイアウトが保存されます。この動作は、同じ動的セグメント上の2つのルート間でも発生します。たとえば、/blog/[slug]/page
および/blog/first
から/blog/second
へのナビゲーション時です。
部分的なレンダリングがない場合、各ナビゲーションはクライアントですべてのページを再レンダリングさせます。変更されるセグメントのみをレンダリングすることで、データ転送量と実行時間が削減され、パフォーマンスが向上します。
5. ソフトナビゲーション
ブラウザはページ間のナビゲーションで「ハードナビゲーション」を行います。Next.jsのApp Routerは、ページ間での「ソフトナビゲーション」を可能にし、変更されたルートセグメントのみが再レンダリングされるようにします(部分的なレンダリング)。これにより、クライアントのReact stateがナビゲーション中に保存されます。
6. 前後のナビゲーション
デフォルトでは、Next.jsは前後のナビゲーションでスクロール位置を維持し、Router Cacheのルートセグメントを再利用します。
7. pages/
とapp/
間のルーティング
pages/
からapp/
へのインクリメンタル移行時に、Next.jsルーターは両者間のハードナビゲーションを自動的に処理します。pages/
からapp/
への移行を検出するために、アプリルートの確率的チェックを活用したクライアントルーターフィルターがありますが、これは稀に偽陽性を引き起こす可能性があります。通常、こうした事例は非常に稀ですが、偽陽性の確率は0.01%に設定されています。この確率は、next.config.js
のexperimental.clientRouterFilterAllowedRate
オプションを通じてカスタマイズできます。偽陽性率を下げると、クライアントバンドル内の生成されたフィルターのサイズが増加することに注意してください。
完全にこの処理を無効にして、pages/
とapp/
間のルーティングを手動で管理したい場合は、next.config.js
でexperimental.clientRouterFilter
をfalse
に設定できます。この機能が無効化されると、appルートと重なるページの動的ルートがデフォルトでは正しくナビゲートされません。