Compose for Web でアドレスバーのURLとCompose Navigationのrouteを同期させてみる試み
最近の日課が KotlinJSとお戯れすることになっているてべすてんです。
これ is なに?
Compose for Web を使うことで Webの知識なしに Webアプリを作ることができます。
しかし そのままでは ブラウザのアドレスバーは考慮されず、画面遷移してもURLが変わることはありません。これでは リロードすると最初の画面に戻ってしまったり、特定の画面のURLをコピーするといったことができません。
そこでCompose for Web で SPA のような動きをするアプリができないか?といったチャレンジの記録になります。
今回はブラウザに標準で用意されている History API を使って ブラウザのURLを変更することで、ブラウザのアドレスバーのURLとアプリの画面を同期させてみました。
サンプル

ポイント:アドレスバーが画面遷移に応じて同期されている
(JSでビルドしてあるため、パフォーマンスは若干劣りますが、Safariでも動くはずです!)
使用技術
- Kotlin: 2.0.21
- Compose Multiplatform
- Navigation Compose: 2.8.0-alpha10
- History API
- Vercel
仕組み
Navigation Compose の画面遷移 と アドレスバー との同期には 以下の4つを考慮する必要があります。
- Navigation Composeの画面遷移に応じてアドレスバーのURLを書き換える。( Navigation Comopose -> アドレスバー )
- アドレスバーの変化に合わせてNavigation Composeを画面遷移させる。( アドレスバー -> Navigation Compose )
- どのページに来ても index.html に遷移させる
- 最初のURLに合わせて
startDestinationを変更する。
サンプルのリポジトリでは NavController.bindBrowserUrl() と言う拡張関数を中心に
1. Navigation Comopose -> アドレスバー の同期
bindBrowserUrl 関数内では NavController の BackStackEntry を監視し、 変更があった際に window.history.pushState() と言う関数を呼び出しています。
window.history.pushState() はサーバにリクエストを送ることなく アドレスバーのURLのみを変更できる History API の関数です。
このようにすることで Navigation Compose の画面遷移に合わせてアドレスバーのURLを書き換える Navigation Comopose -> アドレスバー の同期ができました。
2. アドレスバー -> Navigation Compose の同期
続いてアドレスバーのURLが変更された時に Navigation Composeに反映する方法です。
これには History API の popstate イベントを使用します。
addEventListener("popstate", { TODO() })
のように書くことで アドレスバーのURLの変更を検知できます。
あとはこれに応じてNavControllerで遷移させる処理を書けば アドレスバー -> Navigation Compose の同期も完成です。
3. どのページに来ても index.html に遷移させる
今回のようなSPAでは 1つのHTMLファイルで全てのページを処理します。なので例え存在しないパスに来たとしても 必ず index.html を返すようにする必要があります。
これにはHTTPサーバの設定が必要です。
今回のサンプルは元々 ホスティングサービスである Vercel にデプロイすることを念頭に置いていたため、Vercelの rewrite 機能を使ってみることにします。
プロジェクトルートに vercel.json を配置することで簡単に設定することができます。 どのページに来てもとは言いましたが JSファイルまでindex.htmlに飛ばされてしまうとComposeのコードが実行されないため これはrewriteの対象外にする必要がある 点には注意すると以下のようになるでしょう。
-
"buildCommand"はデプロイする際に実行されるコマンドです。通常はnpm buildなどを設定するかと思いますが、 kotlin jsでは./gradlew :composeApp:jsBrowserDistributionと言うコマンドで本番用のHTMLやJSを出力するためこのように設定しています。 -
"devCommand"はvercel devでテストをする際に実行するコマンドです。Vercelにデプロイした際の挙動をローカル環境でテストすることができます。kotlin jsでは./gradlew :composeApp:jsRunと言うコマンドで開発を行うためこのように設定しています。-
vercelコマンドの実行にはVercel CLIの事前インストールが必要です。npmがインストールされている場合npm i -g vercelで簡単にインストールできます。 - とは言うものの、rewriteの設定は
vercel devでは反映されないのか 確認できませんでした。rewriteの設定を検証するにはvercel deployコマンドで実際にデプロイしなければいけなさそうです。
-
より詳しい情報は Vercelのドキュメント を参照してください。
4. 最初のURLに合わせて startDestination を変更する
上記の設定をすることで どんなURLでアクセスされても index.html をレスポンスすることができるようになります。
あとは URLに合わせて NavHostの startDestination を書き換えましょう。
完成!

これでCompose for WebでSPAのようなアプリができるようになるはずですー! ヤッタネ!😎
良いCMPライフを!
Discussion