Open5

Linking and Navigating

あおけんあおけん

Next.jsルーターは、シングルページのアプリケーションと同じように、
クライアントサイドでページ間のルート遷移を行うことができます。

このクライアント側のルート遷移を行うために、
LinkというReactコンポーネントが用意されている。

import Link from 'next/link'
 
function Home() {
  return (
    <ul>
      <li>
        <Link href="/">Home</Link>
      </li>
      <li>
        <Link href="/about">About Us</Link>
      </li>
      <li>
        <Link href="/blog/hello-world">Blog Post</Link>
      </li>
    </ul>
  )
}
 
export default Home

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

  • / → pages/index.js
  • /about → pages/about.js
  • /blog/hello-world → pages/blog/[slug].js

静的生成(Static Generation)を使用しているページでは、
(初期状態またはスクロールによって)ビューポートにあるすべての<Link />が、
デフォルトでプリフェッチされます(対応するデータを含む)。

サーバレンダリングされたルートの対応するデータは、
<Link />がクリックされたときのみフェッチされます。

あおけんあおけん

Linking to dynamic paths

動的なルートセグメントで便利な補間を使ってパスを作ることもできる。
たとえば、propとしてコンポーネントに渡された投稿のリストを表示。

import Link from 'next/link'
 
function Posts({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/blog/${encodeURIComponent(post.slug)}`}>
            {post.title}
          </Link>
        </li>
      ))}
    </ul>
  )
}
 
export default Posts

または、URLオブジェクトを使用する。

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はダイナミックセグメントを持つオブジェクト
あおけんあおけん

Imperative Routing

next/linkはルーティングのほとんどのニーズをカバーできるはずだが、
next/routerを使わなくてもクライアントサイドのナビゲーションを行うこともできる。

次の例では、useRouterを使って基本的なページ・ナビゲーションを行う方法を示している。

import { useRouter } from 'next/router'
 
export default function ReadMore() {
  const router = useRouter()
 
  return (
    <button onClick={() => router.push('/about')}>
      Click here to read more
    </button>
  )
}
あおけんあおけん

Shallow Routing

サンプルコード: https://github.com/vercel/next.js/tree/canary/examples/with-shallow-routing

シャロールーティングでは、
データ取得メソッドを再度実行することなくURLを変更することができる。
このメソッドには、getServerSideProps、getStaticProps、getInitialPropsが含まれる。

状態を失うことなく、更新されたpathnameとqueryをrouter object(useRouterまたはwithRouterで追加)を介して受け取ることができる。

シャロー・ルーティングを有効にするには、
shallowオプションをtrueに設定する。

import { useEffect } from 'react'
import { useRouter } from 'next/router'
 
// Current URL is '/'
function Page() {
  const router = useRouter()
 
  useEffect(() => {
    // Always do navigations after the first render
    router.push('/?counter=10', undefined, { shallow: true })
  }, [])
 
  useEffect(() => {
    // The counter changed!
  }, [router.query.counter])
}
 
export default Page

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

以下のように、componentDidUpdateを介してURLの変更を監視することもできる。

componentDidUpdate(prevProps) {
  const { pathname, query } = this.props.router
  // verify props have changed to avoid an infinite loop
  if (query.counter !== prevProps.router.query.counter) {
    // fetch data based on the new query
  }
}

Caveats

シャロー・ルーティングは、現在のページのURL変更に対してのみ機能する。
例えば、pages/about.jsという別のページがあり、これを実行したとする。

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

これは新しいページなので、現在のページをアンロードし、
新しいページをロードし、浅いルーティングを要求したにもかかわらず、
データのフェッチを待つ。

シャロー・ルーティングがミドルウェアとともに使用される場合、
以前ミドルウェアなしで行われたように、
新しいページが現在のページと一致することを保証しません。
これはミドルウェアが動的に書き換えられるためで、
シャローではスキップされるデータフェッチなしではクライアントサイドで検証できません。

つまり、

  • シャロー・ルーティングではミドルウェアは実行されない
  • シャロー・ルーティングを使う際は、ミドルウェアに依存しない前提で設計する
  • ミドルウェアが必要なケースでは、シャロー・ルーティングを使わないのもあり