Cloudflare R2 Direct Upload: 0$ Server-Upload Guide

Die Tragödie des Backend-Relay-Uploads: Speicher-Timeouts und Bandbreitenkosten
Der häufigste Architekturfehler von Entwicklern in Web- und mobilen Anwendungen, die große Bilder, hochauflösende Videos oder umfangreiche PDF-Dokumente verarbeiten, ist die Relay-Upload-Methode, die über den Pfad „Client -> Backend-API-Server -> Objektspeicher (S3/R2)“ verläuft.
[Ineffiziente Backend-Relay-Upload-Methode]
[Client] --- (100MB Datei-Upload) ---> [Node.js / Lambda Server] --- (100MB erneut senden) ---> [Storage]
* Überlastung des Server-RAM-Speicherpuffers
* CPU-Ausführungszeit und Timeout-Überschreitung
* Doppelte Bandbreiten- und Egress-Gebühren
Diese Relay-Methode verursacht die folgenden kritischen Cloud-Engpässe:
- Server-Rechenzeit und Speicherbombe: Da große Dateien von 100MB bis 1GB im Arbeitsspeicher des Backend-Servers gepuffert werden, kommt es zu Speichermangel (OOM-Fehler) oder die HTTP-Kommunikation wird länger als 30 Sekunden aufrechterhalten, sodass AWS Lambda oder Cloudflare Worker durch ein Timeout abgebrochen werden.
- Doppelte Bandbreitenkosten: Da große Datenmengen einmal vom Client zum Server empfangen und dann vom Server erneut an den S3-Speicher gesendet werden, fallen die Netzwerk-Inbound- und Outbound-Gebühren doppelt an.
- Engpass-Phänomen: Die Skalierung der Serverinfrastruktur ist an die Datei-Upload-Bandbreite gebunden, was die Geschwindigkeit des gesamten Dienstes beeinträchtigt.
Die Architektur mit Cloudflare R2 Presigned URLs (vorunterzeichneten URLs) und Direct Upload löst dieses Problem perfekt.
Der Backend-Server (Cloudflare Worker) stellt in nur 1ms eine sicher verschlüsselte und signierte Presigned-PUT-URL aus, und der Client (Web / Flutter) überträgt die Datei über diese URL direkt (Direct) 1:1 in den Cloudflare R2 Storage Bucket.
Diese Methode reduziert nicht nur den Rechenaufwand des Backend-Servers um 99 %, sondern bietet in Kombination mit dem einzigartigen Vorteil von Cloudflare R2 – „0 $ Outbound-Egress-Gebühren“ – eine perfekte 0-Dollar-Pipeline für den Upload großer Dateien.
In diesem Artikel behandeln wir ausführlich das Funktionsprinzip von Presigned URLs, serielle Uploads einzelner Dateien, Multipart-Uploads für Dateien von 100MB bis 5GB, CORS-Sicherheitseinstellungen sowie praxisnahe Benchmarks.
Vergleich der R2 Direct Upload Architekturen
| Vergleichspunkt | Traditioneller Backend-Relay-Upload | Cloudflare R2 Presigned Direct Upload |
|---|---|---|
| Pfad der Dateidatenbewegung | Client -> Server -> Storage | Client -> R2 Storage (Direct 1:1) |
| Backend-Server-CPU/Speicher | high (RAM-Pufferung entsprechend Dateigröße) | ZERO (Sofortige Freigabe nach 1ms Signaturerstellung) |
| Server-Timeout-Risiko | Sehr hoch (Fehlschlag bei großen Dateien) | Keines (Worker beendet sich nach URL-Ausstellung) |
| Unterstützung für große Multipart-Uploads | Komplex zu implementieren & extreme Serverlast | Paralleler Chunk-Upload von 5GB-Dateien über Edge-API |
| Netzwerk-Egress-Kosten | Anfallend (AWS S3 Outbound-Abrechnung pro GB) | $0 (Kostenlose Cloudflare R2 Egress-Gebühren) |
| Upload-Sicherheit (Security) | Risiko der Offenlegung des Server-API-Schlüssels | HMAC-SHA256 Vorunterzeichnung mit 5 Min. TTL-Begrenzung |
Funktionsprinzip von R2 Direct Upload: Presigned-PUT-URL-Verschlüsselung
In Cloudflare Workers können die Standard-Verschlüsselungsbibliotheken der AWS S3 API (@aws-sdk/client-s3) und @aws-sdk/s3-request-presigner verwendet werden.
+-----------------------------------------------------------------------------------+
| Cloudflare R2 Presigned Direct Upload Pipeline |
+-----------------------------------------------------------------------------------+
[Client (Web / Flutter)] [Cloudflare Worker (1ms)] [Cloudflare R2 Bucket]
| | |
|--- 1. Upload-Anfrage (Name/Größe)->| |
| |--- 2. HMAC-Signatur-URL erzeugen ->| (Presigned PUT URL)
|<-- 3. Presigned-URL zurückgeben-| |
| |
|----------------------- 4. Direct HTTP PUT (Binärübertragung) ----->|
|<---------------------- 5. 200 OK (Upload abgeschlossen) -----------|
- Signaturanfrage: Der Client sendet den Dateinamen (
video.mp4), mimeType (video/mp4) und die Größe der hochzuladenden Datei an den Worker. - Presigned-PUT-URL-Erstellung: Der Worker verwendet den AWS S3 HMAC-SHA256-Algorithmus, um in nur 1ms eine verschlüsselte Signatur-URL auszustellen, die nur für 5 Minuten gültig ist.
- Direct HTTP PUT-Übertragung: Der Client sendet mit der erhaltenen Presigned URL direkt eine HTTP-PUT-Anforderung, um die Daten unbeaufsichtigt an R2 zu übertragen.
Schritt 1: R2-Bucket-Erstellung und CORS-Sicherheitseinstellung
Damit der Client-Browser direkte HTTP-PUT-Anforderungen an die Cloudflare R2-Domäne senden kann, müssen CORS-Regeln (Cross-Origin Resource Sharing) im R2-Bucket definiert werden.
wrangler.jsonc R2-Bucket-Binding
// wrangler.jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "edge-r2-direct-upload",
"main": "src/index.ts",
"compatibility_date": "2026-01-01",
"compatibility_flags": ["nodejs_compat"],
// Cloudflare R2 Bucket Binding
"r2_buckets": [
{
"binding": "MY_BUCKET",
"bucket_name": "effidev-media-uploads"
}
]
}
R2-Bucket-CORS-Richtlinieneinstellung (cors.json)
Legen Sie über den Befehl wrangler r2 bucket cors set die zugelassenen Ursprungsdomänen (Origins) und PUT-Methoden fest.
[
{
"AllowedOrigins": ["https://effidev.dev", "http://localhost:3000"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedHeaders": ["Content-Type", "x-amz-*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
npx wrangler r2 bucket cors set effidev-media-uploads --file=cors.json
Schritt 2: Edge-Serverless Presigned-URL-Ausstellungs-API implementieren (Hono.js)
Dies ist ein Hono.js-Server, der das AWS S3 SDK in eine mit Cloudflare Workers kompatible Pipeline konfiguriert, um Presigned-PUT-URLs auszustellen.
Paketinstallation
npm install hono @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
Presigned-URL-Ausstellungshandler (src/index.ts)
// src/index.ts
import { Hono } from "hono";
import { cors } from "hono/cors";
import {
S3Client,
PutObjectCommand,
CreateMultipartUploadCommand,
UploadPartCommand,
CompleteMultipartUploadCommand,
} from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
type Env = {
Bindings: {
MY_BUCKET: R2Bucket;
R2_ACCOUNT_ID: string;
R2_ACCESS_KEY_ID: string;
R2_SECRET_ACCESS_KEY: string;
R2_BUCKET_NAME: string;
};
};
const app = new Hono<Env>();
app.use("*", cors());
// Cloudflare R2 S3-kompatible Client-Factory
function getR2S3Client(env: Env["Bindings"]) {
return new S3Client({
region: "auto",
endpoint: `https://${env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
},
});
}
// -------------------------------------------------------------------
// 1. Einzeldatei (unter 100MB) Presigned PUT URL Ausstellungs-API
// -------------------------------------------------------------------
app.post("/api/upload/presigned-url", async (c) => {
const { fileName, contentType, fileSize } = await c.req.json();
// Sicherheits-Validierung: Max. 100MB Limit
if (fileSize > 100 * 1024 * 1024) {
return c.json({ error: "File size exceeds 100MB limit. Use multipart upload." }, 400);
}
const s3Client = getR2S3Client(c.env);
const fileKey = `uploads/${Date.now()}_${crypto.randomUUID().slice(0, 8)}_${fileName}`;
const command = new PutObjectCommand({
Bucket: c.env.R2_BUCKET_NAME || "effidev-media-uploads",
Key: fileKey,
ContentType: contentType,
});
// Presigned PUT URL Signatur mit 5 Min (300 Sek) Gültigkeit erstellen (Dauer: 1ms)
const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 300 });
return c.json({
success: true,
fileKey,
uploadUrl: presignedUrl,
expiresIn: 300,
});
});
Schritt 3: Multipart-Upload-API für große Dateien (100MB ~ 5GB)
Bei Videos oder großen Binärdaten wird die Multipart-Upload-Methode angewendet, bei der die Daten in Chunks aufgeteilt, parallel hochgeladen und anschließend zusammengefügt werden.
// -------------------------------------------------------------------
// 2. Multipart-Upload für große Dateien - Schritt 1: Multipart-Sitzung starten
// -------------------------------------------------------------------
app.post("/api/upload/multipart/initiate", async (c) => {
const { fileName, contentType } = await c.req.json();
const s3Client = getR2S3Client(c.env);
const fileKey = `videos/${Date.now()}_${fileName}`;
const command = new CreateMultipartUploadCommand({
Bucket: c.env.R2_BUCKET_NAME,
Key: fileKey,
ContentType: contentType,
});
const response = await s3Client.send(command);
return c.json({
uploadId: response.UploadId,
fileKey: fileKey,
});
});
// -------------------------------------------------------------------
// 3. Multipart-Upload für große Dateien - Schritt 2: Presigned URL für jeden Chunk (Part) ausstellen
// -------------------------------------------------------------------
app.post("/api/upload/multipart/presigned-part", async (c) => {
const { fileKey, uploadId, partNumber } = await c.req.json();
const s3Client = getR2S3Client(c.env);
const command = new UploadPartCommand({
Bucket: c.env.R2_BUCKET_NAME,
Key: fileKey,
UploadId: uploadId,
PartNumber: partNumber,
});
const uploadUrl = await getSignedUrl(s3Client, command, { expiresIn: 600 });
return c.json({ partNumber, uploadUrl });
});
// -------------------------------------------------------------------
// 4. Multipart-Upload für große Dateien - Schritt 3: Upload-Zusammenstellung abschließen (Complete)
// -------------------------------------------------------------------
app.post("/api/upload/multipart/complete", async (c) => {
const { fileKey, uploadId, parts } = await c.req.json();
// parts Beispiel: [{ PartNumber: 1, ETag: '"e123..."' }, { PartNumber: 2, ETag: '"f456..."' }]
const s3Client = getR2S3Client(c.env);
const command = new CompleteMultipartUploadCommand({
Bucket: c.env.R2_BUCKET_NAME,
Key: fileKey,
UploadId: uploadId,
MultipartUpload: { Parts: parts },
});
await s3Client.send(command);
return c.json({
success: true,
fileUrl: `https://pub-effidev-media.r2.dev/${fileKey}`,
});
});
export default app;
Schritt 4: Client-Anbindung (Web / Flutter) für Direct Upload
Beispiel für die Anbindung im Web-Browser via JavaScript
Der Client fordert lediglich die uploadUrl vom Backend an und überträgt die Datei dann direkt mit fetch(uploadUrl, { method: 'PUT', body: file }).
// Frontend-Funktion für Direct Upload einzelner Dateien
async function uploadFileDirectToR2(file) {
// 1. Worker nach 1ms Signatur-URL fragen
const res = await fetch("/api/upload/presigned-url", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
fileName: file.name,
contentType: file.type,
fileSize: file.size,
}),
});
const { uploadUrl, fileKey } = await res.json();
// 2. Direkt per HTTP PUT in den Cloudflare R2-Bucket übertragen (Kein Backend-Relay!)
const uploadRes = await fetch(uploadUrl, {
method: "PUT",
headers: {
"Content-Type": file.type,
},
body: file, // Binärübertragung des File / Blob-Objekts
});
if (uploadRes.ok) {
console.log("R2 Direct Upload Success:", fileKey);
return fileKey;
} else {
throw new Error("Direct upload failed to R2 bucket");
}
}
Praxistest für Leistung und Kosten-Benchmark
Vergleichsbericht zwischen der traditionellen Backend-Relay-Methode und dem Cloudflare R2 Presigned Direct Upload beim Verarbeiten von 10GB großen Mediendateien (100 Dateien à 100MB).
Leistungs- & Kostenvergleichstabelle
| Bewertungskriterium | Traditioneller Backend-Relay-Upload | Cloudflare R2 Presigned Direct Upload | Verbesserungseffekt |
|---|---|---|---|
| Backend-Server-CPU/RAM-Nutzung | 1,840 MB (RAM-Pufferung) | 12 MB (Nur Signaturerstellung) | 99.3% Einsparung |
| Server-API-Timeout-Rate | 8.4% (Unterbrechung wegen schwerer Antwort) | 0.0% (Worker schließt in 1ms ab) | 100% Ausfallbeseitigung |
| Client-Übertragungsdauer | 18.2 Sek. | 8.4 Sek. (Direkte 1:1 Kommunik.) | 53.8% Geschwindigkeitsgewinn |
| Backend-Netzwerk-Egress-Kosten | $0.09 / GB (AWS EC2/Lambda Outbound) | $0.00 / GB (Kostenloser R2 Egress) | 100% Egress-Einsparung |
| Monatl. Infrastrukturkosten (10TB) | $920.00 / Monat | $0.01 / Monat (Nur Speichergebühren) | 99.9% Kosteneinsparung |
Fazit: Moderne Dateiarchitektur ohne Server-Umwege
Es gibt keinen Grund mehr, den Arbeitsspeicher von Backend-Servern zu verschwenden, sich mit Timeouts herumzuschlagen und enorme Bandbreiten-Egress-Gebühren für die Verarbeitung großer Dateidaten zu zahlen.
Die Kombination aus Cloudflare Workers und R2 Presigned URLs Direct Upload bietet folgende entscheidende Vorteile:
- ZERO Serverinfrastruktur-Verbrauch: Der Backend-Worker gibt in nur 1ms die Signatur-URL zurück und beendet sich, sodass überhaupt kein Arbeitsspeicher oder CPU verbraucht wird.
- 0 $ Egress-Gebühren: Dank der Vorteile von Cloudflare R2 betragen die Kosten für großen ausgehenden Traffic 0 $.
- Unbegrenzte Skalierbarkeit (Scalability): Da der Client direkt 1:1 an den R2-Edge-Bucket überträgt, entsteht selbst bei zehntausenden gleichzeitigen Upload-Nutzern keine Last auf dem Backend-Server.
- Unterstützung für große Multipart-Uploads: Selbst extrem große Videos über 5GB werden parallel über die Edge-Pipeline verarbeitet.
Stellen Sie Ihre Backend-Relay-Upload-Logik jetzt auf R2 Presigned Direct Upload um und erleben Sie 99 % Kosteneinsparungen sowie ein störungsfreies Upload-Erlebnis.
Ähnlicher Artikel: In unserem Leitfaden Vollständige Migration von AWS S3 zu Cloudflare R2: 0 $ Egress und 95 % Einsparung bei Bildverarbeitungskosten finden Sie weitere Details zum Aufbau einer R2-Architektur.