Quand je m'intéresse à l'optimisation des performances web, il y a un point qui revient systématiquement : les images. Elles pèsent souvent lourd et peuvent pénaliser le LCP (Largest Contentful Paint) si elles ne sont pas servies de façon adaptée. Sur Flyweb, j'ai testé plusieurs approches et aujourd'hui je vous partage ma méthode pour automatiser la génération d'images responsives avec Sharp côté serveur et un déploiement sur Vercel. L'idée : servir exactement la bonne image, au bon format, au bon moment — sans perte de qualité perceptible — pour améliorer le LCP.
Pourquoi Sharp + Vercel ?
Sharp est une bibliothèque Node.js très performante pour manipuler les images : redimensionnement, conversion vers WebP/AVIF, compression, optimisation progressive, etc. Vercel permet de déployer des fonctions serverless ou edge qui répondent rapidement et peuvent cache-ner les images en front de CDN. Ensemble, ils forment un duo pratique : Sharp fait le boulot d'optimisation à la volée, Vercel distribue et met en cache efficacement.
Principes que j'applique
- Générer des images à la volée selon des paramètres (largeur, format, qualité).
- Servir WebP/AVIF quand le navigateur les supporte, sinon JPEG/PNG.
- Mettre en place un cache CDN agressif et des en-têtes HTTP adaptés.
- Valider et sécuriser la source des images pour éviter les usages abusifs.
- Ne pas upscaler : éviter la perte de qualité en agrandissant au-delà de l'original.
Schéma de fonctionnement
Voici le flux que j'utilise :
- Le site HTML ou le composant front (Next.js ou site statique) demande une image via une URL dédiée, par ex. /api/image?src=/uploads/photo.jpg&w=800
- La fonction Vercel récupère l'image originale (locale ou stockage cloud), la transforme avec Sharp (resize, convert), choisit le format selon l'en-tête Accept, et renvoie la réponse avec des en-têtes cache optimisés.
- Le CDN de Vercel met en cache la version transformée ; les requêtes suivantes sont servies rapidement depuis le cache.
Paramètres que j'envoie souvent
- src : chemin vers l'image originale (relatif ou URL autorisée)
- w : largeur cible en pixels
- q : qualité (par ex. 75-85 pour JPEG/WebP)
- fm : format souhaité (optionnel — je laisse Sharp décider selon Accept)
Bonnes pratiques pour les tailles (srcset)
Plutôt que de générer manuellement chaque taille, j'automatise la génération d'un srcset en fonction des points d'arrêt. Voici des largeurs que j'utilise fréquemment, adaptées à la majorité des écrans :
| Small | 320 |
| Medium | 640 |
| Large | 1024 |
| XL | 1600 |
| XXL | 2400 |
En pratique, je génère automatiquement les URLs /api/image?src=...&w=320,640,1024... et je fournis le srcset au navigateur. Cela évite de charger une image trop grande et réduit le LCP.
Notes techniques et optimisations Sharp
Quelques options Sharp que j'active systématiquement :
- resize(width) avec { fit: 'cover' } ou 'contain' selon l'usage.
- rotate() pour respecter l'EXIF et éviter les images mal orientées.
- toFormat('webp', { quality }) ou 'avif' si je veux pousser la compresion.
- withMetadata() uniquement si nécessaire (souvent je l'enlève pour gagner du poids).
- jpeg({ quality, progressive: true }) pour des JPEG plus agréables à charger.
J'ajoute parfois un petit sharpen pour compenser la perte de définition lors d'une forte compression : .sharpen() — utilisé avec parcimonie.
Gestion des formats : WebP et AVIF
Je détecte l'en-tête Accept du client et priorise AVIF > WebP > JPEG/PNG. AVIF offre un ratio exceptionnel, mais attention à la compatibilité sur certains navigateurs plus anciens. Le code côté serveur teste Accept et renvoie le format approprié :
- Si Accept inclut "image/avif" → toFormat('avif')
- Sinon si Accept inclut "image/webp" → toFormat('webp')
- Sinon → toFormat('jpeg' ou 'png')
Cache et en-têtes HTTP recommandés
La partie cache est cruciale pour améliorer le LCP. Voici les en-têtes que je place :
- Cache-Control: public, max-age=31536000, immutable — pour les images dont le nom inclut un hash/version.
- Si l'image est générée à la volée et que l'URL n'est pas versionnée, j'utilise stale-while-revalidate pour améliorer disponibilité et rapidité.
- ETag ou Last-Modified pour validation conditionnelle si nécessaire.
Sécurité et validation
Un point que j'ai appris à la dure : ne jamais permettre à l'API d'accepter n'importe quelle URL externe sans contrôle. Cela peut transformer votre endpoint en proxy open. Mes règles :
- Autoriser uniquement des chemins internes ou des domaines externes explicitement whitelists.
- Limiter la taille maximale autorisée pour l'image source.
- Valider les paramètres (width min/max, quality min/max).
Exemples d'usage côté front
Si vous utilisez Next.js, vous pouvez définir un loader pointant vers /api/image et laisser l'Image component construire le srcset. Sinon, pour du HTML classique :
- <img src="/api/image?src=/uploads/pic.jpg&w=1024" srcset="/api/image?src=/uploads/pic.jpg&w=320 320w, /api/image?src=/uploads/pic.jpg&w=640 640w, /api/image?src=/uploads/pic.jpg&w=1024 1024w" sizes="(max-width: 600px) 100vw, 50vw" alt="..."/>
Cela permet au navigateur de choisir la meilleure source selon la densité d'écran et la taille du viewport.
Mesures et validation
Pour vérifier l'impact sur le LCP, j'utilise :
- Google Lighthouse (mode mobile et desktop)
- WebPageTest pour analyser la chaîne de chargement et vérifier si l'image visible est servie rapidement
- Core Web Vitals via Chrome UX Report ou des RUM (Real User Monitoring) si vous avez du trafic
Après avoir mis en place l'API d'images optimized + cache CDN, j'ai vu sur plusieurs projets une baisse significative du temps de chargement du plus grand élément visuel et donc une amélioration du LCP.
Pièges fréquents et comment les éviter
- Ne pas versionner les images statiques : risque de cache obsolète — utilisez un hash dans le nom.
- Générer trop de variantes inutiles : gardez une grille de tailles raisonnable pour éviter explosion du nombre d'objets en cache.
- Oublier la validation des paramètres → risques de DoS via traitements coûteux.
- Utiliser AVIF partout sans fallback → certains navigateurs ne le supportent pas encore complètement.
Si vous voulez, je peux vous fournir un exemple de code pour une API Vercel + Sharp (fonction) adaptée à votre projet Flyweb, avec gestion Accept, en-têtes cache et validation. C'est une mise en place que j'ai automatisée sur plusieurs sites, et qui permet d'améliorer le LCP sans sacrifier la qualité des images.