A practical look at how presigned URLs simplify architecture, reduce server load, and fit naturally into Progressive Web Apps.
Adding image uploads to a Progressive Web App seems simple at first.
The traditional approach is familiar. A browser uploads a file to the application server, the server stores it in S3, and every future request for that image passes through the backend for permission checks.
It works.
Until traffic starts growing.
One developer recently shared how an image upload feature eventually led to a complete redesign of the application's storage architecture.
The original design looked perfectly reasonable
The application supported several kinds of images.
Some were uploaded for AI processing.
Others became preview images that could be public or private.
Some files existed only temporarily and could be removed after processing.
The first implementation followed the classic pattern.
The client uploaded every file to the backend.
The backend stored it in an S3 compatible object store.
When users later requested an image, the backend verified permissions, downloaded the object from storage, and streamed it back to the browser.
Nothing about this architecture seemed unusual.
The problems appeared only after looking at the data flow.
Proxying every image becomes expensive
Every uploaded file passed through the backend twice.
Once during upload.
Once again during download.
That meant application servers spent a significant amount of time moving files instead of processing business logic.
Bandwidth usage increased.
Long lived connections accumulated.
Scaling the application meant adding servers simply to proxy images between browsers and object storage.
Caching also became more complicated.
Because every request passed through the application layer, integrating a CDN or taking advantage of browser caching required additional work.
For a Progressive Web App, the architecture felt even less natural.
PWAs already rely on Service Workers to cache assets locally and provide offline access. Routing every image through the backend reduced many of those advantages.
Presigned URLs changed everything
Instead of transferring files through the backend, S3 compatible storage can generate presigned URLs.
A presigned URL is a temporary signed link that grants permission to upload or download a specific object for a limited period of time.
The backend still performs authentication and authorization.
Instead of receiving the file itself, it generates a temporary upload URL.
The browser then uploads the image directly to object storage.
The same idea applies to downloads.
Rather than proxying the file, the backend returns a temporary download URL that gives the client secure access without exposing the storage bucket publicly.
As a result, the application server is responsible only for permissions, quotas, and metadata.
Large binary files never pass through it.
One additional requirement
Direct browser uploads require CORS to be configured on the storage bucket.
A minimal configuration typically allows the application's origin while permitting the HTTP methods needed for uploads and downloads.
{
"CORSRules": [
{
"AllowedOrigins": [
"https://example.com"
],
"AllowedMethods": [
"GET",
"PUT",
"POST"
],
"AllowedHeaders": [
"*"
]
}
]
}
Once this policy is in place, browsers can communicate with S3 or MinIO directly while remaining within the browser's security model.
The final workflow became much simpler
The browser first requests permission to upload a file.
The backend validates quotas, user permissions, and upload limits before generating a short lived presigned URL.
The browser uploads the image directly to object storage.
After the upload completes, the application stores only the file metadata and associates it with the appropriate record.
When the image is requested later, the backend generates a temporary download URL instead of streaming the file itself.
This design dramatically reduces backend traffic while preserving complete control over who can access each object.
Why this architecture works well for PWAs
Progressive Web Apps benefit naturally from direct object storage access.
Images downloaded through temporary URLs can be cached by the Service Worker and stored for offline use.
Future requests can often be served directly from the local cache without contacting either the backend or object storage.
The backend becomes smaller, simpler, and easier to scale.
Object storage performs the job it was designed for, while the application focuses on authentication, authorization, and business logic.
Moderation remained independent
Image moderation also fit naturally into the new design.
Once an upload was registered, the file entered a moderation workflow before becoming visible to other users.
Because uploads no longer passed through the backend itself, moderation operated independently from the transfer process.
This separation made it easier to replace manual review with automated image classification later without changing the storage architecture.
Key takeaways
Moving to presigned URLs eliminated unnecessary network traffic and significantly reduced backend load.
The architecture became easier to scale because application servers no longer acted as file proxies.
It also aligned much better with how Progressive Web Apps handle offline caching through Service Workers.
The approach does introduce a few responsibilities.
Presigned URLs should have short expiration times.
Unused uploads should be cleaned up periodically.
Permission checks should always happen before generating a signed URL.
Beyond that, the solution is surprisingly straightforward.
Sometimes the best optimization isn't making the backend faster.
It's allowing the backend to step out of the way entirely.

Top comments (0)