SPA
React
next.js
ssr
Next.jsDay 6

Next.jsのルーティング

本記事は公式ドキュメントの翻訳を一部含みます
https://nextjs.org/docs/#routing

静的なルーティング

Next.jsはデフォルトで、ファイルシステムがルーティングとなります。例えば、pages以下に置かれたファイルがpages/about.jsだったとすると、localhost:3000/aboutにアクセスできるようになります。
そしてクライアントサイドのみの遷移を実現するために、Next.jsは<Link>というコンポーネントを用意してくれています。シンプルなルーティングでは、以下のようにhrefパラメータにhref="/about"と書けば遷移できます。

pages/index.js
import Link from 'next/link'

export default () => (
  <div>
    Click{' '}
    <Link href="/about">
      <a>here</a>
    </Link>{' '}
    to read more
  </div>
)

動的なルーティング

では/about/:nameのようなページがパラメータを持って変化するような、動的なページ遷移はどうしたらよいでしょう。

<Link>コンポーネントには、hrefの他にasというパラメータがあり、ブラウザヒストリーの書き換えはasに渡された文字列で行われます。
一方、どのページに、どんなパラメータで遷移するかをNext.js内部に知らせるには、下記サンプルのようなURLオブジェクトをhrefに渡します。
pathnameはpages以下のファイル、queryの値にはクエリのオブジェクトを渡します。

pages/index.js
import Link from 'next/link'

export default () => (
  <div>
    Click{' '}
    <Link
      as='/about/Zeit'
      href={{ pathname: '/about', query: { name: 'Zeit' } }}
    >
      <a>here</a>
    </Link>{' '}
    to read more
  </div>
)

URLオブジェクト

<Link>コンポーネントと同じように、Routerの関数にURLオブジェクトを渡すことで、履歴のpushreplaceが可能です。

import Router from 'next/router'

const handler = () => {
  Router.push({
    pathname: '/about',
    query: { name: 'Zeit' }
  })
}

export default () => (
  <div>
    Click <span onClick={handler}>here</span> to read more
  </div>
)

ルーターイベント

ルーター内部で実行されるイベントを捕捉したコールバックを作ることができます。
以下のイベントがサポートされています:

  • routeChangeStart(url) - ルートが変化を開始する際に発火
  • routeChangeComplete(url) - ルートの変化が完了した際に発火
  • routeChangeError(err, url) - ルートが変化する最中、エラーが起こった際に発火
  • beforeHistoryChange(url) - ブラウザ履歴が変化する直前に発火
  • hashChangeStart(url) - ページが遷移せず、ハッシュでの遷移(id付きのaタグでスクロールするなど)が開始する際に発火
  • hashChangeComplete(url) - ページが遷移せず、ハッシュでの遷移が完了した際に発火

routeChangeStartを使った例がこちらです。

const handleRouteChange = url => {
  console.log('App is changing to: ', url)
}

Router.events.on('routeChangeStart', handleRouteChange)

リアルワールドでのルーティング例

#1 ローディング中を示したい

SSRされたあとはSPAとしてページ遷移を行っているので、遷移中(ローディング中)ということをユーザーに示したいこともあります。
そういう場合はNProgressなどを導入しますが、これをどうやってNext.jsで実行するかが、こちらに例として載っています。
各イベントに対してNProgressのイベントをマッピングさせています。

pages/_app.js
Router.events.on('routeChangeStart', (url) => {
  console.log(`Loading: ${url}`)
  NProgress.start()
})
Router.events.on('routeChangeComplete', () => NProgress.done())
Router.events.on('routeChangeError', () => NProgress.done())

#2 オーバーレイコンポーネントが閉じないとき

SPAあるあるだと思いますが、ページ遷移時に前のコンポーネントの状態が残っていることがあると思います。例えば以下のケースです。

  • ハンバーガーメニューを押すとオーバーレイのコンポーネントが出てくる
  • メニュー内のリンクを押す
  • ページ遷移は起こるがオーバーレイのコンポーネントが閉じない

これを解決するのにはrouteChangeCompleteをイベントを利用すればよいでしょう。

Router.events.on('routeChangeComplete', () => closeMenu())

#3 記事中のaタグでもクライアントで移動したい

CMSからの記事をdangerouslySetInnerHTMLでそのまま出力するとき、クライアントサイドで遷移させたいaタグがあったとしても、そのまま<Link>コンポーネントを使用することは出来ないと思います。
そんなときは、componentDidMountライフサイクルなどで、ターゲットとなるaタグに上記のRouter.pushを使ってNext.jsのリンクを後付けすれば、クライアントサイドでの遷移が実現可能です。

#4 Next.js version6以前の場合、ルーティングイベントが重複して登録できない

Next.js version6以前だと、Router.event.onというイベントハンドラが無かったため、下記のようなutilを作成していました。今はversion7なので需要はないとは思いますが。

page-transition.js
import Router from "next/router";

export const hookOnRouterChangeStart = (fnc, that) => {
  const prev = Router.onRouteChangeStart;
  Router.onRouteChangeStart = async () => {
    if (typeof prev === "function") {
      prev.call(that);
    }
    fnc();
  };
};

おわりに

他にもNext.jsのルーティングは細かいところまでサポートしています。
例えば

  • <Link prefetch>によるページのプリフェッチ
  • scroll={false}によるスクロールイベントの中止
  • <Link>の子コンポーネントがaタグ以外の要素でのページ遷移
  • getInitialPropsが走らないで遷移をするShallow Routing
  • withRouterHOCによるrouterオブジェクトへのアクセス

などがあります。

詳しくはこちらをチェックして下さい。