Aller au contenu
← Tous les articles

16 août 2026
1 min de lecture

  • Next.js
  • Cloudinary
  • Serverless
  • Security
  • Frontend

Les fonctions serverless limitent la taille des requêtes à quelques mégaoctets, et les photos de téléphone sont plus lourdes. Les uploads directs signés règlent ça sans exposer votre secret.

Sur mon propre portfolio, l'admin me permettait d'ajouter plusieurs images à un projet. Ça marchait en local — et échouait en production dès que je choisissais de vraies captures. La raison est assez courante pour mériter un article.

Le problème : la route API devient un goulot d'étranglement

La première version faisait la chose évidente :

  1. le navigateur envoie le fichier à /api/upload ;
  2. la route le transmet à Cloudinary avec la clé secrète ;
  3. elle renvoie l'URL.

Sur les plateformes serverless, la taille du corps d'une requête vers une fonction est limitée (sur Vercel, environ 4,5 Mo). Une photo de téléphone récente ou une capture retina dépasse facilement cette limite. Et envoyer dix images une par une à travers la fonction est lent.

La solution : upload direct depuis le navigateur, avec une signature

Le secret doit rester sur le serveur — mais le fichier n'a pas besoin d'y passer. Le serveur se contente de signer les paramètres de l'upload :

// app/api/admin/uploads/sign/route.ts — protégée par l'auth admin
export async function POST() {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const folder = 'portfolio/projects';
  const signature = createHash('sha1')
    .update(`folder=${folder}&timestamp=${timestamp}${process.env.CLOUDINARY_API_SECRET}`)
    .digest('hex');
  return Response.json({ timestamp, folder, signature, apiKey: process.env.CLOUDINARY_API_KEY });
}

Le navigateur envoie ensuite le fichier directement à Cloudinary avec ces champs. La signature n'autorise que ce dossier, pour une durée limitée : elle ne peut pas être réutilisée pour uploader ailleurs.

Des barres de progression gratuites

fetch ne sait pas encore suivre la progression d'un envoi, mais XMLHttpRequest oui :

xhr.upload.onprogress = (e) => e.lengthComputable && onProgress(e.loaded / e.total);

Avec un petit pool de trois envois en parallèle, dix images partent vite, chacune avec sa propre barre de progression.

Les garde-fous restent nécessaires

Passer en direct ne supprime pas la validation, elle se déplace :

  • Dans le navigateur : types acceptés, taille maximale, nombre maximum de fichiers — pour un retour immédiat.
  • Sur la route de signature : seuls les admins authentifiés obtiennent une signature, et seulement pour des dossiers connus.
  • À l'enregistrement : l'API qui sauvegarde le projet valide les URL et limite la taille de la galerie, parce qu'on ne peut pas faire confiance au navigateur.

Résultat

Les uploads ne dépendent plus de la limite des fonctions, ils partent en parallèle, et le secret ne quitte jamais le serveur. Le même principe fonctionne avec les URL présignées S3 ou tout stockage qui accepte des uploads signés.