リンクとナビゲーション
Next.jsではルート間を移動するための4つの方法があります:
<Link>
コンポーネントを使用するuseRouter
フックを使用する(クライアントコンポーネント)redirect
関数を使用する(サーバーコンポーネント)- ネイティブのHistory APIを使用する
このページではこれらの各オプションの使用方法と、ナビゲーションの仕組みについて詳しく説明します。
<Link>
コンポーネント
<Link>
はHTMLの<a>
タグを拡張した組み込みコンポーネントで、ルート間のプリフェッチとクライアントサイドナビゲーションを提供します。これはNext.jsでルート間を移動するための主要で推奨される方法です。
next/link
からインポートし、コンポーネントにhref
プロップを渡すことで使用できます:
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
<Link>
に渡せるオプションのプロップが他にもあります。詳細はAPIリファレンスをご覧ください。
useRouter()
フック
useRouter
フックを使用すると、クライアントコンポーネントからプログラムでルートを変更できます。
'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リファレンスをご覧ください。
推奨:
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: Promise<{ id: string }>
}) {
const id = (await params).id
if (!id) {
redirect('/login')
}
const team = await fetchTeam(id)
if (!team) {
redirect('/join')
}
// ...
}
補足:
redirect
はデフォルトで307(一時的なリダイレクト)ステータスコードを返します。Server Actionで使用する場合は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ルーターと統合されており、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')}>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
ブラウザの履歴スタック上の現在のエントリを置き換えるために使用します。ユーザーは前の状態に戻ることができません。例えば、アプリケーションのロケールを切り替える場合:
'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')}>English</button>
<button onClick={() => switchLocale('fr')}>French</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')}>English</button>
<button onClick={() => switchLocale('fr')}>French</button>
</>
)
}
ルーティングとナビゲーションの仕組み
Appルーターはルーティングとナビゲーションにハイブリッドアプローチを使用します。サーバー上では、アプリケーションコードがルートセグメントによって自動的にコード分割されます。そしてクライアント上では、Next.jsがルートセグメントをプリフェッチしてキャッシュします。これにより、ユーザーが新しいルートに移動した際にブラウザがページをリロードせず、変更されたルートセグメントのみが再レンダリングされるため、ナビゲーション体験とパフォーマンスが向上します。
1. コード分割
コード分割により、アプリケーションコードを小さなバンドルに分割してブラウザによってダウンロードおよび実行できるようになります。これにより、各リクエストで転送されるデータ量と実行時間が削減され、パフォーマンスが向上します。
サーバーコンポーネントにより、アプリケーションコードはルートセグメントによって自動的にコード分割されます。これにより、ナビゲーション時に現在のルートに必要なコードのみが読み込まれます。
2. プリフェッチ
プリフェッチはユーザーがルートを訪問する前にバックグラウンドでルートを事前に読み込む方法です。
Next.jsでは、ルートが2つの方法でプリフェッチされます:
<Link>
コンポーネント:ルートはユーザーのビューポートに表示されると自動的にプリフェッチされます。プリフェッチはページが最初に読み込まれた時、またはスクロールによって表示された時に行われます。router.prefetch()
:useRouter
フックを使用してプログラムでルートをプリフェッチできます。
<Link>
のデフォルトのプリフェッチ動作(prefetch
プロップが未指定またはnull
に設定されている場合)は、loading.js
の使用方法によって異なります。共有レイアウトは、コンポーネントの「ツリー」から最初のloading.js
ファイルまでレンダリングされてプリフェッチされ、30s
間キャッシュされます。これにより動的ルート全体をフェッチするコストが削減され、ユーザーへの視覚的なフィードバックを向上させるためのインスタントローディング状態を表示できます。
prefetch
プロップをfalse
に設定することでプリフェッチを無効にできます。あるいは、prefetch
プロップをtrue
に設定することで、ローディング境界を超えて完全なページデータをプリフェッチすることもできます。
詳細については<Link>
APIリファレンスをご覧ください。
補足:
- プリフェッチは開発環境では有効ではなく、本番環境でのみ有効です。
3. キャッシング
Next.jsにはルーターキャッシュと呼ばれるインメモリのクライアントサイドキャッシュがあります。ユーザーがアプリケーション内を移動すると、プリフェッチされたルートセグメントと訪問済みルートのReact Server Componentペイロードがキャッシュに保存されます。
これにより、ナビゲーション時に新たにサーバーへリクエストを送信する代わりに、可能な限りキャッシュが再利用されます。リクエスト数とデータ転送量を削減することでパフォーマンスが向上します。
ルーターキャッシュの仕組みとその設定方法について詳しく学んでください。
4. 部分レンダリング
部分レンダリングとは、ナビゲーション時に変更されたルートセグメントのみがクライアント上で再レンダリングされ、共有セグメントは保持されることを意味します。
例えば、2つの兄弟ルート/dashboard/settings
と/dashboard/analytics
間を移動する場合、settings
ページがアンマウントされ、analytics
ページが新しい状態でマウントされ、共有のdashboard
レイアウトは保持されます。この動作は同じ動的セグメント上の2つのルート間でも存在します。例えば/blog/[slug]/page
で、/blog/first
から/blog/second
へ移動する場合です。
部分レンダリングがなければ、各ナビゲーションでクライアント上の完全なページが再レンダリングされます。変更されたセグメントのみをレンダリングすることで、転送されるデータ量と実行時間が削減され、パフォーマンスが向上します。
5. ソフトナビゲーション
ブラウザはページ間の移動時に「ハードナビゲーション」を実行します。Next.js Appルーターはページ間の「ソフトナビゲーション」を可能にし、変更されたルートセグメントのみが再レンダリングされるようにします(部分レンダリング)。これによりナビゲーション中にクライアントReact状態が保持されます。
6. 戻るおよび進むナビゲーション
デフォルトでは、Next.jsは戻るおよび進むナビゲーションでスクロール位置を維持し、ルーターキャッシュ内のルートセグメントを再利用します。
7. pages/
とapp/
間のルーティング
pages/
からapp/
へ段階的に移行する際、Next.jsルーターは自動的に両者間のハードナビゲーションを処理します。pages/
からapp/
への遷移を検出するために、クライアントルーターフィルターはアプリルートの確率的チェックを利用しており、これが時に偽陽性を引き起こす可能性があります。デフォルトでは、偽陽性の可能性を0.01%に設定しているため、このような発生は非常にまれであるはずです。この確率はnext.config.js
のexperimental.clientRouterFilterAllowedRate
オプションでカスタマイズできます。偽陽性率を下げるとクライアントバンドルで生成されるフィルターのサイズが大きくなることに注意が必要です。
あるいは、この処理を完全に無効にしてpages/
とapp/
間のルーティングを手動で管理したい場合は、next.config.js
でexperimental.clientRouterFilter
をfalse
に設定できます。この機能が無効になっている場合、アプリルートと重複するpages内の動的ルートはデフォルトでは適切にナビゲートされません。