「SPAのタブ永遠に開きっぱなし問題」を更新ボタンを設置せず解決した
https://scrapbox.io/files/639004dfc646610021fbec0b.png
こんにちは。強いUIはボタンを捨てるをスローガンにScrapboxを開発しています。shokaiですshokai.icon 昨日はHelpfeelエンジニアのyado.iconさんでした
<a>タグの挙動を工夫する事で、Scrapboxからhttps://gyazo.com/8897d27735f47f4e4048f5fab181cad5みたいなボタンをなくしました
更新ボタンの役割は2つ
更新がある事を教える
押すとアプリが更新される
Scrapboxも昔こういうメニューがあった
https://gyazo.com/bb600b7180d2c9843e7d137da95a45f6
今はもう無い
では解説ですshokai.icon
SPAのタブ永遠に開きっぱなし問題とは?
staticなwebサイトでリンクをクリックすると
最新のassets(HTML/JavaScript/CSS/画像)を取得して、画面遷移する 最初に読み込んだassetsを使いまわしつつ
主に本文のデータだけをAjaxで取得し、画面表示を更新する ユーザーによっては、昔の古いassetsをずっと使い続ける事になる
ブラウザをリロードするか、ページを新規ウィンドウで開き直すまで
webブラウザが古いassets(HTML/JavaScript/CSS)を使い続けると
サーバー側のAPIが更新された場合に、うまく動かない可能性がある
より良い挙動に修正されたバージョンがあるのに使ってもらえない事もある
「そのbugはリロードしたら直るのでリロードしてください」というサポート対応が、一定数あったりする
特にScrapboxでは発生しやすい
メモを書くアプリは開きっぱなしになりがち
SPAである
ブラウザを閉じたりリロードしたりせずに、何日もscrapboxを使い続けるユーザーが多い
それら1つずつをリロードしてくれる日は永遠に来ない
最長で6ヶ月前のassetsが使い続けられていた
2021年に判明した
互換性の無くなった古いclientで接続→サーバーから拒否→再接続→拒否→再接続…
が繰り返される事、6ヶ月間
淡々と記録される接続エラー
ちなみに、ScrapboxがSPAとしてどんな事をやっているかは
スマホのネイティブアプリみたいになっている
サーバーから取得するのは(ほぼ)コンテンツのテキストデータだけ
assets(HTML/JavaScript/CSS/画像)はアプリの様にインストールされ、バックグラウンドで更新される
新しいassetsが準備できている時だけpushStateしない様にした
そうして生まれたのがこの機能です
https://gyazo.com/58203bb399d39805f59b78d458a0daa1
上のGIFは、新しいアプリケーションコードをサーバーにデプロイした直後の様子をローカル開発環境で再現したものです
サーバー側でnpm run build:assets-jsonを実行してから
このコマンドが、新しいclient jsのビルドに相当する
3回の画面遷移を行っている
1回目と3回目の<a>タグクリック
SPAとしての画面遷移
エディタの中だけが再描画されている
navbarや右側のメニューは再描画されていない
2回目の<a>タグクリック
画面全体がリロードされている
SPAではないstaticなwebサイトの画面遷移
assets(HTML/JavaScript/CSS)が更新される
pushStateと対比した場合の呼称として、社内では仮にこう呼んでいますshokai.icon
何がおきているのか?
1回目の<a>タグクリックによる通信で
2秒ほどでダウンロードが完了
以後、<a>タグの挙動が切り替わる
2回目の<a>タグクリック
full document loadが実行され、最新のassetsに切り替わる
どれぐらい速いかというと、この挙動に気づいて「たまにpushStateしない事あるよね」と言及しているユーザーが全く見当たらないぐらい速いshokai.icon
ここまでの道のり
2019年ごろには思いついていた
しかし、すぐ実装するのは無理だった
準備として、色々な改善が行われた
サーバーからの読み込みを減らして初回レンダリング速度を上げる
assets cacheがかなり高い頻度で更新されるようになった
full document loadによる画面チラつきを低減させた
予備テスト
page & pagelist画面からproject設定画面への画面遷移にpushStateを使わないようにしてみた ほとんど気にならなかったshokai.icon
たぶんshokai.iconbalar.icon以外誰も気づいてない
なぜユーザーによる画面遷移(Aタグクリック)をトリガーとするのか?
https://gyazo.com/8897d27735f47f4e4048f5fab181cad5みたいなボタンは設置しない
目立つ更新ボタンを置くと「今テキスト書いてるんだから邪魔するな」という気分になる
かといって、更新を促すボタンは目立たなければ意味が無い
Scrapboxは年間500回以上リリースされる
ユーザーは毎日1〜4回「更新があります」を見る事になる
しかも、たくさんブラウザタブを開いている人は、その数だけリロードボタンを押して回らなければならない
更新地獄である
例えば一週間以上古い時のみ「更新があります」を表示する事も可能だが
各リリースに「重要かどうか」という、新たなメタ情報が必要となる
運用がどんどん複雑になる
重要フラグをつけたリリースにbugがあった場合
rollbackするとサーバーは巻き戻るのに、クライアントは巻き戻らないかもしれない
ユーザー
開発者
サービス運用者
攻撃者
誰かの都合・対策を優先すると、他がそのぶんの面倒をかぶってしまう
不具合等でオンプレ版docker imageを古いバージョンに戻す事がある
docker imageのtagを切り替えて、コンテナを再起動するだけで古いassetsが行き渡るようにした
やはり自動リロードはタイミングが難しい
ユーザーの操作を邪魔してしまう
input formに入力中かもしれない
未送信のデータが残っているかもしれない
ちゃんと動作してるか?の検証も難しい
長時間ブラウザを放置する、等
最終的にこうなった
動作フロー
ユーザーが<a>タグをクリック
以下の条件を全て満たす時
未保存の編集データが無い
未保存の編集データがある場合の様子
https://gyazo.com/013f81925d4109bbda35b644e7a8dc11
右下にspinnerがグルグル回っているのが、未保存の状態
新しいassets cacheが準備できていてもfull document loadせず、pushStateを行う
こうしないと編集データを失ってしまう事がある
結果
ブラウザのリロードボタンを押さなくても、最新のassets(HTML/JavaScript/CSS)に更新されるようになった
開発者視点
古いclientの存在を考慮せず、サーバー側のAPIをガンガン更新してもわりと大丈夫になった
ユーザー視点
普通に使っていれば、画面遷移のタイミングでリロード相当の事を適宜やってくれて便利
「そのbugはリロードしたら直るのでリロードしてください」というサポート対応がゼロになった
「なんか突然リロードされてデータが消えました」という苦情も無い
full document loadによってSPAが更新されている事に気づいている人も、たぶんいない
あまりにも自然に2種類の画面遷移が使い分けられている為
というような感じで、 株式会社Helpfeelでは、シンプルなUIを実現するため「ボタンをもっとわかりやすく、ラベルも工夫して……いや、アルゴリズムを工夫すればボタンぜんぶ消滅させられるのでは!?」という根源的な思考ができるエンジニアを募集しています 明日はテクニカルライターのmiyabaraさんです