仕事で Nuxt.js を使っている。戻るボタンの扱いについて聞かれたので調べてみたところ、これが予想外に難しい。
戻るボタン対応は、Struts を代表とするポストバック系のフレームワークでも鬼門だったけれど、SPAの時代になってもあいかわらず鬼門のようだ。
Backボタンを押した際のブラウザ動作としては、タブ切り替えと同じ挙動の方がありがたいのではと思うのだけど、残念ながらそうはならない。元々、静的ページを前提にした仕組みだから、以前訪れたページがリロードされるような動きとなる。ただし、HTMLにはフォームというものがあるから、そこだけは考慮されていて、キャッシュが残っていればフォームの値だけは復元、なければ完全リロードとなかなかに際どい動きとなる。
もちろん、この動作はJavaScript全盛の今となっては好ましいものではない。フォームの値だけ残されたところで JavaScript のメモリ状態と整合性が取れず中途半端になってしまう。とはいえ、onunload など既存仕様と整合性が取れないことやメモリ状態も含めてキャッシュに保管するのは現実的ではないなど様々な理由が考えられ、この動作が変わることはないだろう。
Nuxt.js に関して言うと、すべての状態が JavaScript 管理となるため、フォームの値だけが残されるという中途半端な状態にはならない。$router.push による画面遷移とさして変わらない動作をする。逆に言えば、一般の静的ページで期待される動作を再現するためには工夫が必要になる、ということでもある。
きっと誰かがプラグインを作っているだろうと高をくくっていたのだけれど、意外にもこれといったものがない。たしかに多くの Web サービスでは、状態保持が必要な画面遷移もあまりないし、細かいことを気にしなければ Vuex で十分かもしれない。
とはいえ、私が仕事で関わるような業務系アプリケーションの場合、状態を伴う画面遷移の方がむしろ一般的なため、何らかの対応が必要となる。本来、仕事の一環なのだから業務時間中に作ればいいとは思うのだが、開発に専念できない現状で片手間にできるような内容ではないし、今後、別案件や個人的な開発にも流用したいことから、プライベートな時間を使ってモジュールを作成した。*1
そうして出来上がったのがこの nuxt-history-state だ。
このモジュールを作るにあたっては、Vue-Router の scrollBehavior と「vue-routerでbackwardかforwardかを判定する - Qiita」が参考になった。
基本的な方針としては、各ページにページ番号を持ち、画面遷移時にインスタンスの$data をページ番号に紐付け保管するという対応になる。ページ番号はブラウザ内で管理される履歴と紐付ける必要があるため、URL のクエリパラメータか history.pushState のいずれかで保持する必要がある。
クエリパラメータで紐付ける方が簡単なのはわかっていたが、URLが汚れる点が気になった。だから、history.pushState で何とかならないかと努力してみたものの、リロード後に Back ボタンを押した場合、popstate イベントが発生しない場合があることが判明したため、デフォルト動作ではリロードをサポートせず、オプションを設定することでクエリパラメータを使ったリロード対応モードになるようにした。
このモジュールを使うと、通常は難しい Back/Forward の判定やリロード後の Back/Forward 継続など、履歴管理が一通りできるようになる。まだ、開発して間もないので、こうした方が良いのではなど意見があれば、Issue に起票してほしい。