宣言的 vs 命令的
Link | router.push() | |
|---|---|---|
| 書き方 | JSX コンポーネント | イベントハンドラ内の関数呼び出し |
| 使う場所 | Server / Client Component どちらでも | Client Component のみ |
| HTML 出力 | <a> タグ(セマンティック) | なし(ナビゲーションのみ) |
| プリフェッチ | デフォルトで有効(本番) | なし |
| 遷移先の決め方 | JSX 上に href を書く | 関数内で動的に指定できる |
| JS なし | リンクとして機能する | 機能しない |
Link — JSX 上に書けるリンク
Link は <a> タグとしてレンダリングされるため、セマンティックな HTML になります。スクリーンリーダーや検索エンジンにリンクとして認識され、ビューポートに入ったリンク先を自動でプリフェッチします。
import Link from "next/link";
// ナビバー
function Nav() {
return (
<nav>
<Link href="/samples">サンプル一覧</Link>
<Link href="/docs">ドキュメント</Link>
</nav>
);
}
// カード(全体をリンクにする)
function SampleCard({ slug, title }: { slug: string; title: string }) {
return (
<Link href={`/samples/${slug}`} className="block rounded border p-4 hover:bg-gray-50">
<h2>{title}</h2>
</Link>
);
}
向いている場所:
- ナビゲーションバー・フッターリンク
- パンくずリスト
- カード・リストアイテムのクリック遷移
- 「詳細を見る」「一覧に戻る」などの静的なリンクテキスト
router.push() — イベントハンドラ内の遷移
遷移先を非同期処理の結果で決めたい場合や、副作用を実行してから遷移させたい場合に使います。"use client" が必要です。
"use client";
import { useRouter } from "next/navigation";
// フォーム送信後の遷移
export default function LoginForm() {
const router = useRouter();
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const res = await fetch("/api/login", { method: "POST", ... });
if (res.ok) {
router.replace("/dashboard");
router.refresh();
} else {
// エラー表示
}
}
return <form onSubmit={handleSubmit}>...</form>;
}
// 条件によって遷移先が変わる場合
export function ActionButton({ id }: { id: string }) {
const router = useRouter();
async function handleClick() {
const result = await fetchSomething(id);
if (result.type === "post") {
router.push(`/posts/${result.slug}`);
} else {
router.push(`/pages/${result.slug}`);
}
}
return <button onClick={handleClick}>開く</button>;
}
向いている場面:
- ログイン・ログアウト後の遷移
- フォーム送信成功後の遷移
- 非同期処理の結果に応じて遷移先が変わる操作
- 遷移前にバリデーションや副作用処理が必要な操作
典型ユースケースでの使い分け
| ユースケース | 推奨 | 理由 |
|---|---|---|
| ナビバーのメニューリンク | Link | 静的な href、セマンティック HTML |
| サンプルカードのクリック | Link | カード全体を <a> で囲むとアクセシブル |
| 「詳細を見る」テキストリンク | Link | リンクとして表現できる |
| ログイン成功後のリダイレクト | router.replace() | 非同期処理後・履歴を残したくない |
| フォーム送信後の完了ページ | router.replace() | 送信完了後に遷移先を確定する |
| 検索フォームの「検索」ボタン | router.push() | 入力値で URL を動的に構築 |
| 結果に応じて複数遷移先がある | router.push() | 条件分岐が必要 |
誤用しやすいポイント
単純なリンクに router.push() を使う
// ❌ Link で十分な場面に router.push() を使っている
"use client";
export function TopLink() {
const router = useRouter();
return <button onClick={() => router.push("/")}>トップへ</button>;
}
// ✅ Link を使う(プリフェッチ・アクセシビリティ・SEO の恩恵がある)
import Link from "next/link";
export function TopLink() {
return <Link href="/">トップへ</Link>;
}
<div> や <span> を onClick でリンクに見せる
// ❌ クリッカブルな div はスクリーンリーダーに認識されない
<div onClick={() => router.push("/samples")}>サンプル一覧</div>
// ✅ Link を使って <a> タグとして出力する
<Link href="/samples">サンプル一覧</Link>
ボタンに見せたいリンクに router.push() を無理に使う
外観をボタンにしたいだけなら、Link に CSS を当てる方がシンプルです。
// ✅ ボタンに見せたいリンクは Link + className でスタイルを当てる
<Link
href="/samples/new"
className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
サンプルを追加
</Link>
router.push() / router.replace() / redirect() の使い分けは → router.push() vs router.replace() vs redirect()
遷移後に Server Component を再フェッチする方法は → router.refresh()