Créer des URL de recherche et de filtres partageables en JavaScript
Un visiteur passe cinq minutes à trouver les bons produits, sélectionne deux catégories et trie les résultats par prix. Il copie l’URL de la page pour un collègue. Celui-ci l’ouvre et retrouve la liste par défaut, sans les filtres.
L’interface conservait son état uniquement en JavaScript. L’URL n’avait pas changé.
Pour des résultats de recherche, une documentation filtrée, un catalogue de produits ou certains outils de conception, une URL partageable apporte un vrai confort. Elle permet d’enregistrer une vue dans les favoris, de la retrouver après un rechargement ou d’envoyer un lien qui reproduit les sélections.
Le navigateur fournit déjà l’essentiel : URL, URLSearchParams et l’API History. Il reste à choisir ce qui appartient à la barre d’adresse et à gérer les changements sans perdre de paramètres ni divulguer de données sensibles.
Choisir les réglages à placer dans l’URL
Les bons candidats décrivent ce que le visiteur consulte : un terme de recherche, un numéro de page, une catégorie, un ordre de tri ou des étiquettes publiques. Lorsqu’un réglage produit une vue réellement différente, pouvoir l’enregistrer dans les favoris est souvent utile.
Tout n’a pas sa place dans ce lien. Gardez les mots de passe, jetons d’accès, notes privées, contenus non publiés, adresses e-mail et autres données sensibles hors des paramètres de requête. Les URL peuvent apparaître dans l’historique du navigateur, les journaux du serveur, les outils d’analyse, les captures d’écran et les informations de provenance. Une URL n’est pas un espace de stockage privé.
Un catalogue consultable pourrait proposer cette adresse :
https://example.com/resources?q=css&tag=layout&tag=accessibility&sort=newest
Les deux clés tag sont intentionnelles. De nombreuses applications utilisent des paramètres répétés pour représenter plusieurs filtres sélectionnés. L’encodeur, décodeur et éditeur de paramètres URL de Web2Generators permet de les examiner et de les modifier avant d’écrire le code de l’application. Sa présentation avec exemples explique aussi pourquoi un signe plus peut être interprété comme une espace.
Lire les paramètres sans découper les chaînes à la main
Découper une chaîne de requête avec .split("&") et .split("=") est une erreur courante. Cela néglige l’encodage en pourcentage, les clés répétées, les valeurs vides et les caractères Unicode. Utilisez plutôt les API URL du navigateur :
const url = new URL(window.location.href);
const params = url.searchParams;
const query = params.get("q") ?? "";
const tags = params.getAll("tag");
const sort = params.get("sort") ?? "relevance";
get() renvoie la première valeur correspondante, ou null ; getAll() renvoie toutes les valeurs correspondantes dans l’ordre. Elles sont déjà décodées. Si l’URL contient tag=css&tag=html, getAll("tag") renvoie ['css', 'html'].
Le contenu de la barre d’adresse est contrôlé par l’utilisateur. Validez-le même si les paramètres ont été produits par votre application. Vous pouvez, par exemple, limiter les valeurs de tri autorisées ainsi que le nombre et la longueur des filtres :
function readFilters(url = new URL(window.location.href)) {
const params = url.searchParams;
const allowedSorts = new Set(["relevance", "newest", "title"]);
const requestedSort = params.get("sort") ?? "relevance";
return {
q: Array.from((params.get("q") ?? "").trim()).slice(0, 100).join(""),
sort: allowedSorts.has(requestedSort) ? requestedSort : "relevance",
tags: params.getAll("tag")
.filter(tag => /^[a-z0-9-]{1,30}$/.test(tag))
.slice(0, 8)
};
}
La limite de recherche compte les points de code Unicode plutôt que les unités UTF-16 : elle ne coupe donc pas une paire de substituts en deux. Ces règles illustrent des choix de produit, pas des limites universelles. Un moteur de recherche peut accepter d’autres langues et des expressions plus longues, tandis qu’un catalogue peut reconnaître des identifiants d’étiquettes plutôt que des noms. Validez les valeurs selon le véritable schéma de l’application.
Écrire les changements avec URLSearchParams
Pour modifier un filtre, créez un objet URL à partir de la page actuelle, puis modifiez son objet searchParams. Ne concaténez pas directement une valeur brute saisie par l’utilisateur dans l’URL :
function buildFilterUrl({ q, sort, tags }) {
const url = new URL(window.location.href);
url.searchParams.delete("q");
url.searchParams.delete("sort");
url.searchParams.delete("tag");
if (q) url.searchParams.set("q", q);
if (sort !== "relevance") url.searchParams.set("sort", sort);
for (const tag of tags) url.searchParams.append("tag", tag);
return url;
}
append() ajoute une paire même si la clé existe déjà. set() remplace les entrées portant ce nom par une seule valeur. Passez un état validé à cette fonction. Elle supprime ses propres clés avant de les reconstruire : les étiquettes répétées restent intentionnelles et les paramètres sans rapport peuvent être conservés. Leurs valeurs subsistent, mais la sérialisation peut modifier leur écriture encodée.
Attention toutefois à ces autres paramètres. Une URL contenant une signature cryptographique peut devenir invalide si sa chaîne de requête est reconstruite, réordonnée ou réencodée. Traitez généralement les URL signées comme des valeurs opaques, sauf indication contraire de leur protocole de signature.
Le piège du signe plus
Dans les paramètres encodés au format formulaire, un + seul représente une espace. Un signe plus littéral dans une recherche de C++ doit être encodé, généralement en %2B. URLSearchParams s’en charge avec set("q", "C++"). N’encodez pas préalablement cette valeur avec encodeURIComponent() : les signes pourcentage risqueraient d’être encodés une seconde fois.
Essayez des valeurs représentatives dans l’encodeur et décodeur URL de Web2Generators : espaces, +, esperluettes, caractères accentués, emoji et clés répétées. L’outil facilite l’inspection visuelle ; il ne remplace pas les API URL dans une application.
Choisir entre replaceState et pushState
L’API History modifie l’adresse affichée sans recharger immédiatement la page :
const nextUrl = buildFilterUrl({
q: "css grids",
sort: "newest",
tags: ["layout", "responsive"]
});
history.replaceState(history.state, "", nextUrl);
Choisissez replaceState() lorsqu’un petit ajustement doit actualiser l’entrée courante de l’historique, par exemple pendant la saisie d’une recherche. Choisissez pushState() lorsqu’une navigation explicite mérite une entrée accessible avec le bouton Précédent, par exemple après un clic sur “Appliquer les filtres” ou le passage à une autre page de résultats.
Aucune de ces méthodes ne récupère automatiquement les données, ne redessine les résultats ni ne déclenche popstate. Votre code doit afficher le nouvel état et gérer le retour à une ancienne entrée. Les deux méthodes exigent une URL de même origine. Si un routeur de framework gère la navigation, utilisez son API plutôt que de remplacer son état d’historique.
L’exemple autonome suivant utilise les fonctions readFilters() et buildFilterUrl() définies plus haut. Chargez le script après le formulaire, ou utilisez un script de type module :
<form id="filter-form">
<label for="search">Search resources</label>
<input id="search" name="q" type="search">
<button type="submit">Apply search</button>
</form>
const form = document.querySelector("#filter-form");
const search = document.querySelector("#search");
function restoreFromUrl() {
const filters = readFilters();
search.value = filters.q;
// Render results using filters.q, filters.sort and filters.tags here.
}
form.addEventListener("submit", (event) => {
event.preventDefault();
const candidate = new URL(location.href);
candidate.searchParams.set("q", search.value);
const nextUrl = buildFilterUrl(readFilters(candidate));
if (nextUrl.href !== location.href) {
history.pushState(null, "", nextUrl);
}
restoreFromUrl();
});
window.addEventListener("popstate", restoreFromUrl);
restoreFromUrl();
Cet exemple montre la synchronisation avec l’URL, pas un moteur de recherche complet. Dans une application réelle, restaurez tous les filtres et appelez la même fonction d’affichage ou de chargement des données après la soumission et après popstate.
Garder un historique utilisable
Si chaque frappe appelle pushState, un utilisateur ayant saisi un mot de douze lettres risque de devoir appuyer douze fois sur Précédent. Pour une modification en direct, replaceState() évite cette accumulation ; pour une soumission explicite, l’exemple ci-dessus n’ajoute une entrée que si l’URL change. Remplacer l’entrée pendant la saisie efface aussi la vue précédente : ajouter ensuite la même URL à l’historique ne la fera pas réapparaître. Choisissez les étapes à conserver avant de combiner ces approches. Regroupez les mises à jour de l’URL et les requêtes après une courte pause de saisie, et ignorez les réponses devenues obsolètes pour que les résultats correspondent à l’URL courante.
Décidez aussi ce qui se passe lorsque tous les filtres sont supprimés. Retirer les clés inutiles donne généralement une URL plus claire que sort=relevance&q=&page=1. Le lien devient également plus facile à lire et à partager.
Tester l’URL comme point d’entrée indépendant
Ne testez pas uniquement en naviguant dans une application déjà chargée. Copiez l’URL finale dans un nouvel onglet ou une fenêtre privée. Retrouve-t-on la bonne vue ? Fonctionne-t-elle sans connexion à un compte ? Un filtre obsolète produit-il un message utile ?
Vérifiez Précédent, Suivant, le rechargement, l’ouverture directe, l’affichage mobile et la combinaison avec d’autres paramètres. Le serveur doit fournir l’application au chemin partagé ; pushState() ne crée pas de route côté serveur. Le guide d’inspection des requêtes réseau vous aidera à vérifier que les filtres ne demandent que les données prévues. S’ils appellent une API, le générateur de commandes cURL permet de reproduire les requêtes GET pour les déboguer.
Dernier point de sécurité : un paramètre d’URL n’autorise jamais l’accès à une ressource. Le serveur doit toujours contrôler les autorisations d’accès aux données privées. Affichez les termes de recherche comme du texte, jamais comme du HTML non vérifié. Pour distinguer l’affichage sûr du texte et le rendu de contenu enrichi, consultez Échappement ou assainissement HTML : quand utiliser chaque méthode.
D’autres usages pour les liens partageables
Les filtres ne sont qu’un exemple. Un outil de conception peut encoder une palette, une documentation peut pointer vers une langue ou une version, et une liste de produits peut conserver son tri. Notre présentation de Color Studio montre un outil qui propose des liens de palettes partageables. Le générateur d’avatar illustre aussi l’intérêt de retrouver plus tard un résultat choisi.
Ces outils placent leurs codes dans le fragment après #, plutôt que dans les paramètres de requête. Le fragment n’est pas envoyé dans la requête HTTP, mais les scripts de la page peuvent le lire et les liens copiés le révèlent. N’y placez pas de secrets.
L’essentiel est de rendre l’état décrit par l’URL prévisible, limité et adapté au partage. Lorsqu’un lien représente fidèlement la vue courante, les favoris et la navigation du navigateur deviennent utiles à votre interface.
Pour aller plus loin
- MDN : URLSearchParams - présentation en français ; la version anglaise détaille davantage l’encodage et les signes plus.
- MDN : API History
- MDN : replaceState() (en anglais)
- MDN : pushState() (en anglais)
Avant de publier votre prochaine interface de filtrage, essayez un lien réel dans l’encodeur, décodeur et éditeur de paramètres URL de Web2Generators pour repérer les erreurs d’encodage et de paramètres répétés.