Zero-Overhead File Uploads: S3 Presigned URLs & Client-Side Compression
System ArchitectureAWS S3Node.jsPerformanceServer Optimization

Zero-Overhead File Uploads: S3 Presigned URLs & Client-Side Compression

SU
Sahil Umraniya
July 26, 2026
6 min read
Article

Zero-Overhead File Uploads: S3 Presigned URLs & Client-Side Compression

When building web applications that support user uploads (such as high-res images, PDFs, or media attachments), traditional file upload pipelines route the raw binary payload directly through the Node.js / Express application server.

While straightforward, this pattern introduces severe performance bottlenecks:

  • Bandwidth Double-Dipping: Files travel twice across the wire (Client -> App Server -> Cloud Storage).
  • RAM Bloat: Multi-megabyte file buffers saturate server memory, triggering Node.js event loop blocks and process crashes under load.
  • CPU Spikes: Server-side image resizing and format conversion exhaust CPU cores needed for API business logic.

Here is how to solve this using AWS S3 Presigned URLs and Client-Side Compression.


1. Direct-to-S3 Architecture Overview

Instead of acting as a proxy for file data, the API server acts solely as a Security Gatekeeper:

  1. Client Request: Frontend requests a temporary upload authorization key from the backend API, sending only filename and MIME type metadata.
  2. Presigned URL Token: Backend uses AWS SDK to generate a cryptographically signed 30-second expiration URL.
  3. Direct Upload: Client streams compressed image payload directly to AWS S3 endpoints via
    HTTP PUT
    .
  4. Instant URL Resolution: Client receives the clean public CDN URL ready for immediate UI rendering or database persistence.

2. Server-Side Presigned Token Engine

By generating single-use tokens with strict expiration timeouts, your server never touches raw file bytes:

typescriptExample
import { NextResponse } from 'next/server'; import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { v4 as uuidv4 } from 'uuid'; const s3Client = new S3Client({ region: process.env.AWS_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, }, forcePathStyle: true, requestChecksumCalculation: "WHEN_REQUIRED", responseChecksumValidation: "WHEN_REQUIRED", }); export async function POST(request: Request) { const { filename, filetype } = await request.json(); const key = \`uploads/\${uuidv4()}-\${filename.replace(/\\s+/g, '-')}\`; const command = new PutObjectCommand({ Bucket: process.env.AWS_BUCKET_NAME!, Key: key, }); // 30-second TTL limits token replay risk const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 30 }); const fileUrl = \`https://s3.\${process.env.AWS_REGION}.amazonaws.com/\${process.env.AWS_BUCKET_NAME}/\${key}\`; return NextResponse.json({ signedUrl, fileUrl }); }

3. Client-Side Image Compression & Bandwidth Optimization

To save over 80% of network transfer volume before the file even leaves the browser, execute client-side canvas compression:

typescriptExample
// Compress original file to lightweight WebP/AVIF format before S3 PUT async function compressImage(file: File): Promise<Blob> { return new Promise((resolve) => { const img = new Image(); img.src = URL.createObjectURL(file); img.onload = () => { const canvas = document.createElement('canvas'); const maxDim = 1600; let { width, height } = img; if (width > maxDim || height > maxDim) { if (width > height) { height = Math.round((height * maxDim) / width); width = maxDim; } else { width = Math.round((width * maxDim) / height); height = maxDim; } } canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); ctx?.drawImage(img, 0, 0, width, height); canvas.toBlob((blob) => resolve(blob!), 'image/webp', 0.82); }; }); }

4. Performance & Server Cost Savings

MetricTraditional Node ProxyPresigned S3 + Client CompressionImprovement
Server RAM Usage50 MB - 300 MB / upload~ 2 KB (JSON Metadata)99.9% Reduction
App Network Bandwidth10 MB per 10 MB file0 MB transferred via API100% Offload
Average Upload Speed~ 4.2 Seconds~ 0.8 Seconds81% Faster
Server Crash RiskHigh under concurrent spikesZero impact on main event loop100% Resilient

By decoupling file ingress from application servers, you safeguard Node.js execution threads while providing lightning-fast media performance.

Did you enjoy this article?

Check out more insights on AI, Agents, and Engineering on the main blog.

Sahil Umraniya

Start Your Project Today

Looking for a reliable Full Stack Engineer for your next project or team? I'm available for both freelance work and full-time opportunities. Let's create something extraordinary together.

Based in Ahmedabad, Gujarat

Start a Conversation

Tell me about your project. Average response time: under 4 hours.