by MintJams

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

もうライブラリはいらない?ブラウザネイティブのNavigation APIを使って、軽量なクライアントサイドルーターを実装する方法を解説。History APIとの違いや非対応ブラウザへの対応も網羅した実践ガイド。

Web開発において、シングルページアプリケーション(SPA)は今や当たり前になりました。しかし、そのSPAの根幹をなすクライアントサイドルーターは、React RouterやVue Routerといったライブラリに依存することがほとんどです。これらのライブラリは強力で便利ですが、時として過剰な機能やバンドルサイズの増大を招くこともあります。そんな中、ブラウザネイティブのAPIであるNavigation APIwindow.navigation)が注目を集めています。この記事では、この新しいAPIを使って、ライブラリに頼らない“素の”クライアントサイドルーターをどのように実装するか、その実戦的なポイントと非対応ブラウザへの対応策について掘り下げて解説します。このAPIを使いこなすことで、より軽量でパフォーマンスの高いアプリケーションを構築する道が開けるでしょう。


この記事でわかること

  • Navigation APIの基本的な使い方と利点
  • navigateイベントの活用方法
  • History APIとの違いとNavigation APIを使うメリット
  • 非対応ブラウザへの対応方法(プログレッシブエンハンスメント)
  • 実際のコード例で学ぶ、シンプルなクライアントサイドルーターの実装

Navigation APIとは?History APIとの違いと基本的な使い方

Navigation APIは、ブラウザのナビゲーション(進む、戻る、URL変更など)を制御するための新しいAPIです。従来のHistory APIpushState()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.urlevent.downloadRequestで正しくフィルタリングしているか確認しましょう。
  • 非同期処理の完了を待っているか: event.intercept()handler内の処理は非同期で行われることがほとんどです。awaitを使ってfetchやレンダリング処理の完了を待っているか、確認しましょう。
  • フォールバックの完全性: 非対応ブラウザで、popstateイベントが正しく設定されており、戻る/進むボタンが期待通りに動作するか検証しましょう。

チェックリスト

ステップ 確認項目
基本動作 navigation.navigate()でページが遷移するか?
✅ 戻る/進むボタンで期待通りのコンテンツが表示されるか?
ルーティング navigateイベントが正しく捕捉されているか?
event.intercept()が呼ばれているか?
フォールバック 'navigation' in windowで分岐が正しく行われているか?
✅ 非対応ブラウザで従来のページ遷移が動作するか?
コンテンツ fetchなどで取得したコンテンツが正しくレンダリングされているか?

まとめ

この記事では、ブラウザネイティブのNavigation APIを使って、軽量でシンプルなクライアントサイドルーターを実装する方法について解説しました。従来のライブラリに依存せず、ブラウザのネイティブな振る舞いを活用することで、よりパフォーマンスの高いSPAを構築することが可能です。また、プログレッシブエンハンスメントの手法を取り入れることで、Navigation API非対応のブラウザでもアプリケーションが動作するように配慮することも重要です。

この記事が、あなたのウェブ開発における新しいアプローチを考えるきっかけとなれば幸いです。ご覧いただきありがとうございました。


参考資料