TOOLS BOX/ガイド/Link vs router.push()
Concept

Link vs router.push()

Next.js の宣言的ナビゲーション(Link コンポーネント)と命令的ナビゲーション(router.push())の使い分けガイド。JSX 上のリンクには Link、イベントハンドラや条件分岐後の遷移には router.push() を使う。

nextjslinkrouterpushnavigationaccessibilityseoclient-component

どういう場面で使うか

  • ·Link: ナビバー・パンくず・カードなど、JSX 上にリンクとして表現できる遷移
  • ·router.push(): ボタンクリック後の非同期処理完了後・条件分岐後など、イベントハンドラ内での遷移

注意点 / Pitfalls

  • ·シンプルなページ遷移に router.push() を使うと、Link が持つプリフェッチ・アクセシビリティ・SEO の恩恵を失う
  • ·Link はマークアップ上に href が必要なため、非同期処理の結果に応じた遷移先を動的に決めるのには向かない
  • ·<button> の onClick に router.push() を使うとアクセシブルだが、単なるページ遷移なら <Link> の方がシンプル
  • ·Link の prefetch はデフォルトで有効(本番環境のみ)。リンク数が多い場合は prefetch={false} で制御できる

補足

Link はコンポーネントとして JSX に書く宣言的ナビゲーション。router.push() は useRouter フックから取得した関数を呼ぶ命令的ナビゲーション。「JSX 上にリンクを置けるか」が分岐の基準になる。

宣言的 vs 命令的

Linkrouter.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()

関連ドキュメント

関連サンプル

同じテーマや技術スタックを使った実装例