Media & CDN

Image delivery needs a CDN, not just a storage bucket

A raw file in a bucket is not a fast image on a page. Draftbase's media management pipeline uploads, processes, and serves images through a CDN with explicit cache headers, so delivery speed isn't left to chance. Storage answers "where is the file." A CDN answers "how fast does it reach the browser," and that second question is the one most media setups leave unanswered.

Ready to simulate an upload

Why media delivery is a performance problem, not just a storage problem

"Media management" often gets scoped as an upload form and a database record. That framing misses the part that actually affects visitors: how the resulting file reaches a browser, and how long it takes to get there.

Storing an image is the easy part. Serving it fast, at the right size, on every request is where most media pipelines fall down. Images account for roughly 60-70% of the average webpage's total weight. (Source) That makes images the single biggest lever for page performance on most sites.

Sites that treat media delivery seriously see it in their Core Web Vitals. The share of mobile pages passing a good Largest Contentful Paint score rose from 44% in 2022 to 62% in 2025, per HTTP Archive's Web Almanac data, reported by Hostinger. (Source) Largest Contentful Paint is usually an image. Fixing how that image gets processed and cached moves the metric directly.

A CMS that only stores the original file pushes this work back onto your app. You build a resize pipeline yourself. You set your own cache headers by hand. You hope nothing regresses when traffic spikes. A CMS that treats media as a delivery problem does that work once, centrally, for every asset, instead of once per project.

The storage layer and the delivery layer are not the same job. Storage just needs to keep the bytes safe. Delivery needs the right format, the right size, and a cache policy that keeps a CDN from re-fetching the same file on every request. Conflating the two is why so many teams end up with slow image-heavy pages despite paying for fast hosting.

This is also why "just use S3" is an incomplete answer. A bucket alone has no concept of image variants, no processing step, and no cache policy attached by default. It will happily serve a 6MB original to a mobile visitor on a slow connection, at the same speed it serves a 6MB original to anyone else, because nothing in that setup distinguishes a media asset from any other blob of bytes.

A CDN in front of that bucket helps with geography, but it can only cache what the origin tells it to cache. Without a Cache-Control header on the response, a CDN has no signal for how long a given image is safe to keep at the edge. The header and the CDN are two halves of the same mechanism — one is useless to a visitor without the other.

How Draftbase handles media management and CDN delivery

Media management in Draftbase is a pipeline, not a single upload endpoint. Four pieces work together: a presigned upload step, an async processing step, an explicit cache policy on the CDN delivery path, and entry-level versioning. Each step exists to remove one specific failure mode teams hit when they build this themselves.

This split mirrors the write/read separation the rest of Draftbase already uses for content: writes go through an authenticated management surface, reads go through a separate delivery path built for speed. Media follows the same shape — the upload and confirm steps are management operations, and the CDN-fronted URL is the delivery side, cached and public.

Presigned upload, confirmed separately

The API issues a presigned upload URL and form fields for the file. The browser posts the file directly to storage from there. A separate confirm call then registers the asset — the file bytes never route through the API server. The asset starts life with a pending status until processing finishes.

Processing on a dedicated Lambda

A separate Lambda function picks up the original once it lands in storage. It resizes and re-encodes the image there, off the request path. An upload never blocks a request/response cycle on the main API while this runs, even if the resize takes several seconds on a large original.

Explicit cache headers on delivery

Delivery responses carry a Cache-Control header with public, max-age, and stale-while-revalidate. The max-age comes from a configurable environment value, not a hardcoded number or a vendor default you can't inspect. Public means any CDN or shared cache in front of the response, not just the requesting browser, is allowed to store it.

Versioned like any other field

Media is referenced from entries as a field type, the same way a text or reference field is. Swapping an image is tracked in that entry's revision history, not invisible to it. There's no separate media-history feature to maintain, because it's the same revision mechanism every field already uses.

Put together, this covers the four places a homegrown media pipeline usually breaks: a blocked API request during upload, an unprocessed original served at full size, a missing cache header that forces re-fetches on every load, and a swapped image with no record of when or why it changed.

None of these four pieces requires configuration before an upload works. The presigned URL flow, the Lambda trigger, and the delivery cache headers are wired up by default for every environment in an org. What's configurable is the cache max-age and the org's upload size limit, so the defaults match a typical site and the knobs are there for the cases that don't.

Media management and CDN delivery approaches compared

The table below lines up three common setups: rolling your own upload path against a plain S3 bucket, a generic headless CMS media field, and Draftbase's pipeline. The differences show up most in processing and caching, the two steps teams skip first under deadline pressure. None of the three approaches is wrong on day one — a plain bucket works fine for a handful of images. The gap widens as asset count and traffic grow, which is exactly when a manual pipeline is hardest to retrofit.

ApproachSelf-hosted uploads (app server / plain S3 bucket)Generic headless CMS media fieldDraftbase media
Upload pathBlocks the app serverPresigned upload, varies by vendorPresigned upload, confirmed separately from the write path
ProcessingCustom pipeline you buildVendor-dependentDedicated Lambda, decoupled from the API
CachingManual header configVendor defaultExplicit Cache-Control + stale-while-revalidate on delivery
VersioningManualVendor-dependentTied to entry revisions like any other field

Upload path and processing get most of the attention because they're visible during development. Caching and versioning are the two rows teams regret skipping months later, once traffic and edit history both start to matter. Both are the kind of thing that works fine in a demo and then quietly costs money or trust at scale — extra origin bandwidth on one side, an unexplained image swap on the other.

Common pitfalls in media delivery

The most common mistake is serving the original upload directly instead of a processed variant. A camera photo or export can run several megabytes. Shipping that straight to a browser wastes bandwidth on every single request. It also slows down the exact metric — Largest Contentful Paint — that a CDN and cache headers are supposed to help. Resizing and compressing once at upload time, then serving the smaller result, fixes this permanently instead of per-request. It's a one-time cost at upload instead of a repeated cost on every page view afterward.

The second mistake is skipping cache headers entirely. Every request then re-fetches from origin, even when the underlying file hasn't changed at all. This adds latency for the visitor and load on the origin for no benefit, since a correctly set stale-while-revalidate window would let a CDN serve a slightly stale copy instantly while it revalidates in the background.

The third mistake is treating media as separate from content versioning. A swapped image silently changes what a "published" entry shows, with no audit trail and no way to answer "what did this look like last week." All three pitfalls trace back to the same root cause: media handled as a one-off file operation instead of as content that's processed, cached, and versioned like everything else in the CMS.

None of these three are exotic failures. They're the default outcome of wiring uploads to a bucket without also deciding what happens after the upload — who resizes the file, who sets the headers, and who notices when the file behind an entry changes. A media management pipeline is what makes those three decisions once, up front, instead of leaving them for whoever hits the bug first.

Try media management on Draftbase

Upload an image, watch it move through processing, and pull it from the CDN with cache headers already set. Media management stays out of your way once it's configured — you upload, the pipeline handles the rest, and the delivery API is ready to check against the reference below.

Frequently asked questions

Do media uploads go through Draftbase's main API?

Only the request and confirmation steps do. The API issues a presigned upload URL, then the browser uploads the file bytes directly to storage. The API then confirms the asset, registering it as pending. The file itself never passes through the API server, so a large upload can never tie up a request thread there. This is the same reason a slow upload on a bad connection doesn't degrade the API for every other user in the org at the same time.

What processes an uploaded image?

A dedicated Lambda function, separate from the main API, resizes and optimizes the image once it lands in storage. This keeps a large upload from tying up an API request/response cycle while processing runs. The Lambda writes the final processed file back to storage and updates the asset's status once it finishes, so the API never has to wait on that work synchronously.

What cache headers does Draftbase set on media delivery?

Delivery responses carry a Cache-Control header with public, a configurable max-age, and a stale-while-revalidate window five times that max-age. The max-age is an environment setting, not a fixed value baked into the app, so it can be tuned per deployment. A public directive lets any CDN or intermediate cache store the response, not just the requesting browser, which is what keeps repeat requests for the same image from hitting origin at all.

Is a media asset versioned like other content?

Yes. Media is referenced from entries as a field type, the same as text or reference fields, so swapping an image is tracked in that entry's revision history. There's no separate media-versioning system to learn — an image field behaves the same as any other field when an entry gets a new revision, which means rollback works the same way too.

What happens if image processing fails?

The asset is marked failed with the error message attached, instead of silently staying in a pending state. You can see the failure and re-upload rather than debugging a missing image later. That status is visible on the asset record itself, so a failed process step surfaces as data you can query, not a gap you only notice when a page renders a broken image.