💨

Next.jsにおけるSPA的遷移の考え方

2021/09/10に公開

Next.jsにおけるSPA的遷移の考え方

追記

(2021年09月21日): Linkタグを用いるとSSR時でもブラウザリロードすることなくコンポーネントを再レンダーすることができる。
具体的には、サーバに対してGETリクエストを送り、getServerSidePropsを実行させた後をpropsを受け取り、再レンダーするような挙動になっている。

これとNext.jsのLayoutシステムを用いれば、ヘッダをアンマウントさせずに画面遷移はできるが、ページ内部のモーダルなどはレイアウトにするのは厳しいので本記事で説明するshallowオプションを扱うのが良いと思われる。

背景

インターン先で既存SSR(React)プロジェクトをNext.jsに移行する際に、プロフィールページのタブやモーダルといった箇所でコンポーネントの切り替えと共にURLも変わるようなSPA的遷移の実装を移行するのに苦労した

具体的にはreact-routerのhistory.push()にあたる機能が Next.js ですぐに見つけられなかった

今まではタブなどの切り替え時にhistory.push()をすることで実装していたが、
それがNext.jsではどのようにすれば実装できるかについて議論する

単語定義

SPA的遷移

ここではコンポーネントの切り替えに応じてHistoryが変わることとする

  • e.g. react-routerでは<Switch>, <Route>などでコンポーネントの切り替えを行い、そこにHistoryの変更が伴うような実装がされている

ページ

ここでは単一のサーバ側でのエンドポイントのこととする

  • react-routerで生成されるパス(例えば//about)は同じ1つのページを指す
  • Single Page Application という略称を考えるととても正しいはず

Next.jsにおける/pagesの概念

Next.jsでは/pagesに存在しているファイルたちはコンポーネントを出し分けるために存在するわけではなく、一つ一つが単一のエンドポイントとして存在する(ファイル同士が関連することはない)

それに対してreact-routerは<Switch>, <Route>などでHistoryオブジェクトに同期させてコンポーネントを出し分けてる

なぜ今までhistory.pushでタブやモーダルがいい感じに動いていたかというとページが一つだったためである
もう少し具体的にいうとexpress側で設定していたエンドポイントが1つだったため

app.get("/*", async (req, res, _next) => {
  ...
}

例えば下記のようなエンドポイントが用意されていた時

app.get("/id/:userSlug", async (req, res, _next) => {})
app.get("/id/:userSlug/connections", async (req, res, _next) => {})

/id/:userSlugから/:id/:userSlug/connectionsに対してサーバ側の処理もせず、ページリロードもせず、Historyオブジェクト内の履歴スタックのみ追加することは出来ないはずである
(/id/:userSlug内のreact-routerと/id/:userSlug/connections内のreact-routerはお互いを認識しない)

SPA的遷移の実現方法

一般的なSPA的遷移の実装方法

上述の/pagesの話だが、果たしてreact-routerを用いてコンポーネントを切り替えるときページが変わっているかというと変わっていない
(ユーザ的にではなくアプリケーション的に)

これらのことからSPA的遷移を実装する際には

  • 同一ページ内で
  • コンポーネントの切り替えを行う実装を用意し
  • コンポーネントの切り替え時にHistoryオブジェクトを変更してあげる

仕組みを用意すれば良いことがわかった

Next.jsでのSPA的遷移の実装方法

  • 同一ページ内で
  • コンポーネントの切り替えを行う実装を用意し
  • コンポーネントの切り替え時にHistoryオブジェクトを変更する

これを一つ一つ行えば良い。

同一ページ

  • [...slugs].tsx
    • Next.jsでは/id/[...slugs].tsxとすると/id/XX/id/XX/connectionsも同じページとして扱うことができる。
    • Dynamic Routes

コンポーネントの切り替えを行う実装

  • これはタブなりモーダルなりでそれぞれの実装を行う
    • e.g. react-tabs, イベント

コンポーネントの切り替え時にHistoryオブジェクトを変更する

  • shallowオプション
    • Linkタグ、NextRouter.push()で指定することができる
    • Shallow Routing

shallowオプションは結局何をやっているのか

shallowオプションが何をやっているかというと
getServerSidePropsなどをせず、表示をリロードさせず、Hisotryオブジェクトのみを変える動作
つまり上述の「Historyオブジェクトを変更する」部分である。

shallowオプションが同一ページ内でしか動作しない理由

  • SPA的遷移の実装方法を考えるとそもそもSPA的遷移は同一ページ内でしか行われないことが分かった
  • このことから同一ページ内でしかshallowオプションが動作しないのは制限というよりもそもそもの前提によるものであることが分かる

結論

ここまで「Next.jsにおけるSPA的遷移の考え方」について議論してきたが、
Next.jsのページ機構とSPA的遷移の実装方法を考えるとNext.jsでのSPA的遷移は実装可能なことが分かった。

shallowオプションを実装過程で使用することになるが、このオプションについて不便に感じるかはそれぞれの感じ方であり、
SPA的遷移に必要な機能は持っていることが分かった。

Discussion