12 mai 2026 · Cloud · NestJS · Architecture
Cloudflare R2 : du stockage objet sans frais de sortie pour vos applications
Pourquoi R2 plutôt que S3 pour stocker les fichiers d'une application : architecture, URLs présignées avec NestJS et pièges à éviter.
Quand une application doit stocker des fichiers utilisateurs (documents, images, PDF), le réflexe est souvent Amazon S3. Mais pour un projet dont le budget se compte en euros par mois plutôt qu'en milliers, la facture S3 cache un piège : les frais de sortie (egress). Chaque téléchargement de fichier par vos utilisateurs vous coûte de la bande passante facturée. C'est précisément ce que Cloudflare R2 élimine.
Ce qu'est R2, en une phrase
R2 est un stockage objet compatible avec l'API S3 (mêmes SDK, mêmes concepts de buckets et de clés), avec une différence économique majeure : zéro frais de sortie. Vous payez le stockage (environ 0,015 $/Go/mois) et les opérations, pas les téléchargements.
Pourquoi c'est décisif pour une app à documents
Prenons une application de dossiers médicaux où les utilisateurs consultent régulièrement leurs documents. Le ratio lecture/écriture est très déséquilibré : un document uploadé une fois sera téléchargé des dizaines de fois. Sur S3, ce trafic de lecture est le poste de coût principal et, surtout, il est imprévisible : il croît avec l'usage, pas avec le volume stocké. Sur R2, le coût est fonction du seul volume : prévisible, budgétable, sans mauvaise surprise en cas de succès.
L'intégration côté NestJS
R2 étant compatible S3, le SDK AWS officiel fonctionne tel quel, seul l'endpoint change :
import { S3Client } from '@aws-sdk/client-s3';
const r2 = new S3Client({
region: 'auto',
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});
Le pattern que je recommande pour les fichiers privés : les URLs présignées. Le backend ne sert jamais les octets lui-même : il génère une URL temporaire (quelques minutes de validité) que le client utilise pour uploader ou télécharger directement auprès de R2 :
import { GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
async getDownloadUrl(key: string): Promise<string> {
const command = new GetObjectCommand({ Bucket: 'documents', Key: key });
return getSignedUrl(this.r2, command, { expiresIn: 300 });
}
Bénéfices : votre serveur applicatif (un petit VPS, par exemple) n'est jamais le goulot d'étranglement des transferts de fichiers, et le contrôle d'accès reste entièrement chez vous : c'est votre API qui décide qui obtient une URL, et pour combien de temps.
Les pièges à connaître
- La compatibilité S3 n'est pas totale. L'essentiel y est (put, get, multipart, presign), mais certaines fonctionnalités avancées de S3 manquent. Vérifiez la liste officielle si vous migrez un code existant.
- Nommez vos clés avec discipline dès le premier jour
(
users/{id}/documents/{uuid}.pdf) : le stockage objet n'a pas de dossiers, et une convention de nommage propre est votre seul outil d'organisation et de purge. - Ne mettez jamais de données sensibles dans les noms de clés : ils apparaissent dans les URLs et les logs.
La leçon
Pour une application qui sert beaucoup de fichiers avec un budget contraint, le critère n'est pas « quel est le stockage le moins cher au Go » mais « quel est le coût total quand mes utilisateurs utilisent vraiment l'application ». R2 rend ce coût prévisible, et pour un produit qui doit vivre des années, la prévisibilité est une fonctionnalité.