Le Cauchemar du NAV

Pourquoi la chose la plus logique du monde est impossible nativement sur le web

Introduction

Bon. tu savais, toi, comment faire en sorte de charger juste une partie d'un contenu qui est commun à toutes les pages, et cela change automatiquement toutes les pages ?

Vous voyez ce que je veux dire ? J'ai un principe. Si le bout de code existe déjà plus de DEUX fois, on en fait un fichier à part et comme ça, on s'embête plus. C'est du bon sens. C'est l'informatique de base. Le fameux principe DRY (Don't Repeat Yourself).

J'ai naïvement testé ça. Bref... je vous explique pourquoi j'ai foiré et pourquoi cela foire. Accrochez-vous, on va aller plus loin dans la technique pour comprendre ce qu'est VRAIMENT un navigateur et comment fonctionne son moteur de rendu.

Les essais dans l'ordre

1. object

Celui là c'est le plus logique. On met :

object data="./_includes/nav.html"
/object

Ça charge le fichier. Ça s'affiche parfaitement. Les styles s'appliquent si ils sont à l'interieur du fichier. Tout est beau.

Jusqu'à ce que tu cliques sur un lien. Rien ne se passe.

Parce que le clic reste prisonnier à l'intérieur de l'object. Le navigateur crée ce qu'on appelle un Browsing Context séparé. C'est une frontière impénétrable en HTML pur (sans JS). Il ne peut pas changer l'URL de la page parente par défaut.

2. template

Ok donc on essaye le template. C'est fait pour ça non ?

Non. La balise <template> ne fait rien toute seule. Le parseur du navigateur la lit, vérifie que le HTML est valide, mais crée un "DocumentFragment" inerte. Il ne le dessine pas à l'écran.

Pour l'utiliser tu dois écrire du Javascript : le sélectionner, le cloner, l'insérer dans le DOM actif. Tu viens d'inventer ton propre micro-framework client. Retour à la case départ.

3. iframe

Pire que l'object. Même problème d'isolation de contexte (la fameuse politique Same-Origin et le cloisonnement de l'objet Window). En plus tu te bats avec le flux de la page : scrollbars, problèmes de hauteur fixe, CSS de la page parente qui ne traverse pas la frontière du DOM. C'est un enfer absolu.

4. fetch() + innerHTML

Là on passe dans le hack JS asynchrone :

fetch('./componant/nav.html')
.then(r => r.text())
.then(html => {
  document.querySelector('#nav').innerHTML = html
})

Ça marche ! Ouaaai ! Super ! Le nav s'affiche.

Non. (j'ai mal à mon web) Tu viens de poignarder les performances de ta page. Ce code s'exécute au Run-Time (pendant la navigation), APRÈS que le navigateur ait téléchargé et analysé ta page initiale. Tu forces le navigateur à faire un deuxième voyage réseau (Network Request) pour aller chercher le menu, puis à recalculer tout l'affichage (Reflow/Repaint). C'est ce qui cause le "Flash of Unstyled Content" ou les sauts de page.

5. PHP

Ça marche parfaitement. Bien sûr. Il suffit de faire :

<?php include 'nav.html'; ?>

Magie. Mais le problème, c'est que ça demande un moteur d'exécution côté serveur (un serveur Apache/Nginx avec le module PHP). À chaque fois qu'un visiteur clique, le serveur doit "réfléchir", assembler la page en direct, et l'envoyer.

C'est lourd. C'est lent. Et surtout, sur des hébergements ultra-rapides et gratuits comme Github Pages, on ne sert que des fichiers statiques. Il n'y a pas de "cerveau" PHP derrière. Donc c'est mort.

La vérité technique : le moteur sous le capot

L'absence de include en HTML n'est pas un oubli. C'est un choix d'ingénierie fondamental lié à la façon dont un navigateur dessine une page web.

Le "Critical Rendering Path" (Le chemin critique)

Quand ton navigateur reçoit un fichier HTML, il lit le texte de haut en bas et construit un arbre mathématique : le DOM (Document Object Model). Son but absolu est d'afficher le premier pixel à l'écran le plus vite possible.

Si une balise d'inclusion native existait, voici ce qu'il se passerait :

C'est ce qu'on appelle la cascade réseau (Network Waterfall). C'est catastrophique pour les performances. Le HTML a été conçu pour structurer un document unique qui peut être parsé en streaming, d'une seule traite.

La distinction Build-Time vs Run-Time

La solution professionnelle à ce problème n'est pas de forcer le navigateur du visiteur (Run-Time) à faire le travail d'assemblage. La solution, c'est de le faire AVANT, sur la machine du développeur ou sur le serveur de déploiement (Build-Time).

C'est exactement ce que fait Jekyll (ou n'importe quel générateur statique comme Astro, Eleventy ou Hugo).

1

Le moteur de Templating (Liquid)

Jekyll utilise un langage appelé Liquid. Quand tu écris {% include nav.html %}, ce n'est pas du HTML. C'est une instruction pour le compilateur de Jekyll.

2

La phase de Compilation (Build)

Au moment où tu envoies ton code sur GitHub, un programme (écrit en Ruby pour Jekyll) s'allume. Il scanne tes fichiers, lit tes variables (le Front Matter), attrape le code de ton composant `nav.html`, et l'injecte littéralement dans le flux de texte de tes pages. Il "aplatit" ton architecture.

3

Le résultat Statique

Le produit final de Jekyll est un bête dossier contenant des fichiers .html normaux. Le code dupliqué y est ! Mais c'est la machine qui l'a dupliqué pour toi.

Bénéfice: Zéro consommation de ressources CPU côté serveur lors de la visite. Zéro javascript côté client. Le navigateur reçoit un fichier plat, l'avale d'un coup, et l'affiche instantanément. C'est l'essence même de la performance web brute.

Conclusion

L'inclusion de composants HTML natifs sans JS et sans perte de performance relève de la physique quantique web : c'est un paradoxe.

Donc voilà. Tu as trois choix architecturaux :

  1. La punition : Tu copies/colles ton nav dans toutes les pages manuellement.
  2. L'obésité client (Run-Time) : Tu ajoutes un framework Javascript (React, Vue) qui va surcharger le navigateur de ton visiteur pour faire l'assemblage.
  3. La construction technique (Build-Time) : Tu utilises un générateur statique comme Jekyll pour faire le sale boulot en amont, et tu livres de la vitesse de Buzz l'éclaire (vers l'inf... pardon).

Voilà c'est tout, à toi de regarder sur le web