Un intranet local est un petit site interne fait de fichiers HTML, CSS et JavaScript, posé dans un dossier partagé et ouvert directement dans le navigateur. Il n’a besoin ni de serveur, ni d’installation, ni de connexion internet : un double-clic sur index.html suffit, et tous les calculs se font sur le poste. Pour un outil de calcul, un simulateur ou un formulaire guidé, c’est souvent la solution la plus simple à déployer dans une PME.
C’est le format que j’ai utilisé chez mon employeur pour transformer des classeurs Excel complexes en mini-sites intranet locaux : mêmes formules, mais chaque feuille devient un écran clair et guidé. Encore faut-il connaître les règles du jeu, car le navigateur traite les fichiers locaux avec méfiance. Voici ce qui fonctionne, ce qui ne fonctionne pas, où ranger les données et à quel moment un serveur devient nécessaire.
Le principe : un dossier, une page, aucun serveur
L’application tient dans un seul dossier, organisé par exemple ainsi :
index.html : la page, avec ses écrans ;
css/app.css : la mise en forme ;
js/regles.js : les calculs, regroupés à un seul endroit et testables ;
js/app.js : l’interface, qui lit les saisies et affiche les résultats ;
donnees/baremes.js : les données de référence, comme les barèmes ou les listes de choix.
Quand on ouvre la page depuis le disque ou un partage réseau, l’adresse affichée commence par file:// au lieu de https:// (MDN). Le navigateur affiche la page, applique les styles et exécute normalement les scripts classiques : formulaires, calculs et impression fonctionnent, même hors ligne. En pratique, on dépose le dossier sur un partage accessible à l’équipe et on crée un raccourci vers index.html sur chaque poste.
Les trois limites du protocole file://
Pour des raisons de sécurité, de nombreux navigateurs, dont Firefox et Chrome, donnent désormais aux fichiers locaux une origine dite opaque : les chargements soumis aux règles CORS échouent entre fichiers locaux (MDN). Les scripts classiques, les feuilles de style et les images se chargent normalement ; le reste demande trois adaptations.
1. Pas de modules JavaScript
Un script chargé avec type="module" déclenche une erreur CORS quand la page est ouverte en file:// (MDN). Il faut donc des scripts classiques, chargés dans l’ordre, qui partagent un seul objet global pour ne pas se marcher dessus : window.App = window.App || {}; en tête de chaque fichier, puis App.calculerRemise = function (montant) { … };. Si vous développez avec des modules, un outil d’assemblage peut produire un fichier classique unique pour la livraison.
2. Pas de fetch() vers les fichiers voisins
Les requêtes fetch() et XMLHttpRequest soumises à CORS n’acceptent que les adresses HTTP et HTTPS : lire un baremes.json posé à côté de la page échoue (MDN). La parade est simple : écrire les données de référence dans un fichier JavaScript, App.BAREMES = [{ seuil: 0, taux: 0 }, { seuil: 1000, taux: 0.02 }];, chargé par une balise <script src="donnees/baremes.js"></script>. Pour lire un fichier fourni par l’utilisateur, on passe par un bouton d’import, décrit plus bas.
3. Un stockage du navigateur au comportement non garanti
Pour les pages ouvertes depuis une URL file:, le comportement de localStorage n’est pas défini et peut varier selon les navigateurs (MDN). Selon MDN, les navigateurs actuels semblent donner à chaque URL file: son propre espace, sans aucune garantie, et certains ont déjà changé de règle. Conséquence possible : la même page ouverte par un autre chemin (une lettre de lecteur réseau au lieu du chemin du serveur, par exemple) ou après un déplacement du dossier retrouve un espace vide. Préfixez chaque clé avec le nom de l’outil, comme 'devis:brouillon', entourez les accès d’un try…catch, et ne confiez jamais au navigateur la seule copie d’une donnée.
Où vivent les données : cinq options
Une application locale manipule deux sortes de données : les données de référence, que seul le responsable de l’outil modifie, et les données saisies par les utilisateurs. Chacune a sa place.
Les options de stockage d’une application HTML locale
| Option | Ce que c’est | Où sont les données | Usage conseillé |
| Fichier .js de référence | Barèmes, listes et paramètres écrits en JavaScript | Dans le dossier de l’application | Données de référence, en lecture seule |
| localStorage | Paires clé-valeur en texte, jusqu’à 5 Mio par origine | Dans le navigateur du poste | Préférences, brouillon en cours |
| IndexedDB | Base d’objets asynchrone, avec index et transactions | Dans le navigateur du poste | Historique local, volumes importants |
| Export et import | L’utilisateur enregistre un fichier JSON ou CSV, puis le rouvre | Là où l’utilisateur l’enregistre | Copie de référence, échanges avec Excel |
| API File System Access | Lecture et réécriture d’un fichier choisi par l’utilisateur | Sur le disque ou le partage | Sauvegarde directe, dans Chrome et Edge (pas Firefox ni Safari) |
La limite de 5 Mio pour localStorage vient de la page de MDN sur les quotas de stockage. IndexedDB accepte des volumes bien plus importants, y compris des fichiers, mais il reste attaché à un navigateur, sur un poste. La règle à retenir : le navigateur sert de mémoire de travail, les fichiers servent de référence.
Pourquoi le navigateur n’est pas un lieu de stockage sûr
Les données d’un navigateur peuvent disparaître sans que l’utilisateur ait rien fait de mal :
- par défaut, le stockage est dit « au mieux » : quand le disque manque de place, le navigateur peut effacer les données des sites les moins récemment utilisés, et il efface alors toutes les données d’une origine d’un coup (MDN) ;
- quand sa protection contre le suivi intersites est activée, Safari supprime les données créées par script d’un site avec lequel l’utilisateur n’a eu aucune interaction pendant sept jours d’utilisation du navigateur (même source) ;
- en navigation privée,
localStorage est vidé à la fermeture du dernier onglet privé (MDN) ;
- un nettoyage des données de navigation, un changement de navigateur ou de poste, et tout est perdu ;
- ces données restent sur le poste : en général, elles ne profitent pas des sauvegardes du partage réseau.
On peut demander un stockage persistant avec navigator.storage.persist(), que le navigateur accorde ou non (MDN). C’est utile, mais pas suffisant : prévoyez toujours un export.
Export et import : la solution qui fonctionne partout
Le moyen le plus fiable de conserver et de partager des données sans serveur est aussi le plus ancien : un fichier. L’utilisateur clique sur « Exporter » et le navigateur télécharge un fichier ; il clique sur « Importer » pour le rouvrir.
- Export : on transforme les données en texte avec
JSON.stringify(donnees, null, 2), on en fait un Blob, puis un lien temporaire : a.href = URL.createObjectURL(blob); a.download = 'suivi.json'; a.click();. L’attribut download fonctionne avec les adresses blob: (MDN), donc aussi depuis une page locale.
- Import : un champ
<input type="file" accept=".json">, puis JSON.parse(await input.files[0].text()) (MDN). Vérifiez la structure et la version du fichier avant de remplacer quoi que ce soit.
- CSV pour Excel : un CSV encodé en UTF-8 s’ouvre correctement par double-clic dans Excel s’il commence par une marque d’ordre d’octets, ou BOM (Microsoft) :
new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8' }). Le séparateur de liste dépend des paramètres régionaux de Windows (Microsoft) : testez l’ouverture sur un poste de l’équipe.
Ajoutez dans chaque fichier exporté la date, la version de l’application et le nom de l’outil. Ces trois informations permettent, des mois plus tard, de savoir d’où vient un fichier et s’il peut encore être réimporté.
Écrire directement dans un fichier : l’API File System Access
Télécharger un nouveau fichier à chaque sauvegarde finit par encombrer le dossier Téléchargements. L’API File System Access va plus loin : l’utilisateur choisit un fichier une fois, et l’application peut le relire et le réécrire.
- Support :
showOpenFilePicker() et showSaveFilePicker() existent dans Chrome et Edge depuis la version 86 (et dans Opera), pas dans Firefox ni Safari, d’après les tables de compatibilité de MDN (MDN). L’API est toujours marquée comme expérimentale.
- Conditions : elle exige un contexte sécurisé, ce qui inclut les adresses
file: (MDN), et une action de l’utilisateur, comme un clic sur un bouton (MDN).
- Écriture sûre :
createWritable() écrit en général dans un fichier temporaire et ne remplace l’original qu’à la fermeture du flux (MDN). Une interruption en cours d’écriture ne laisse donc pas, en principe, un fichier à moitié écrit.
Le code tient en quelques lignes : const fichier = await window.showSaveFilePicker({ suggestedName: 'suivi.json' });, puis const flux = await fichier.createWritable(); await flux.write(texte); await flux.close();. Testez d’abord 'showSaveFilePicker' in window, et revenez à l’export classique si l’API manque.
Rien, dans ce fonctionnement, n’empêche deux postes d’enregistrer le même fichier : la dernière sauvegarde écrase la précédente. Organisez les fichiers pour éviter les conflits, avec un fichier par personne, par dossier traité ou par période, plutôt qu’un fichier commun.
Sécurité : ce que le dossier partagé protège, et ce qu’il ne protège pas
Une application locale n’a pas de comptes utilisateurs. Sa seule protection, ce sont les droits d’accès du dossier partagé, gérés par votre service informatique. Trois conséquences :
- Tout ce qui est dans le dossier est lisible par ceux qui y ont accès, code compris. Un mot de passe vérifié en JavaScript n’est pas une protection : il suffit d’ouvrir le fichier pour le lire. Aucun secret, clé d’API ou identifiant, ne doit figurer dans le code.
- Pas de données sensibles sans accès restreint : santé, salaires, évaluations. La CNIL recommande de limiter les accès aux seules données dont chaque utilisateur a besoin. Si le dossier est ouvert à toute l’entreprise, ces données n’ont rien à y faire.
- L’intégrité compte autant que la confidentialité : donnez aux utilisateurs un accès en lecture seule au dossier de l’application, et l’écriture au seul responsable de l’outil. Sinon, n’importe qui peut modifier une formule dans
regles.js, sans laisser de trace.
Pour les autres bonnes pratiques, voyez la checklist de sécurité d’une application web.
Sauvegarde et mises à jour : les règles d’exploitation
Un outil sans serveur reste un outil de production. Quelques règles suffisent à l’exploiter sereinement :
- Les fichiers de données sont enregistrés sur un partage couvert par les sauvegardes : vérifiez-le avec le service informatique
- Le code source est conservé dans un dépôt versionné, en dehors du dossier utilisé par l’équipe
- Chaque version porte un numéro, affiché en pied de page et inscrit dans les fichiers exportés
- Une mise à jour se teste dans un dossier de recette avant de remplacer le dossier partagé
- Les calculs sont revérifiés à chaque version avec un jeu de cas de référence, comparé au classeur d’origine
- L’application est testée sur les navigateurs réellement installés dans l’entreprise
Le jeu de cas de référence et la façon de comparer automatiquement les résultats au classeur sont décrits dans le guide recréer des formules Excel en JavaScript.
Quand passer à un intranet avec serveur
L’application locale atteint ses limites quand :
- plusieurs personnes doivent modifier les mêmes données en même temps ;
- il faut des comptes, des rôles et un historique de qui a fait quoi ;
- l’outil doit lire les données d’un autre logiciel, comme un ERP, par une API ;
- il doit être accessible hors du réseau de l’entreprise, ou sur mobile ;
- les données sont sensibles et demandent des droits plus fins que ceux d’un dossier.
Il existe un palier intermédiaire : servir le même dossier par un serveur web interne. L’adresse passe en http://, et les modules JavaScript comme les appels fetch() vers les fichiers voisins fonctionnent sans autre changement. Pour essayer sur un poste, MDN propose par exemple la commande python3 -m http.server (MDN). Mais un serveur de fichiers n’ajoute ni comptes ni base de données. Pour cela, il faut une véritable application web, avec authentification, rôles et données isolées : c’est l’architecture que j’ai mise en place pour Docalio, mon portail client en ligne.
En résumé : léger, fiable, à condition de respecter ses limites
Un intranet local en HTML et JavaScript est la façon la plus légère de donner à une équipe un outil fiable : pas de serveur, pas d’installation, des calculs qui tournent hors ligne. Il demande trois précautions : des scripts classiques et des données de référence en fichiers .js, des fichiers exportés comme copie de référence, et un dossier dont les droits sont pensés. Dès que les données doivent être partagées en temps réel ou protégées par des comptes, passez à un serveur. Pour faire d’un classeur un outil de ce type, voyez la méthode dans transformer un fichier Excel en application web, ou mon accompagnement Excel en application web.