Next.jsのルーティング

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

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

```react: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の値にはクエリのオブジェクトを渡します。

```react: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オブジェクトを渡すことで、履歴の`push`と`replace`が可能です。

```react
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`を使った例がこちらです。

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

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

# リアルワールドでのルーティング例
## #1 ローディング中を示したい
SSRされたあとはSPAとしてページ遷移を行っているので、遷移中（ローディング中）ということをユーザーに示したいこともあります。
そういう場合はNProgressなどを導入しますが、これをどうやってNext.jsで実行するかが、[こちらに例として載っています。](https://github.com/zeit/next.js/blob/master/examples/with-loading/pages/_app.js)
各イベントに対してNProgressのイベントをマッピングさせています。

```react: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`をイベントを利用すればよいでしょう。

```react
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
- `withRouter`HOCによるrouterオブジェクトへのアクセス

などがあります。

[詳しくはこちらをチェックして下さい。](https://nextjs.org/docs/#routing)