Remplacez les numéros de port par des URL nommées en développement
Remplacez les ports localhost par des URL nommées avec .localhost, un reverse proxy ou portless pour éviter conflits, cookies partagés et mauvais onglets.
Un nom de domaine localhost est un nom d’hôte lisible par l’humain, tel que app.localhost, qui se résout vers 127.0.0.1, permettant à chaque service local de conserver une adresse stable au lieu d’un numéro de port fluctuant.
Vous avez probablement déjà vécu ce moment où trois serveurs de développement tournent, vous revenez sur l’onglet localhost:3000 pour vérifier un correctif, et l’application qui s’affiche est celle du projet d’hier. Remplacer localhost:3000 par app.localhost résout d’un coup toute une série de désagréments quotidiens (conflits de ports, URL qui dérivent, fuites de cookies et le problème du « mauvais onglet ») parce que chaque application obtient son propre nom d’hôte et, avec lui, sa propre portée de navigateur isolée. Cet article présente trois façons d’y parvenir : le TLD .localhost intégré, un reverse proxy artisanal, et portless, un proxy local dédié signé Vercel Labs.
Points clés à retenir
- Le TLD
.localhostest réservé à un usage en boucle locale par la RFC 6761 ; ainsi, tout nom placé sous ce TLD se résout vers127.0.0.1dans Chrome, Firefox et Edge sans entrée dans le fichier hosts. Safari, qui délègue au résolveur système, peut encore en nécessiter une. - Comme les navigateurs délimitent la portée des cookies par hôte en ignorant le port,
app.localhostetapi.localhostrestent séparés, tandis quelocalhost:3000etlocalhost:3001partagent le même stock de cookies. - Le TLD
.localhostseul ne supprime pas le port : votre application écoute toujours sur l’un d’eux, il vous faut donc un reverse proxy pour associer le nom d’hôte à ce port. - portless (Vercel Labs, encore en pré-1.0) attribue à chaque application un port éphémère dans la plage 4000–4999 via la variable d’environnement
PORTet y route une URL stablename.localhost, avec HTTPS et HTTP/2 activés par défaut. - Une URL nommée stable consignée dans un fichier agents permet aux outils de codage assistés par IA d’atteindre le bon service au lieu de deviner entre le port 3001 et le 8080.
Pourquoi les URL nommées surpassent-elles les numéros de port ?
Le développement local basé sur les ports se casse de manière prévisible dès que vous exécutez plus d’un service. Démarrez une deuxième application sur un port occupé et Node lève une erreur EADDRINUSE. Les frameworks qui incrémentent automatiquement évitent le plantage mais introduisent une dérive : votre blog est sur localhost:3001 aujourd’hui et sur localhost:3002 demain, si bien que les favoris pourrissent et que l’historique du navigateur pour localhost:3000 devient un empilement inexploitable de projets sans rapport. Arrêtez un serveur, démarrez-en un autre sur le port libéré, et un onglet resté ouvert sert discrètement l’autre projet : c’est le problème du « mauvais onglet ».
L’échec le plus insidieux, c’est la fuite d’état. Les navigateurs délimitent la portée des cookies par hôte en ignorant le port : localhost:3000 et localhost:3001 écrivent donc dans le même stock de cookies. L’état de session d’une application se propage dans une autre. Les sous-domaines nommés corrigent cela au niveau de l’origine : app.localhost et api.localhost sont des noms d’hôte distincts, ils séparent donc proprement les cookies, et comme la politique de même origine s’appuie sur le schéma, l’hôte et le port, ils séparent aussi localStorage et sessionStorage. Les recommandations de Microsoft sur ce TLD vont dans le même sens : donner à chaque application locale son propre nom permet de cloisonner les ressources liées au nom, comme les cookies, et le nom affiché dans la barre d’adresse vous indique d’un coup d’œil quelle application vous êtes en train de consulter.
Qu’est-ce que le TLD .localhost ?
Discover how at OpenReplay.com.
Le mécanisme d’URL nommée le plus simple est livré avec votre navigateur. La RFC 6761 réserve le TLD .localhost, ainsi que tous les noms qui en dépendent, à l’adresse de boucle locale ; c’est la raison pour laquelle app.localhost répond sur 127.0.0.1 sans aucune configuration. Chrome, Firefox et Edge gèrent cette résolution en interne, en associant tout nom *.localhost à 127.0.0.1 ou ::1 ; un tel nom agit donc comme un alias de ce qui est déjà servi sur localhost. C’est Safari qu’il faut surveiller : il transmet le nom au résolveur DNS du système, et toutes les configurations de résolveur ne répondent pas pour les sous-domaines .localhost ; vous aurez peut-être besoin d’une entrée /etc/hosts dans ce cas.
Il y a toutefois un bémol : le TLD seul ne supprime pas le port. Votre application écoute toujours sur :3000, et app.localhost sans port aboutit simplement sur app.localhost:80, où rien n’écoute. Pour réellement se débarrasser du numéro, il vous faut un reverse proxy sur le port 80 ou 443 qui lit l’en-tête Host et transmet la requête au véritable port de l’application.
Solution artisanale : fichier hosts et reverse proxy
Vous pouvez assembler des URL nommées à partir d’éléments que vous connaissez déjà. Ajoutez un nom d’hôte dans /etc/hosts (ou appuyez-vous sur la résolution automatique de .localhost), puis exécutez un reverse proxy qui associe le nom au port de votre serveur de développement. Une configuration Caddy est à peu près aussi concise que possible :
app.localhost {
reverse_proxy localhost:3000
}
api.localhost {
reverse_proxy localhost:8080
}
Caddy provisionne automatiquement les certificats TLS locaux ; nginx et Traefik font le même travail au prix d’une configuration plus lourde. Pour des domaines locaux avec joker, dnsmasq peut résoudre tout un espace *.test vers 127.0.0.1, ce qui vous évite les entrées hosts nom par nom. Et chaque serveur de développement doit toujours avoir son hôte et son port fixés (Vite via server.host et server.port, webpack via devServer) pour que le proxy dispose d’une cible stable.
Le compromis, c’est la maintenance. Vous entretenez la configuration du proxy, la confiance accordée aux certificats, les entrées hosts et l’attribution des ports par projet, et vous maintenez ces quatre éléments synchronisés à la main au fil des services qui apparaissent et disparaissent. Pour une ou deux applications pérennes, c’est acceptable. À l’échelle d’un monorepo, cela devient une corvée à part entière.
portless : des URL nommées qui fonctionnent, tout simplement
portless est un proxy local qui automatise toute la chaîne. Vous préfixez votre commande de développement : next dev devient portless run next dev, ou bien vous lancez simplement portless et le laissez déduire le nom de l’application depuis package.json, la racine git ou le répertoire. Le proxy démarre automatiquement, attribue un port libre dans la plage 4000–4999, l’injecte via la variable d’environnement PORT et route https://name.localhost vers celui-ci. Les frameworks qui ignorent PORT, comme Vite, Astro, Angular et Expo, reçoivent l’option --port appropriée, plus une option --host correspondante lorsque c’est nécessaire.
Dans les versions 0.15.x, portless active HTTPS avec HTTP/2 par défaut sur le port 443, en générant une autorité de certification locale et en lui accordant la confiance lors de la première exécution. Il s’élève automatiquement en privilèges avec sudo sur macOS et Linux, car se lier au port 443 nécessite les droits root, et portless trust réintègre l’AC si vous avez ignoré l’invite. Les articles antérieurs présentant une option --https à activer explicitement et un port :1355 par défaut décrivent une version obsolète. HTTP/2 est utile en local pour une raison précise : un navigateur ne maintient que six connexions HTTP/1.1 ouvertes vers un même hôte, si bien qu’un serveur de développement livrant des centaines de fichiers séparés et non bundlés finit par les mettre en file d’attente, alors qu’une seule connexion HTTP/2 les transporte tous en parallèle. portless requiert Node.js 24 ou une version ultérieure.
Quelques fonctionnalités font toute la différence sur les configurations plus importantes. Des sous-domaines comme api.myapp.localhost permettent d’organiser les microservices ; un unique fichier portless.json à la racine d’un monorepo détecte automatiquement les packages du workspace. Pour un service à port fixe que vous ne pouvez pas modifier, tel qu’un conteneur Docker, portless alias <name> <port> lui associe une URL nommée, et PORTLESS=0 contourne totalement le proxy pour la CI ou un test rapide. Si vous souhaitez un TLD personnalisé, portless recommande .test, également réservé par la RFC 6761, et en déconseille deux autres : .local entre en conflit avec mDNS et Bonjour, tandis que .dev appartient à Google, qui le force à passer en HTTPS via HSTS.
Pourquoi des URL locales stables comptent pour les agents de codage IA
Les agents de codage IA échouent sur le même point que les humains avec les ports, mais silencieusement : ils codent en dur un numéro aperçu plus tôt dans le contexte, ou se trompent en devinant. Un agent qui lit une adresse fixe https://api.myapp.localhost dans un fichier AGENTS.md cible toujours le bon service, au lieu d’alterner entre 3001 et 8080 d’une session à l’autre et de vous interrompre pour demander. Il s’agit d’une évolution générale de l’outillage de développement : les points de terminaison stables constituent une infrastructure pour l’automatisation. portless fournit des fichiers de compétences (skill files), et les versions 0.15.x ajoutent des pages de documentation en Markdown ainsi qu’un index llms.txt, afin que ses URL soient découvrables par les agents dès l’installation.
Choisir une approche
TLD .localhost seul | TLD + reverse proxy | portless | |
|---|---|---|---|
| Supprime le port ? | Non | Oui | Oui |
| Outils supplémentaires | Aucun | Caddy/nginx/Traefik | Une installation globale |
| HTTPS | Manuel | Fourni par le proxy | Activé par défaut |
| Détection auto en monorepo | Non | Non | Oui |
| Compatible avec les agents | Partiellement | Partiellement | Oui (skill files, llms.txt) |
| Friction de mise en place | Minimale | Moyenne (synchronisation manuelle) | Faible |
La décision en une ligne : optez pour le TLD intégré associé à un reverse proxy si vous ne voulez aucun nouvel outil et que la maintenance de la configuration ne vous rebute pas ; optez pour portless si vous voulez que les URL nommées fonctionnent sans effort sur de nombreux services, dans un monorepo ou avec des agents IA.
Des URL locales nommées, stables et lisibles par l’humain sont strictement supérieures aux numéros de port, et vous pouvez les adopter en quelques minutes : ajoutez dès aujourd’hui un Caddyfile de deux lignes, ou préfixez un script de développement avec portless et ne pensez plus jamais à EADDRINUSE.
FAQ
Dois-je ajouter les sous-domaines .localhost à mon fichier /etc/hosts ?
Non, pas dans Chrome, Firefox ni Edge. Ces trois navigateurs résolvent eux-mêmes tout nom placé sous le TLD .localhost vers 127.0.0.1, car la RFC 6761 réserve ce TLD à un usage en boucle locale ; app.localhost et api.localhost fonctionnent donc sans aucune configuration. Safari fait exception, car il confie la résolution au résolveur DNS du système, et toutes les configurations de résolveur ne répondent pas pour les sous-domaines .localhost. Ajoutez-y une entrée /etc/hosts si un nom ne se charge pas.
L'utilisation d'un domaine .localhost supprime-t-elle le numéro de port de mon serveur de développement ?
Non. Le TLD .localhost ne fait que résoudre le nom d'hôte vers 127.0.0.1 ; votre application écoute toujours sur son port d'origine, si bien que app.localhost sans port aboutit sur app.localhost:80, où rien ne tourne. Pour réellement se débarrasser du numéro, il vous faut un reverse proxy sur le port 80 ou 443 qui lit l'en-tête Host et transmet la requête au véritable port de l'application. C'est précisément ce qu'automatisent des outils comme Caddy ou portless.
Pourquoi les cookies fuient-ils entre localhost:3000 et localhost:3001, mais pas entre app.localhost et api.localhost ?
Les navigateurs délimitent la portée des cookies par hôte en ignorant le port : localhost:3000 et localhost:3001 partagent donc le même hôte, localhost, et par conséquent le même stock de cookies. Les sous-domaines nommés ont des hôtes distincts, si bien que app.localhost et api.localhost conservent des cookies séparés. Comme la politique de même origine s'appuie sur le schéma, l'hôte et le port, des noms d'hôte distincts séparent aussi proprement localStorage et sessionStorage, ce que ne font pas les origines basées sur les ports.
Quelle version de Node.js portless exige-t-il, et fonctionne-t-il sans sudo ?
portless requiert Node.js 24 ou une version ultérieure. Sur macOS et Linux, il s'élève automatiquement en privilèges avec sudo lors de la première exécution, car se lier au port 443 pour HTTPS nécessite les droits root. HTTPS fonctionne avec HTTP/2 dès l'installation, et portless crée une autorité de certification locale et lui accorde la confiance lors de sa première exécution ; utilisez portless trust pour ajouter l'AC ultérieurement si vous avez ignoré l'invite initiale.