Next.js と TypeScript で学ぶ、Link とナビゲーションの使い方

TwitterFacebookHatena

TL;DR

このページでは、Link の方法や、Next.js のルーターを使って、ページ間のクライアントサイドのルート遷移を実現する方法について解説します。これにより、シングルページアプリケーションのような動作を実現します。また、動的なパスへのリンクや、ルーターオブジェクトの注入方法、命令的なルーティング、そして浅いルーティングについても説明します。

この記事は、Next.js の公式ドキュメンテーションを参考に、Link とナビゲーションについて解説しています。特に、Linking and Navigatingのセクションを基に、その内容を理解しやすく解釈し直し、さらに詳細な説明を加えています。

この記事は、Next.js の公式ドキュメンテーションの素晴らしい内容を尊重し、その知識を広めることを目指しています。そのため、この記事の内容は、公式ドキュメンテーションの内容をそのまま意訳するのではなく、それを基に私たち自身の理解と経験を元にした解釈と説明を加えています。

もし、より詳細な情報や公式の説明を求める場合は、Next.js の公式ドキュメンテーションを直接ご覧いただくことを強く推奨します。この記事の解説はあくまで参考の一つであり、公式ドキュメンテーションには、より詳細な情報や最新の更新情報が含まれています。

開発環境 バージョン
Next.js 13.4.4(Pages Router)
TypeScript 5.0.4
Emotion 11.11.0
React 18.2.0

リンクとナビゲーション

Next.js のルーターは、ページとページの間を移動することができます。これは、一つのページで全部が完結するアプリと同じような動きです。このページ間の移動を行うために、React の部品である Link が用意されています。

// Link という部品を使えるようにします
import Link from 'next/link'

// Home という名前の部品を作ります
function Home() {
  return (
    <ul>
      <li>
        {/* "Home" と書かれたリンクを作ります。クリックすると "/" のページに行きます */}
        <Link href="/">Home</Link>
      </li>
      <li>
        {/* "About Us" と書かれたリンクを作ります。クリックすると "/about" のページに行きます */}
        <Link href="/about">About Us</Link>
      </li>
      <li>
        {/* "Blog Post" と書かれたリンクを作ります。クリックすると "/blog/hello-world" のページに行きます */}
        <Link href="/blog/hello-world">Blog Post</Link>
      </li>
    </ul>
  )
}

// 他の部品で Home を使えるようにします
export default Home

上記の例では、複数のリンクが使用されています。それぞれのリンクは、パス(href)を既知のページにマッピングしています。

パス 対応するファイル
/ pages/index.js
/about pages/about.js
/blog/hello-world pages/blog/[slug].js

ビューポート内の任意の <Link />(初期表示またはスクロールによる)は、デフォルトで事前にフェッチされます。これには、静的生成を使用するページの対応するデータも含まれます。サーバーサイドでレンダリングされるルートの対応するデータは、<Link />がクリックされたときにのみフェッチされます。

外部リンク

外部リンクを開く場合、Link は使えません。特に新しいタブで開く場合は、直接 a タグを使用します。Next.js の Link コンポーネントや React Router の Link コンポーネントは、アプリケーション内のルーティングに使用します。

また、rel="noopener noreferrer" を指定することで、新しいタブが元のページとは独立したプロセスで開くようにし、セキュリティを強化します。

<a href="example.com" target="_blank" rel="noopener noreferrer">
  タイトル
</a>

このコードは、"タイトル" というテキストのリンクを作成します。このリンクをクリックすると、新しいタブで example.com が開きます。

動的なパスへのリンク

動的なページへのリンクを作るために、パスを作るときに変数を使うこともできます。例えば、部品に渡されたブログの投稿のリストを表示するためには、次のようにします。

// Link という部品を使えるようにします
import Link from 'next/link'

// Posts という名前の部品を作ります。この部品は "posts" という名前のデータを受け取ります
function Posts({ posts }) {
  return (
    <ul>
      {/* 受け取った "posts" のデータを一つずつ "post" という名前で取り出します */}
      {posts.map((post) => (
        <li key={post.id}>
          {/* "post.title" と書かれたリンクを作ります。クリックすると "/blog/投稿のスラッグ" のページに行きます */}
          {/* 投稿のスラッグは、特殊な文字があっても大丈夫なようにエンコードします */}
          <Link href={`/blog/${encodeURIComponent(post.slug)}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}

// 他の部品で Posts を使えるようにします
export default Posts

この例では、パスを UTF-8 互換に保つために encodeURIComponent が使用されています。また、URL オブジェクトを使用する方法もあります。

// Link コンポーネントをインポートします
import Link from 'next/link'

function Posts({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link
            href={{
              pathname: '/blog/[slug]',
              query: { slug: post.slug },
            }}
          >
            {post.title}
          </Link>
        </li>
      ))}
    </ul>
  )
}

export default Posts

この場合、補間を使用してパスを作成する代わりに、href で URL オブジェクトを使用します。ここで:

  • pathname は、pages ディレクトリ内のページの名前です。この場合は /blog/[slug] です。
  • query は、動的セグメントのオブジェクトです。この場合は slug です。

ルーターの注入

React コンポーネントでルーターオブジェクトにアクセスするためには、useRouter または withRouter を使用できます。一般的には、useRouter の使用を推奨します。

命令的なルーティング

next/link を使うと、ほとんどのページ間の移動ができます。でも、それを使わなくても、ページ間の移動はできます。以下の例は、useRouter を使って、簡単にページ間の移動をする方法を示しています。

// useRouter という部品を使えるようにします
import { useRouter } from 'next/router'

// ReadMore という名前の部品を作ります
export default function ReadMore() {
  // useRouter を使って、router という名前の部品を作ります
  const router = useRouter()

  // "Click here to read more" と書かれたボタンを作ります
  // このボタンをクリックすると、"/about" のページに行きます
  return <button onClick={() => router.push('/about')}>Click here to read more</button>
}

浅いルーティング

浅いルーティングを使うと、URL を変えてもデータを取り直す必要がなくなります。これは getServerSidePropsgetStaticPropsgetInitialProps などのデータを取る方法にも適用されます。

新しくなった pathnamequeryuseRouter または withRouter を使って作ったルーター部品を通じて受け取ります。これにより、データを失うことなく、URL を変えることができます。

浅いルーティングを使うには、shallow オプションを true にします。以下の例を見てみましょう:

// 必要な部品を使えるようにします
import { useEffect } from 'react'
import { useRouter } from 'next/router'

// Page という名前の部品を作ります
function Page() {
  // useRouter を使って、router という名前の部品を作ります
  const router = useRouter()

  useEffect(() => {
    // 最初に画面が表示された後に、URL を '/?counter=10' に変えます
    // このとき、データを取り直す必要がないので、shallow を true にします
    router.push('/?counter=10', undefined, { shallow: true })
  }, [])

  useEffect(() => {
    // URL の 'counter' が変わったら、何かをします
  }, [router.query.counter])
}

// 他の部品で Page を使えるようにします
export default Page

URL は /?counter=10 に更新され、ページは置き換えられず、ルートの状態だけが変更されます。

また、以下に示すように、componentDidUpdate を通じて URL の変更を監視することもできます:

componentDidUpdate(prevProps) {
  const { pathname, query } = this.props.router
  // 無限ループを避けるために、プロップが変更されたことを確認します
  if (query.counter !== prevProps.router.query.counter) {
    // 新しいクエリに基づいてデータをフェッチします
  }
}

注意点として、浅いルーティングは現在のページでの URL 変更に対してのみ機能します。例えば、別のページ

pages/about.js があり、次のように実行するとします:

router.push('/?counter=10', '/about?counter=10', { shallow: true })

これは新しいページなので、現在のページをアンロードし、新しいページをロードし、データフェッチを待つことになります。これは、浅いルーティングを行うように指示したにもかかわらずです。

ミドルウェアと浅いルーティングを使用すると、新しいページが現在のページと一致することを確認することはありません。これは、ミドルウェアが動的に書き換えることができ、データフェッチをスキップする浅いルーティングでは、クライアントサイドで確認できないためです。したがって、浅いルーティングの変更は常に浅いものとして扱われるべきです。

Next.js と TypeScript で学ぶ、Link とナビゲーションの使い方