もうルーターはいらない?Navigation APIで創る “素の”クライアントサイドルーター

Web開発において、シングルページアプリケーション(SPA)は今や当たり前になりました。しかし、そのSPAの根幹をなすクライアントサイドルーターは、React RouterやVue Routerといったライブラリに依存することがほとんどです。これらのライブラリは強力で便利ですが、時として過剰な機能やバンドルサイズの増大を招くこともあります。そんな中、ブラウザネイティブのAPIであるNavigation API(window.navigation
)が注目を集めています。この記事では、この新しいAPIを使って、ライブラリに頼らない“素の”クライアントサイドルーターをどのように実装するか、その実戦的なポイントと非対応ブラウザへの対応策について掘り下げて解説します。このAPIを使いこなすことで、より軽量でパフォーマンスの高いアプリケーションを構築する道が開けるでしょう。
この記事でわかること
- Navigation APIの基本的な使い方と利点
navigate
イベントの活用方法- History APIとの違いとNavigation APIを使うメリット
- 非対応ブラウザへの対応方法(プログレッシブエンハンスメント)
- 実際のコード例で学ぶ、シンプルなクライアントサイドルーターの実装
Navigation APIとは?History APIとの違いと基本的な使い方
Navigation APIは、ブラウザのナビゲーション(進む、戻る、URL変更など)を制御するための新しいAPIです。従来のHistory API
がpushState()
やreplaceState()
といったURLの変更操作に特化していたのに対し、Navigation APIはナビゲーション全体を包括的に扱います。最大の特長は、ナビゲーション開始時に発生するnavigate
イベントをフックして、ページ遷移をキャンセルしたり、独自の処理を差し込んだりできる点です。これにより、開発者はブラウザのネイティブな挙動に深く介入し、より柔軟なルーティングロジックを実装できます。
History APIとの比較
機能 | History API | Navigation API |
---|---|---|
イベントハンドリング | popstate イベントで履歴スタックの変更を検知するのみ。pushState() /replaceState() 単独呼び出しではイベントが発火しない |
navigate イベントですべてのナビゲーションをキャッチ可能 |
ナビゲーション制御 | URL変更を検知するだけで、遷移自体を制御できない | navigate イベントのintercept() メソッドで遷移を横取りし、独自の処理を実行できる |
状態管理 | history.state に単純なデータを保存 |
navigation.currentEntry.getState() で現在のエントリの状態を取得可能 |
ナビゲーションを制御するnavigate
イベント
navigate
イベントは、ユーザーがリンクをクリックしたり、戻る/進むボタンを押したり、プログラム的にnavigation.navigate()
を呼び出したりした際に発生します。このイベントのintercept()
メソッドを呼び出すことで、ブラウザのデフォルトのページ遷移を止め、JavaScriptでDOMを書き換えるなどのSPA的な処理を実行できます。
window.navigation.addEventListener('navigate', (event) => {
// 外部サイト・ダウンロード・インターセプト不可・ハッシュ遷移は素通し
const url = new URL(event.destination.url);
if (
url.origin !== location.origin || // クロスオリジン判定
event.downloadRequest !== null || // ダウンロード判定
event.canIntercept === false || // インターセプトできない遷移を判定
event.hashChange // ハッシュ(#fragment)変更のみの遷移を判定
) {
return;
}
// リンクのクリックやバック/フォワードボタンでの遷移を横取りする
event.intercept({
async handler() {
// ページのコンテンツを非同期で取得
// この例では、/about.html のような静的ファイルを想定
const response = await fetch(url.pathname);
const content = await response.text();
// コンテンツをレンダリングする
document.getElementById('content').innerHTML = content;
// タイトル更新(取得したHTMLから<title>を解析)
const m = content.match(/<title>([^<]*)<\/title>/i);
document.title = m?.[1] ?? '新しいページ';
},
});
});
このコードでは、navigate
イベントが発生すると、event.intercept()
を使ってブラウザの通常の遷移をキャンセルしています。そして、handler
の中でfetch
を使ってコンテンツを取得し、DOMを書き換えることでSPAのルーティングを実現しています。event.downloadRequest
によるダウンロードリンクの判定は、UXを損なわないための重要なテクニックです。
プログレッシブエンハンスメントによる非対応ブラウザへの対応
Navigation APIはまだ新しいAPIであり、すべてのブラウザが完全にサポートしているわけではありません(2025年8月現在)。Google ChromeやEdgeなど、Chromiumベースのブラウザは対応していますが、FirefoxやSafariはまだ実験的な段階です。そのため、非対応ブラウザでもアプリケーションが動作するように、プログレッシブエンハンスメントの考え方を取り入れる必要があります。
これは、モダンなブラウザではNavigation APIによるスムーズなSPA体験を提供しつつ、非対応ブラウザでは従来のページ遷移(a
タグによる通常のリンク遷移)にフォールバックさせる手法です。
const router = {
// Navigation APIが使えるかどうかをチェック
isSupported: 'navigation' in window,
init() {
if (this.isSupported) {
window.navigation.addEventListener('navigate', (event) => {
// ナビゲーションイベントを横取り
const url = new URL(event.destination.url);
if (url.origin !== location.origin) {
return;
}
event.intercept({
async handler() {
// SPAのルーティング処理
await this.handleRoute(url.pathname);
},
});
});
} else {
// 非対応ブラウザ用のフォールバック処理
// 従来のHistory APIやaタグのクリックイベントで対応
document.body.addEventListener('click', (event) => {
const anchor = event.target.closest('a');
if (
anchor &&
anchor.origin === location.origin &&
!anchor.hasAttribute('download') &&
anchor.target !== '_blank' &&
!event.defaultPrevented &&
event.button === 0 &&
!event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey
) {
event.preventDefault();
history.pushState({}, '', anchor.href);
this.handleRoute(anchor.pathname);
}
});
// 戻る/進むボタンに対応
window.addEventListener('popstate', () => {
this.handleRoute(location.pathname);
});
}
},
async handleRoute(pathname) {
const routeContent = await this.fetchContent(pathname);
document.getElementById('content').innerHTML = routeContent;
},
async fetchContent(pathname) {
// ページコンテンツを取得する関数
// ...
},
};
router.init();
このように、最初に'navigation' in window
でAPIの存在をチェックし、利用可能ならNavigation APIを使用し、そうでなければ従来の<a>
タグのクリックイベントなどを利用してルーティング処理を実装します。これにより、すべてのユーザーに適切な体験を提供できます。
トラブル時のチェックポイント
もし、Navigation APIを使ったルーティングがうまく動かない場合は、以下のポイントを確認してみてください。
navigate
イベントのガード: 外部リンクやダウンロードリンク、ハッシュ変更など、意図しないナビゲーションをevent.destination.url
やevent.downloadRequest
で正しくフィルタリングしているか確認しましょう。- 非同期処理の完了を待っているか:
event.intercept()
のhandler
内の処理は非同期で行われることがほとんどです。await
を使ってfetch
やレンダリング処理の完了を待っているか、確認しましょう。 - フォールバックの完全性: 非対応ブラウザで、
popstate
イベントが正しく設定されており、戻る/進むボタンが期待通りに動作するか検証しましょう。
チェックリスト
ステップ | 確認項目 |
---|---|
基本動作 | ✅ navigation.navigate() でページが遷移するか? |
✅ 戻る/進むボタンで期待通りのコンテンツが表示されるか? | |
ルーティング | ✅ navigate イベントが正しく捕捉されているか? |
✅ event.intercept() が呼ばれているか? |
|
フォールバック | ✅ 'navigation' in window で分岐が正しく行われているか? |
✅ 非対応ブラウザで従来のページ遷移が動作するか? | |
コンテンツ | ✅ fetch などで取得したコンテンツが正しくレンダリングされているか? |
まとめ
この記事では、ブラウザネイティブのNavigation APIを使って、軽量でシンプルなクライアントサイドルーターを実装する方法について解説しました。従来のライブラリに依存せず、ブラウザのネイティブな振る舞いを活用することで、よりパフォーマンスの高いSPAを構築することが可能です。また、プログレッシブエンハンスメントの手法を取り入れることで、Navigation API非対応のブラウザでもアプリケーションが動作するように配慮することも重要です。
この記事が、あなたのウェブ開発における新しいアプローチを考えるきっかけとなれば幸いです。ご覧いただきありがとうございました。