Aller au contenu principal
Site en cours de refonte — quelques pages peuvent bouger ou évoluer.
11 mars 2026

Navigation API (Baseline) : la bonne façon de gérer la navigation dans une SPA (sans hacks)

La Navigation API standardise enfin ce que les SPA bricolent depuis 10 ans : intercepter, historiser, restaurer le scroll et animer proprement.

7 min de lecture
24 vues
réactions
Partager :
Navigation API (Baseline) : la bonne façon de gérer la navigation dans une SPA (sans hacks)

Les SPA ont longtemps “simulé” la navigation : pushState à la main, popstate incomplet, scroll restauré au petit bonheur, transitions qui clignotent… Bref, ça marche, mais c’est fragile. La Navigation API arrive avec une promesse simple : revenir à une navigation fiable, standard, et pilotable — et elle est maintenant Baseline, donc utilisable sans vivre dans la peur du support navigateur.

Le vrai problème des routers SPA : ils reconstruisent un navigateur

Dans une SPA, on veut :

  • Intercepter un clic sur un lien interne (sans recharger)

  • Gérer back/forward proprement

  • Synchroniser URL, état, scroll, focus, analytics

  • Garder une UX fluide (chargements, transitions, pas de “flash”)

Historiquement, on fait ça avec :

  • history.pushState() / replaceState()

  • window.onpopstate (qui n’est pas un événement de “navigation”, mais un signal de changement d’entrée d’historique)

  • du code spécifique pour les formulaires, les redirects, les middlewares, la restauration du scroll…

Ça finit souvent en “framework routing” très intelligent… et très couplé au DOM, au runtime, et aux conventions internes. La Navigation API remet un cadre : un événement de navigation unifié, un modèle d’entrées d’historique modernisé, et des primitives pensées pour les soft navigations.

Au lieu de guetter popstate et d’espérer ne rien casser, vous écoutez un vrai événement :

  • navigation + événement navigate : un point d’entrée unique pour gérer la navigation

  • event.intercept() : vous dites au navigateur “je prends la main” et vous exécutez une navigation SPA propre

  • navigation.navigate() : vous déclenchez une navigation comme un citoyen du navigateur (pas comme une bidouille)

  • Entrées d’historique riches : vous manipulez des NavigationHistoryEntry (id, key, index, state…)

  • Scroll restoration et transitions : des hooks plus cohérents, et une meilleure intégration avec les transitions modernes

Adopter la Navigation API dans une app existante (sans tout réécrire)

Bonne nouvelle : vous pouvez l’introduire progressivement. L’objectif n’est pas de jeter votre router, mais de remplacer la plomberie (interception + historique + scroll) par l’API standard.

1) Mettre un feature flag (obligatoire en prod)

Commencez par détecter le support. Si ce n’est pas dispo, vous retombez sur votre mécanisme actuel (router classique).

2) Intercepter les navigations internes

Le cœur, c’est l’événement navigate. Vous filtrez ce que vous prenez en charge (même origine, pas de téléchargement, pas de navigation que vous ne savez pas gérer), puis vous interceptez.

// Router SPA minimal basé sur la Navigation API
function enableNavigationAPI({ render, load }) {
  if (!('navigation' in window)) return false;

  navigation.addEventListener('navigate', (event) => {
    // URL cible
    const url = new URL(event.destination.url);

    // On ne gère que la même origine
    if (url.origin !== location.origin) return;

    // Si le navigateur demande explicitement un reload, on laisse faire
    if (event.navigationType === 'reload') return;

    // À vous de décider : on ignore certains cas (ex: téléchargement)
    // if (event.downloadRequest) return;

    event.intercept({
      // Optionnel : contrôler le scroll (voir section dédiée)
      scroll: 'manual',

      async handler() {
        // 1) Charger les données
        const data = await load(url);

        // 2) Rendre l'écran
        render(url, data);
      }
    });
  });

  return true;
}

Ce pattern a deux gros avantages :

  • Back/forward déclenche le même pipeline que vos navigations “clic”

  • Vous centralisez enfin la logique de navigation (et donc les effets de bord)

3) Déclencher une navigation “propre” depuis votre code

Dans beaucoup de SPA, on fait history.pushState puis on render. Ici, on peut naviguer via l’API :

  • navigation.navigate('/produits/42')

  • navigation.navigate(url, { state: {...} }) pour stocker un état d’entrée

Intérêt : vous restez dans le modèle “navigation” du navigateur, ce qui rend l’intégration plus cohérente (observabilité, transitions, comportements attendus).

State, historique, et “soft navigations” : enfin quelque chose de fiable

Le vieux couple pushState / popstate force souvent à réinventer :

  • une identité d’entrée d’historique

  • un mapping URL → écran

  • une gestion d’état (ex: quel onglet était ouvert sur la page précédente ?)

Avec la Navigation API, vous pouvez vous appuyer sur :

  • navigation.currentEntry : l’entrée courante

  • navigation.entries() : la liste des entrées (utile pour debug / UX avancée)

  • entry.getState() : récupérer l’état associé

Le résultat : vous pouvez stocker des infos utiles (un filtre, un tri, une ancre logique) sans polluer l’URL, tout en gardant un back/forward cohérent.

Scroll restoration : arrêter de casser le cerveau de l’utilisateur

La restauration du scroll est un des sujets les plus pénibles en SPA :

  • Back doit revenir au scroll précédent (comme un vrai site)

  • Une navigation “nouvelle page” doit souvent remonter en haut

  • Mais certaines navigations internes (tabs, filters) ne doivent pas bouger

Avec event.intercept({ scroll: 'manual' }) vous reprenez la main de façon explicite, au bon endroit (dans le handler de navigation), au lieu d’éparpiller ça dans 12 hooks.

navigation.addEventListener('navigate', (event) => {
  const url = new URL(event.destination.url);
  if (url.origin !== location.origin) return;

  event.intercept({
    scroll: 'manual',
    async handler() {
      await appNavigate(url);

      // Exemple de règle simple :
      // - back/forward : restaurer
      // - navigation classique : top
      if (event.navigationType === 'traverse') {
        // Si votre app stocke des positions, restaurez ici.
        restoreScrollForEntry(event.destination);
      } else {
        window.scrollTo({ top: 0, left: 0, behavior: 'instant' });
      }
    }
  });
});

À adapter à votre UI (listes, pages infinies, scroll dans un container, etc.), mais l’idée est là : la navigation devient le chef d’orchestre.

Perf et UX : là où ça fait vraiment la différence

La Navigation API n’est pas “juste un nouvel event”. Elle aide à mettre au propre des pratiques qui impactent directement vos métriques et la sensation de qualité.

Moins de jank, transitions plus propres

Quand la navigation est centralisée, vous pouvez :

  • déclencher un état “loading” cohérent (pas 3 loaders concurrents)

  • couper les requêtes précédentes quand une nouvelle navigation arrive (abort)

  • brancher des transitions (y compris des transitions de vue) sans hacks

Soft navigations mieux instrumentées

Le web moderne mesure de plus en plus les “soft navigations” (navigations sans rechargement) côté navigateurs et outils. Une navigation SPA qui suit un modèle standard est plus simple à observer, et plus facile à rendre robuste (notamment quand votre app grossit).

Cas limites à anticiper (sinon vous allez vous tirer une balle dans le pied)

  • Liens externes et cross-origin : ne les interceptez pas.

  • Téléchargements : laissez le navigateur gérer.

  • Reload explicite : si l’utilisateur force un refresh, ne luttez pas.

  • Form submissions : choisissez une stratégie claire (vraie soumission vs interception SPA). La Navigation API est justement un endroit propre pour traiter ce cas, au lieu d’un patchwork d’handlers.

  • Accessibilité : après navigation, gérez focus et titre de page. Une navigation propre n’est pas une navigation accessible par magie.

Une stratégie réaliste : “Navigation API d’abord, router ensuite”

Si vous avez déjà React Router, Vue Router, Nuxt, etc., l’approche la plus saine est :

  • Introduire la Navigation API comme couche de navigation bas niveau (interception/historique)

  • Faire passer votre router applicatif (matching routes → rendu) derrière event.intercept()

  • Déplacer progressivement scroll restoration, analytics, gestion des annulations dans ce pipeline unique

Vous gardez vos routes, vos loaders, vos layouts… mais vous arrêtez de bricoler le navigateur.

Conclusion

Le meilleur argument pour la Navigation API n’est pas “c’est nouveau”. C’est : ça simplifie un problème que les SPA ont rendu inutilement compliqué. Interception standard, historique plus fiable, scroll maîtrisé, transitions mieux intégrées… et maintenant que c’est Baseline, on peut enfin la considérer comme un choix pragmatique, pas un pari.

Sources

Cet article vous a plu ?

Commentaires

Laisser un commentaire

Entre 10 et 2000 caractères

Les commentaires sont modérés avant publication.

Aucun commentaire pour le moment.

Soyez le premier à donner votre avis !