Decorative background gradient
Back to Blog
Next.js S3 UploadDigitalOcean Spaces NodejsPresigned URLs Upload

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.

Next.js S3 Upload to DigitalOcean Spaces with Presigned URLs

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

bash
ts

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.

ts

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

tsx
tsx

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.

ts

If you're already using next/image, add the CDN hostname to remotePatterns so Next.js can optimize these images too:

ts

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:

ts

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.

Frequently Asked Questions

Do I need a different SDK for DigitalOcean Spaces than for AWS S3?

No. DigitalOcean Spaces implements the S3 API, so the same @aws-sdk/client-s3 package works — you just point the endpoint config at your Spaces region (e.g. https://nyc3.digitaloceanspaces.com) instead of an AWS endpoint.

Why use a presigned URL instead of uploading through my API route?

Routing file bytes through your Next.js server doubles the bandwidth cost and ties up server resources for the duration of the upload. A presigned URL lets the browser upload directly to object storage — your server only ever handles a small signing request, not the file itself.

How long should a presigned upload URL be valid for?

Keep it short — a few minutes is usually enough for a real upload to start and finish. Anyone with the URL can upload to that specific key until it expires, so a long expiry unnecessarily widens the window for misuse if the URL leaks.

How do I validate file type and size before generating a presigned URL?

Check the requested content type and declared size in the Server Action or Route Handler before calling PutObjectCommand, and reject anything outside your allowed types/limits there. You can also enforce a max size via a Content-Length range condition when generating the presigned POST policy instead of a simple PUT URL.

Working on something similar? Take a look at my services and case studies, or book a free call to talk about your idea.

Related Articles

Caching Strategies and Cache Invalidation — The Complete Guide
CachingSystem DesignPerformance

Caching Strategies and Cache Invalidation — The Complete Guide

A practical guide to caching strategies (cache-aside, write-through, write-behind) and the cache invalidation techniques that keep them from serving stale data.

September 8, 2026Read more →
Database Indexing and Read Replicas — A Practical Guide
DatabasePostgreSQLSystem Design

Database Indexing and Read Replicas — A Practical Guide

How to choose the right database indexes, avoid the ones that quietly hurt write performance, and scale reads with replicas without introducing replication lag bugs.

September 8, 2026Read more →
Load Balancing and Stateless Service Design — A Practical Guide
System DesignLoad BalancingScalability

Load Balancing and Stateless Service Design — A Practical Guide

How load balancers distribute traffic, why stateless services are what actually makes horizontal scaling work, and how to fix the sticky-session traps that quietly reintroduce state.

September 8, 2026Read more →
Session Stores and Database Connection Pooling Explained
BackendRedisDatabase

Session Stores and Database Connection Pooling Explained

Why in-memory sessions break horizontally scaled apps, how to move session state to Redis correctly, and how connection pooling keeps your database from falling over under concurrent load.

September 8, 2026Read more →
Vertical vs Horizontal Scaling: How to Choose and Implement Each
System DesignScalabilityArchitecture

Vertical vs Horizontal Scaling: How to Choose and Implement Each

A practical comparison of vertical and horizontal scaling — what each actually fixes, where each breaks down, and the architecture changes horizontal scaling requires that most guides skip.

September 8, 2026Read more →
AI Agent Guardrails and Safety — Preventing Prompt Injection and Runaway Actions
Ai Agent SafetyPrompt Injection DefenseAgent Guardrails

AI Agent Guardrails and Safety — Preventing Prompt Injection and Runaway Actions

Build practical guardrails for AI agents — prompt injection defenses, destructive-action confirmation, iteration caps, and permission scoping.

September 7, 2026Read more →

Trending Topics