ページ遷移
ページが切り替わるまでの流れ
SPA ナビゲーションでは、ページ全体を読み直す代わりに、次のページの HTML を取得して <main id="main-content"> と一致する data-spa-region 要素を置き換えます。グローバルなスタイルなど、属性のないアプリケーションシェルはそのまま残ります。
Diagram source
sequenceDiagram
participant U as 利用者
participant R as SPAランタイム
participant S as サーバー
participant L as ClientLifecycle
participant H as HonoXクライアント
U->>R: 対象のリンクをクリック
R->>R: 前のページ移動を中断
R->>S: 対象URLを取得(Accept: text/html)
S-->>R: HTML
R->>L: ページスコープをunmount
R->>R: headを更新し、regionとmainを置き換え
R->>H: createClient()
R->>L: ページスコープをmount
R->>R: 履歴・スクロール位置・フォーカス・通知を更新
R-->>U: spa:navigation
処理の順序には意味があります。ページ固有の後始末は main を置き換える前に済ませ、新しいページの初期化処理は、HonoXが island を hydrate した後に実行します。
新しいページ移動が始まると、進行中のページ移動は中断されます。中断された処理は、ページ情報、DOM、履歴、ページのライフサイクル、フォーカス、通知のいずれも更新できません。
ページ移動の対象になるリンク
startSpaNavigation は document のクリックを監視し、次の条件をすべて満たす場合だけページ移動を処理します。
- イベントが
defaultPreventedではない - 主ボタン(左ボタン)でクリックされた
- Meta、Ctrl、Shift、Alt のいずれも押されていない
- クリック位置に最も近い
a[href]がshouldNavigateの条件を満たす - 現在のページのハッシュだけを変える移動ではない
shouldNavigate は、リンクが次の条件を満たすことを確認します。
targetがない、または_selfであるdownload属性がないdata-spa="false"が指定されていない- 同一オリジンかつ同一プロトコルである
ハッシュだけの移動とは、パスとクエリが現在のページと同じで、ハッシュが空でない場合です。この場合はブラウザ本来のページ内アンカーに任せます。
戻る・進む操作(popstate)はリンクの判定を通りません。現在の URL に対して、popstate: true を指定したページ移動を開始します。
ページ移動と中断処理
ページ移動ごとに AbortController を作成し、新しい移動が始まるたびに前の移動を中断します。
navigate は次の順序で処理します。
- クリックによる移動で、スクロール位置の復元が有効な場合は、現在の位置を
history.stateに保存する Accept: text/html, application/xhtml+xmlを付けて対象 URL を取得するDOMParserでレスポンスを解析する- 取得した document と現在の document の両方に
main#main-contentがあることを確認する - レスポンス URL から最終的な URL を求め、リダイレクトを反映する
onBeforePageCommit(signal)を実行する<head>を更新し、一致する region を置き換えた後に、現在のmainを取得したmainで置き換えるonPageCommit({ root, signal })を実行する- View Transition を使う場合は、DOM 更新の完了を待つ
- クリックによる移動なら履歴を追加し、スクロール位置を復元する
- ページのルートにフォーカスを移し、新しいページを読み上げ用に通知する
spa:navigationイベントを発火する
ランタイムは、非同期処理の区切りごとに、ページ移動の世代と signal を確認します。最新の移動だけが、ページ情報、DOM、履歴、ページのライフサイクル、フォーカス、通知、イベントを更新できます。
同じページ内での戻る・進む操作では、HTML を取得しません。保存済みのスクロール位置を復元し、ハッシュがあればその要素へ、なければページのルートへフォーカスを移します。
取得は15秒でタイムアウトし、HTML 以外の Content-Type が返された場合は失敗として扱います。リダイレクト先が別オリジンになった場合も失敗します。
中断されていないエラーが起きた場合、ランタイムは最終的な遷移先へ window.location.assign() を呼び、ページ全体の読み込みに切り替えます。ページ内容を置き換えた後にコミット処理が失敗した場合も同じです。どちらの経路でも、ブラウザにはサーバーが描画した完全な document が表示されます。これをページ全体の読み込みへのフォールバックと呼びます。