【第5回】Learn Next.js チュートリアル解説:ページ間ナビゲーション(next/link と usePathname)

Next.jsのnext/linkとusePathnameを使ったページ間ナビゲーションの仕組みを示す図解 プログラミング

前回(第4回)は、ダッシュボードのネストされたレイアウト構造(マトリョーシカ構造)と部分レンダリングの仕組みについて学びました。

この記事でせっかく美しいネストレイアウト(layout.tsx)を作りましたが、実は <a> タグのままだと移動のたびにブラウザが画面全体をリロードしてしまい、部分レンダリングの恩恵を受けられません。

今回学ぶ <Link> タグに置き換えて初めて、前回作った layout.tsx の『サイドバーを固定して中身だけサクッと変える』という機能が完成します。

第5回となる今回は、作成したページ間をスムーズ移動するための「ナビゲーション機能」を実装していきます!

💡 今回学ぶこと(トピック)

  1. next/link (<Link> コンポーネント)の使い方
  2. usePathname() フックを使ったアクティブリンクのハイライト表現
  3. Next.js が爆速なページ遷移を実現する裏側の仕組み(コード分割とプリフェッチ)

1. 従来の <a> タグの問題点と <Link> コンポーネント

通常の HTML では、ページ遷移に <a> タグを使用します。 現在作成中のサイドバーのリンクも <a> タグになっています。

/app/ui/dashboard/nav-links.tsxを確認してみます。

// ★ここにはまだ next/link のインポートがない!

export default function NavLinks() {
  return (
    <>
      {links.map((link) => {
        const LinkIcon = link.icon;
        return (
          /* ★ここが <a> タグになっている */
          <a
            key={link.name}
            href={link.href}
            className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3"
          >
            <LinkIcon className="w-6" />
            <p className="hidden md:block">{link.name}</p>
          </a>
        );
      })}
    </>
  );
}


ホーム・請求書(Invoices)・顧客(Customers)のページを切り替えた時、ブラウザにどのような挙動が起きているでしょうか?

画面遷移のたびに「ページ全体のリロード(フルリフレッシュ)」が発生し、画面が一瞬白くチラ可能性があります。

しかし、自分が開発サーバーで確認しているとき白飛びは感じられませんでした。これは App Router の「レイアウト共有(layout.tsx)」と「SPA(Single Page Application)的なルーターキャッシュ」が働いているため、あまり遷移の違いを感じないかもしれません。

なぜ <a> タグでも白飛びしない(ように見える)のか?

1. 共通の外枠(layout.tsx)が再利用されているから

第4回で作成した /app/dashboard/layout.tsx のおかげで、左側のサイドバー自体は最初からブラウザ上に残ったまま固定されています。

<a> タグで移動しても、共通のレイアウト部分はそのまま維持され、右側のメインコンテンツ領域(children)の切り替えだけで済む場合があるため、画面全体が一瞬真っ白になるような派手なチラつきが目立たなくなっています。

2. ブラウザや開発サーバー(next dev)の高速キャッシュ

Next.js の開発サーバーは、ローカル環境(localhost)で動いているため応答が極めて高速です。そのため、<a> タグによる通常のページ遷移が発生していても、一瞬でレンダリングが完了して「白飛びしていない(サクサク動いている)」ように見えることがあります。

<Link> コンポーネントへの置き換え

Next.js では、JavaScript を用いたクライアントサイド・ナビゲーションを実現するために <Link/> コンポーネントが用意されています。

/app/ui/dashboard/nav-links.tsx を開き、next/link から Link をインポートして <a> タグを <Link> に書き換えます。

// /app/ui/dashboard/nav-links.tsx
import {
  UserGroupIcon,
  HomeIcon,
  DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link'; // ★ Link をインポート
 
// ...
 
export default function NavLinks() {
  return (
    <>
      {links.map((link) => {
        const LinkIcon = link.icon;
        return (
          // ★ <a> を <Link> に変更(href属性はそのまま)
          <Link
            key={link.name}
            href={link.href}
            className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3"
          >
            <LinkIcon className="w-6" />
            <p className="hidden md:block">{link.name}</p>
          </Link>
        );
      })}
    </>
  );
}

使い方は <a> タグとほとんど同じで、<a href=”…”> を <Link href=”…”> にするだけです。 保存してブラウザで確認すると、ページ全体のリロード(白飛び)がなくなり、まるでネイティブアプリのようなヌルサクな画面遷移に変わったことが実感できるはずです!

2. Next.js のナビゲーションが爆速な理由(自動コード分割 & プリフェッチ)

なぜ Next.js の <Link> を使うとこんなに滑らかに切り替わるのでしょうか? 裏側では、大きく2つの最適化が自動で行われています。

① 自動コード分割(Automatic Code-Splitting)

従来の React SPA(Single Page Application)では、最初のアクセス時にアプリ全体の巨大な JavaScript コードを一度にダウンロードしていました。
一方 Next.js では、ルート(ページ)単位でコードを自動分割します。必要なページのコードしか読み込まないため、初回の表示が非常に高速になり、万が一あるページでエラーが起きても他のページには影響しません。

② プリフェッチ(Prefetching)

本番環境(production)において、画面上に <Link> コンポーネントが表示(画面の表示領域/viewportに入った時)されると、Next.js は背景でリンク先のページのコードを自動的にあらかじめ読み込み(プリフェッチ)しておきます。 ユーザーがリンクをクリックした瞬間には、すでに遷移先のコードが準備できているため、一瞬でページが切り替わるのです!

Next.jsのプリフェッチによって遷移先ページのデータを事前読み込みし、高速なページ遷移を実現する仕組みの図解

✍️ 【体験談】SPAの煩雑な設定から解放された「<Link>を置くだけ」の衝撃

従来の React アプリで SPA(Single Page Application)風のサクサク遷移を作ろうとすると、ルーティングライブラリの設定に追われ、初期ロードが重くならないように React.lazy や dynamic import を駆使して手動でコード分割(Code Splitting)の設計を書くのが当たり前でした。

しかし、Next.js では 「ただ <Link> タグを使って普通に書くだけ」。 開発者が何も意識しなくても、勝手にページ単位でコードが細切れに分割され、画面にリンクが入った瞬間に裏でバックグラウンド読み込み(プリフェッチ)まで完了してくれます。

「余計な最適化コードを書かなくていい、ただ普通に書けば一番速いアプリになる」というフレームワークとしての完成度に、深く感動した瞬間でした。

3. 実践パターン:アクティブリンク(現在地)のハイライト表示

ユーザーが「今どのページを開いているか」を視覚的に伝えるため、アクティブなリンクのデザインを変更(ハイライト表示)してみましょう。

URL から現在のパスを取得するには、Next.js が提供する usePathname() フックを使用します。

Step 1: “use client” の追加とフックのインポート

usePathname() は React のフック(Hook)であるため、コンポーネントを Client Component に指定する必要があります。ファイルの先頭に ‘use client’; を追加しましょう。

// /app/ui/dashboard/nav-links.tsx
'use client'; // ★ Client Component 指定

import {
  UserGroupIcon,
  HomeIcon,
  DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link';
import { usePathname } from 'next/navigation'; // ★ usePathname をインポート

Step 2: 現在のパスを取得して判定する

NavLinks コンポーネント内で usePathname() を呼び出して現在の URL パスを取得し、条件分岐ライブラリ clsx を使って、pathname が link.href と一致した時だけスタイルのクラス(青背景・青文字)を適用します。

// /app/ui/dashboard/nav-links.tsx
'use client';
 
import {
  UserGroupIcon,
  HomeIcon,
  DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import clsx from 'clsx'; // ★ clsx のインポート
 
export default function NavLinks() {
  const pathname = usePathname(); // ★ 現在のパスを取得
 
  return (
    <>
      {links.map((link) => {
        const LinkIcon = link.icon;
        return (
          <Link
            key={link.name}
            href={link.href}
            className={clsx(
              'flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3',
              {
                // ★ 現在のパスと一致している場合のみ、青色スタイルのクラスを追加
                'bg-sky-100 text-blue-600': pathname === link.href,
              },
            )}
          >
            <LinkIcon className="w-6" />
            <p className="hidden md:block">{link.name}</p>
          </Link>
        );
      })}
    </>
  );
}

保存して開発サーバーで確認してみます。

npm run dev

ブラウザで localhost:3000/dashboard にアクセスすると、以下の画面のように、現在開いているページのメニュー(「Home」「Invoices」「Customers」)が青くハイライト表示されるようになれば成功です。

usePathnameフックでアクティブリンクを適用したダッシュボード画面のスクリーンショット

❓ クイズで復習!

Q: 本番環境(production)で、画面の表示領域(viewport)に <Link> コンポーネントが入った時、Next.js は自動で何をしますか?

  • A: 追加の CSS をダウンロードする
  • B: 画像を事前ロードする
  • C: リンク先のルート(ページ)のコードをプリフェッチする
  • D: リンク先で遅延読み込み(lazy loading)を有効にする

正解: C(Prefetch the code for the linked route)

Next.js はリンクが画面内に見えた時点で、その先のコードをバックグラウンドで事前読み込みしておきます。そのため、クリックした瞬間に即座に画面が切り替わります。

まとめ

第5回では、Next.js の快適なナビゲーション機能について学びました。

  • <a> から <Link> への変更:画面全体のリロードがなくなり、爆速なクライアントサイド・ナビゲーションを実現。
  • 自動コード分割とプリフェッチ:開発者が意識せずとも初期表示が軽く、遷移も一瞬。
  • usePathname() フック:’use client’; と組み合わせることで、現在地のハイライト表示などを簡単に実装できる。

次回はいよいよ「第6章:データベースのセットアップ」に入ります!実際にデータベースを構築し、データの取得に向けた準備を進めていきましょう!

コメント