Next.js(App router)でrouter.push()のページ遷移が非常に遅かったのでlazy loadingを取り入れてみた

# はじめに

社内ツールでNext.js（App router）を使用する中で困ったことと解決策を記載してみます。
Next.jsもApp routerもまだまだ触り始めたばかりですのでもし誤った認識があればご教示いただけます幸いです。

# 今回困ったこと
あるページ（以下、Page A）からの特定のアクション（たとえばクリックなど）をきっかけにrouter.push()を使用して別のページ（以下、Page B）に遷移する機能を実装しようとしました。

しかし、router.push()による遷移先であるPage Bには非常に時間のかかるfetchを扱うサーバーコンポーネントが存在しており、そのためrouter.push()が発火してからもサーバーサイドでPage Bを読み込んでいる間Page Aに留まり、結果としてPage Bへの遷移までに時間がかかるという問題が発生しました。

Page Bでのfetch処理のパフォーマンスを改善することで根本的な解決を行いましたが、修正の過程で別の解決策も有用でしたので、以下に記載します。

# 解決策
lazy loadingを使用することで、Page Bに遷移してから重いコンポーネントをloadingする

--- 

[MDN](https://developer.mozilla.org/ja/docs/Web/Performance/Lazy_loading)ではlazy loadingは（遅延読み込み）以下のように説明されています。
>遅延読み込み (Lazy loading) とは、リソースをノンブロッキング（クリティカルでない）ものとして識別し、必要なときだけこれらを読み込む戦略のことです。クリティカルレンダリングパス (en-US)の長さを短縮する方法であり、ページのロード時間の短縮につながります。


# 具体的なコード
lazy loadingの書き方は2パターンあるようです。

## 前提：lazy loadingしないパターン

``` jsx
// Server Component:
import MyServerComponent from '../components/MyServerComponent'
 
export default function ServerComponentExample() {
    return (
        <div>
            <div>lazy loadingしないパターン</div>
            <MyServerComponent />
        </div>
    )
}
```
これはlazy loadingしていないコードで、MyServerComponentが読み込まれるまでページ全体が読み込まれません。
このMyServerComponentをlazy loadingするように書き換えたいと思います！


## パターン1：Dynamic Importsを使用する
``` jsx
import dynamic from 'next/dynamic'
 
// Server Component:
const MyServerComponent = dynamic(() => import('../components/MyServerComponent'))
 
export default function ServerComponentExample() {
    return (
        <div>
            <div>この文字はページ遷移後すぐに表示されますが、Componentは遷移してから読み込みが始まります</div>
            <MyServerComponent />
        </div>
    )
}
```
こちらは公式にも詳しく記載されている方法です。
上記のような書き方を行うことでMyServerComponentがlazy loadingされます。
サーバーコンポーネントを動的にインポートする場合、サーバーコンポーネント自体ではなく、サーバーコンポーネントの子であるクライアントコンポーネントのみがlazy loadingされるようです。

## パターン2：React.lazy()&Suspenseを使用する
``` jsx
import { Suspense, lazy } from 'react'
import { Spinner } from '../components/Spinner'
const MyServerComponent = lazy(() => import('../components/MyServerComponent'))

export const dynamic = 'force-dynamic'

export default function ServerComponentExample() {
    return (
        <>
        	<div>この文字はページ遷移後すぐに表示されますが、Componentは遷移してから読み込みが始まります</div>
            <Suspense fallback={<Spinner />}> 
                <MyServerComponent />
            </Suspense>
        </>
    )
}
```
そもそもnext/dynamicはReact.lazy() と Suspenseの合わせ技のような書き方で、Next.jsのドキュメントから察するにnext/dynamicを使うのが一般的のようです。

一方で、サンプルコードにも記載した`export const dynamic = 'force-dynamic’`(SSG無効)などをはじめとした`export const dynamic = 'xxx’`を使用している部分では、React.lazy()&Suspenseを使用する方が使いやすいな個人的に思いました。
理由はパターン1のnext/dynamicを使うと `import dynamic from 'next/dynamic'` と`export const dynamic = 'xxx’`とで、変数の`dynamic`がバッティングしてしまうためです。

# まとめ
router.push()によるpage遷移が遅いのはユーザーにとってストレスなので、一解決策としてlazy loadingは有用だなと思いました。

参考になれば幸いです。

# 参考
- MDN
    - [遅延読み込みについて](https://developer.mozilla.org/ja/docs/Web/Performance/Lazy_loading)
- Next.js公式ドキュメント
    - [Lazy Loadingについて](https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading)
    - `export const dynamic = 'xxx’`など、[Route Segment Configについて](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config)