Edited at
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オブジェクトへのアクセス

などがあります。

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