Étiquette : frontend

  • Comment optimiser les images pour le web

    Comment optimiser les images pour le web

    Si une page semble lente, les images sont la première chose que je vérifie, et c’est presque toujours le coupable. Le texte et le code sont minuscules. Une seule photo de bannière non compressée peut peser plus que tout votre bundle JavaScript. La bonne nouvelle, c’est que l’optimisation d’images est surtout mécanique, et vous pouvez tout automatiser.

    Choisissez le bon format

    Le choix du format est la décision la plus rentable que vous prenez. Le JPEG convient aux photographies mais il est vieux et peu efficace. Le PNG est pour les images qui ont besoin de transparence ou de bords nets, comme les logos et les captures d’écran, et il est catastrophique pour les photos. La réponse moderne pour presque tout est le WebP, qui donne des photos de qualité JPEG à une fraction de la taille, et l’AVIF quand vous voulez pousser la compression encore plus loin. Je sers de l’AVIF avec un repli WebP et un repli JPEG en dessous.

    • Photographies : AVIF ou WebP, jamais du JPEG brut si vous pouvez l’éviter.
    • Logos et icônes : SVG si possible, il s’agrandit à l’infini et ne pèse rien.
    • Captures avec du texte : PNG ou WebP sans perte pour que le texte reste net.

    Redimensionnez avant de compresser

    C’est l’erreur que je vois sans cesse. Les gens prennent une photo d’appareil de 4000 pixels de large, la compressent, et l’affichent dans une colonne de 600 pixels. Le navigateur télécharge tous ces pixels gaspillés et les jette. Redimensionnez d’abord l’image à la plus grande taille à laquelle elle sera réellement affichée, puis compressez. Le seul redimensionnement réduit souvent la taille du fichier de 80 pour cent avant même de toucher aux réglages de qualité.

    Automatisez avec sharp

    Je n’édite pas les images à la main dans une application. Je les passe par un script utilisant la bibliothèque sharp, qui est rapide et produit un excellent résultat. Un court pipeline redimensionne et convertit tout un dossier :

    const sharp = require('sharp');
    
    sharp('input.jpg')
      .resize({ width: 1200, withoutEnlargement: true })
      .webp({ quality: 75 })
      .toFile('output.webp')
      .then(() => console.log('done'));

    La qualité 75 en WebP est mon réglage par défaut. La différence entre 75 et 90 est invisible à l’écran mais double la taille du fichier. Glissez-le dans votre build et chaque image reçoit le même traitement automatiquement, ce qui s’intègre bien à un pipeline lancé via le CI/CD avec GitHub Actions.

    Servez des tailles adaptatives

    Un téléphone et un ordinateur de bureau ne devraient pas télécharger la même image. Générez quelques largeurs et laissez le navigateur choisir avec srcset. Le balisage indique au navigateur quelles tailles existent et la largeur d’affichage de l’image, et il prend la plus petite qui reste nette :

    <img
      src="photo-800.webp"
      srcset="photo-400.webp 400w, photo-800.webp 800w, photo-1200.webp 1200w"
      sizes="(max-width: 600px) 100vw, 600px"
      alt="Une légende descriptive"
    >

    Chargez en paresseux tout ce qui est sous la ligne de flottaison

    Ajoutez loading="lazy" aux images qui ne sont pas visibles au premier rendu de la page. Le navigateur diffère alors leur chargement jusqu’à ce que l’utilisateur s’en approche en faisant défiler. C’est un changement d’un seul attribut au gros retour, car il empêche les images hors écran de se disputer la bande passante avec le contenu que les gens voient vraiment. Laissez-le toutefois sur votre image de bannière, car vous voulez qu’elle se charge immédiatement.

    Définissez toujours largeur et hauteur

    Définissez des attributs de largeur et de hauteur explicites, ou un ratio d’aspect en CSS, sur chaque image. Sans eux, le navigateur ne sait pas combien d’espace réserver, donc la page sautille pendant le chargement des images. Ce saut est le décalage de mise en page, et c’est l’une des métriques sur lesquelles Google vous note. Cela donne aussi une impression de site cassé aux utilisateurs. Réserver l’espace ne coûte rien et corrige le problème complètement.

    Le gain

    Mettez tout cela ensemble et une page typique chargée d’images passe de plusieurs mégaoctets à quelques centaines de kilooctets sans perte de qualité visible. Cela se voit directement dans vos temps de chargement et vos Core Web Vitals. Des images plus légères donnent un site plus vif partout, y compris au moment où vous le publiez via Cloudflare Pages et que vos visiteurs le chargent depuis la périphérie. Optimisez une fois dans le build, et vous n’avez plus jamais à y penser image par image.

  • Comment ajouter une recherche plein texte à un site statique

    Comment ajouter une recherche plein texte à un site statique

    La première objection que j’entends, c’est qu’un site statique ne peut pas avoir de recherche puisqu’il n’y a pas de serveur à interroger. C’est faux. Vous pouvez construire un index de recherche au moment de la génération du site et le publier comme un simple fichier, puis le parcourir entièrement dans le navigateur. Pour quelques milliers de documents au plus, c’est assez rapide pour que les utilisateurs croient à un backend.

    Pourquoi la recherche côté client fonctionne

    L’astuce est de déplacer le travail au moment du build. Pendant que mon générateur parcourt déjà chaque page pour produire le HTML, il est trivial d’y collecter aussi le titre, l’URL et le texte de chacune dans une liste. Cette liste devient un fichier JSON. Le navigateur le télécharge une fois, construit un index en mémoire, et chaque frappe ensuite est instantanée car rien ne quitte l’appareil. Pas de base de données, pas d’API, aucun coût par requête.

    Construisez l’index au moment de la génération

    Pendant le build, je retire les balises HTML de chaque page et j’ajoute un petit enregistrement à un tableau. Gardez le texte du corps réduit ; vous n’avez pas besoin de chaque mot, et un index plus léger se télécharge plus vite. Voici l’essentiel :

    const index = pages.map(page => ({
      title: page.title,
      url: page.url,
      excerpt: page.excerpt,
      body: page.text.slice(0, 2000)
    }));
    
    fs.writeFileSync('dist/search-index.json', JSON.stringify(index));

    Écrire ce fichier n’est qu’une étape de plus dans le même pipeline qui produit vos pages, donc cela s’insère naturellement dans un build que vous lancez peut-être déjà via le CI/CD avec GitHub Actions. L’index est publié à côté de votre HTML sur le même réseau de périphérie.

    Choisissez une bibliothèque, ou écrivez la version bête

    Pour les petits sites, vous pouvez vraiment écrire votre propre comparateur en une douzaine de lignes. Mettez tout en minuscules, découpez la requête en mots, et notez les documents selon le nombre de mots qu’ils contiennent. Ça marche. Mais dès que vous voulez la tolérance aux fautes, la correspondance par préfixe ou un classement par pertinence, prenez une bibliothèque. J’aime Fuse.js pour la correspondance approximative et MiniSearch quand je veux un vrai score plein texte. Les deux sont minuscules et tournent dans le navigateur sans étape de build.

    import MiniSearch from 'minisearch';
    
    const res = await fetch('/search-index.json');
    const docs = await res.json();
    
    const mini = new MiniSearch({
      fields: ['title', 'body'],
      storeFields: ['title', 'url', 'excerpt']
    });
    mini.addAll(docs);
    
    const results = mini.search('cloudflare deploy', { fuzzy: 0.2 });

    Reliez-le au champ de saisie

    Branchez un écouteur sur votre champ de recherche, mais ne lancez pas une recherche à chaque frappe. Temporisez de 150 millisecondes environ, sinon une personne qui tape vite déclenche une douzaine de recherches pour un seul mot. À chaque événement temporisé, lancez la requête, prenez les meilleurs résultats, et affichez-les comme une liste de liens. Montrez le titre et l’extrait pour que les gens sachent quel résultat ils veulent avant de cliquer.

    • Temporisez la saisie pour chercher sur une pause, pas à chaque lettre.
    • Limitez aux huit ou dix premiers résultats ; personne ne fait défiler un menu de recherche.
    • Surlignez le terme correspondant dans le résultat pour que la pertinence saute aux yeux.
    • Gérez explicitement l’état vide et l’état sans résultat.

    Chargez l’index paresseusement

    Ne téléchargez pas l’index de recherche au chargement de la page. La plupart des visiteurs ne cherchent jamais, donc payer ce coût d’avance est du gaspillage. Je récupère le JSON la première fois que quelqu’un place le curseur dans le champ de recherche, je le mets en cache dans une variable, et je le réutilise. La première recherche a un minuscule délai pendant l’arrivée du fichier, toutes les suivantes sont instantanées, et ceux qui ne cherchent jamais ne paient pas un octet. Cela garde votre chargement initial léger, ce qui compte pour les mêmes raisons qui m’obsèdent dans l’optimisation des images pour le web.

    Quand s’arrêter et passer à un service

    La recherche côté client a un plafond. Au-delà d’environ dix mille documents, l’index devient assez gros pour que le télécharger et l’analyser fasse mal, surtout sur téléphone. À cette échelle, je passe à un service de recherche hébergé qui expose une API. Mais honnêtement, la plupart des blogs et des sites de doc n’approchent jamais cette limite. Construisez l’index, publiez le JSON, cherchez dans le navigateur, et vous obtenez une fonctionnalité qui semble coûteuse pour presque rien et zéro serveur à maintenir.