Next.js S3 Upload to DigitalOcean Spaces with Presigned URLs
Build a secure Next.js file upload pipeline to DigitalOcean Spaces using the AWS SDK S3 client, presigned URLs, and CDN delivery — no server relay.

Uploading files from a Next.js app to object storage sounds simple until you consider bandwidth cost, server load, and security. Relaying every upload through your API routes means your server pays for both the inbound and outbound bandwidth of every file, twice. Presigned URLs solve this by letting the browser upload directly to storage while your server only issues a short-lived, scoped permission slip.
This guide builds that pipeline against DigitalOcean Spaces, which is S3-compatible.
Step 1: Configure the S3 Client for DigitalOcean Spaces
The only difference from configuring AWS S3 directly is the endpoint — Spaces implements the same S3 API, so the rest of the SDK behaves identically.
Step 2: Generate a Presigned Upload URL on the Server
Validate the request before signing anything — this is your only checkpoint before the file leaves the client.
Two things matter here: the URL expires in 120 seconds (short enough to limit misuse if it leaks, long enough for a real upload to complete), and validation happens before signing — never after.
Step 3: Upload Directly from the Browser
The file bytes go straight from the browser to Spaces via the PUT request — your Next.js server never sees the file body, only the small JSON signing request.
Step 4: Serve Files Through the CDN Endpoint
DigitalOcean Spaces has two endpoints for the same bucket: the raw origin (nyc3.digitaloceanspaces.com) and a CDN-fronted one (nyc3.cdn.digitaloceanspaces.com). Always serve public files through the CDN endpoint — it caches content at edge locations globally, which is meaningfully faster than hitting the origin region directly on every request.
If you're already using next/image, add the CDN hostname to remotePatterns so Next.js can optimize these images too:
Step 5: Handle Private Files with Presigned Download URLs
For files that shouldn't be publicly readable, skip ACL: "public-read" on upload and generate a presigned GET URL on demand instead:
This keeps the object private in storage while still letting an authorized user access it for a limited window, without your server proxying the file bytes.
Key Takeaways
DigitalOcean Spaces' S3 compatibility means the standard AWS SDK works unmodified — only the endpoint config changes. Route uploads through short-lived presigned URLs so files go directly from browser to storage instead of through your server, validate type and size before signing (not after), and always serve public assets through the CDN endpoint for fast global delivery.






