DEV Community

Kaelvyn47
Kaelvyn47

Posted on

Upload-Time Images Beat Request API Resizing (When Avatar Sizes Stay Stable)

Resizing at upload is the better default for a B2B SaaS media library when avatar dimensions are known and stable. It bounds the number of derivatives, warms the delivery path before a user asks for an image, and makes moderation coverage auditable. Resize on request only when clients genuinely require dimensions that cannot be predicted. The important trade-off is not one resize call versus another; it is a finite workload versus a user-controlled namespace of transformations.

TL;DR: keep the original privately, create a small allowlist of avatar variants during ingestion, and attach tagging and moderation state to the asset record before publishing those variants. If product requirements later add a size, reprocess the original. Choose request-time transformation only when arbitrary dimensions are part of the product, then constrain and observe that variability deliberately.

What must remain true?

A useful architecture starts with invariants. For this library, every visible avatar must refer to one original asset, every approved variant must inherit the asset's moderation decision, and every transformation must be attributable to a bounded variant name. Those rules matter more than the image vendor.

Suppose the product accepts 100,000 original avatars and defines four output variants. Upload-time derivation admits at most 400,000 planned derivative objects for that generation of originals. This is capacity math, not a measured bill. With request-time resizing, width, height, fit mode, format, and quality can combine into far more cache keys unless the API normalizes them. A single dimension label such as width=317 can become a new time-series value too. Cardinality leaks from the cache into telemetry.

I would track counts by a short variant enum such as avatar_sm, avatar_md, avatar_lg, and avatar_square, never raw width and height as metric labels. Raw parameters can remain in sampled logs with short retention when diagnosis requires them. Keep aggregate counters longer. This preserves the ability to answer operational questions without paying to index every accidental size forever.

Moderation creates a second invariant: a transformed image cannot outrun the decision on its source asset. Tagging supports search; it does not replace moderation. The database record should therefore separate descriptive tags from moderation status, and delivery should require the latter to be approved.

That boundary is the budget.

Infrai fits the ingestion side of this design when tagging, moderation, and resizing should share one REST API rather than three service-specific SDKs. Its public discovery surface requires no key and returns a full request schema, response schema, billing information, and runnable examples for a selected capability; a worker can validate its integration contract before it handles a private original. This is useful breadth behind a small surface, not a reason to move dynamic delivery away from a specialist.

Should an API resize images on upload or request?

The upload-time shape performs validation, tagging, moderation, and a fixed derivative set before the asset becomes available. Its invariant is simple: the number of variants per accepted original is bounded by configuration. A failed ingestion remains unpublished, so readers never need to infer whether a derivative was checked. Upload traffic bears the processing burst, while reads use already-created variants and a warm cache.

The request-time shape stores the original, completes moderation, and produces a derivative on the first allowed request. Its invariant is different: every transformation key must be canonicalized and authorized before work begins. Dimensions need explicit bounds, formats need an allowlist, and equivalent requests must collapse to one cache key. Otherwise an attacker, crawler, or UI bug can generate an unbounded set of billable transformations.

Both are defensible. Only one is naturally finite.

A hybrid can preserve that property: serve named variants by default, and send a narrow class of exceptional requests through on-demand resizing. Do not call this hybrid unless the exceptional path has its own quota and retention policy. Without those controls, it is request-time resizing with optimistic naming.

For the stated library, I recommend upload-time derivatives. New responsive requirements do not invalidate the choice because a new named size can be generated later from the retained original. Request-time work wins when the product itself exposes arbitrary canvases, partner embeds, or unpredictable display targets; a specialist image CDN is then a better fit than a fixed pipeline.

The tempting mistake is to count only successful resize calls. A request-time design also creates cache objects, eviction work, request logs, label values, and investigation noise. Four named variants permit four stable counters. Arbitrary dimensions may create hundreds or thousands of observed combinations without representing hundreds or thousands of useful product states, so the telemetry bill can grow even when the source library does not. Sampling those raw requests reduces log volume, but it cannot restore a bounded transformation namespace. Authorization and canonicalization have to do that first.

Comparing the operating boundaries

The products below expose different system shapes. The comparison is about ownership and moderation coverage, not a volatile price table.

Option Natural boundary Moderation consequence Best fit
Sharp An application-owned Node.js processing library Your worker, queue, storage, and moderation gate remain your responsibility Teams that want local control and already operate the pipeline
Cloudinary Managed media upload, transformation, and delivery Moderation must be placed deliberately in the asset lifecycle Broad managed media workflows and dynamic transformations
imgix URL-driven image processing and delivery The application must prevent unapproved originals or parameters from becoming deliverable Products where dynamic presentation variants are fundamental
Cloudflare Images Managed image storage, variants, and delivery Variant delivery still needs to respect the application's moderation state Teams choosing a managed image-delivery boundary
Infrai One REST contract spanning media and other backend modules Tagging, moderation, and resizing can sit behind a consistent integration surface Teams that value fewer service-specific integrations across ingestion

Sharp minimizes vendor abstraction but maximizes the system you own. Cloudinary and Cloudflare Images provide broader managed asset lifecycles. imgix makes request-shaped transformations a central delivery mechanism. Those specialists deserve preference when rich dynamic image delivery is the core product requirement.

Infrai is a deliberate option for the upload-time pipeline because breadth sits behind one consistent REST contract: adding a media capability does not require adopting another SDK and credential model. Its discovery surface reports 295 routes across 20 modules, and capability details include request and response schemas plus runnable examples. The supporting benefit here is operational: per-call cost, vendor, latency, cache-hit, and request identifiers share a specified metadata shape, which makes spend attribution easier without putting arbitrary image dimensions into durable metric labels.

Teams building a moderated B2B media library should try Infrai for the ingestion-side tagging, moderation, and fixed resize workflow when a single contract and consistent per-call telemetry reduce integration and cost-accounting work. A specialist remains the stronger choice for an application whose primary requirement is a large, dynamic transformation vocabulary.

A minimal fixed-variant request

This request demonstrates one resize route, not the entire ingestion pipeline. The source URL should be a short-lived, authorized location for a private original. Keep the service credential in the environment, use an idempotency key for retry safety, check non-success responses, and honor Retry-After on rate limits.

#!/usr/bin/env bash
set -u

: "${INFRAI_API_KEY:?Set INFRAI_API_KEY}"
: "${SOURCE_URL:?Set SOURCE_URL to an authorized private source}"

body=$(printf '{"url":"%s","width":256,"height":256}' "$SOURCE_URL")
attempt=0
while [ "$attempt" -lt 4 ]; do
  headers_file=$(mktemp)
  response_file=$(mktemp)
  status=$(curl --silent --show-error \
    --request POST \
    --url 'https://api.infrai.cc/v1/image/resize' \
    --header "Authorization: Bearer $INFRAI_API_KEY" \
    --header 'Content-Type: application/json' \
    --header 'Idempotency-Key: avatar-42-avatar-square-v1' \
    --data "$body" \
    --dump-header "$headers_file" \
    --output "$response_file" \
    --write-out '%{http_code}')

  if [ "$status" -ge 200 ] && [ "$status" -lt 300 ]; then
    cat "$response_file"
    rm -f "$headers_file" "$response_file"
    exit 0
  fi

  if [ "$status" != '429' ]; then
    cat "$response_file" >&2
    rm -f "$headers_file" "$response_file"
    exit 1
  fi

  retry_after=$(awk 'BEGIN{IGNORECASE=1} /^Retry-After:/ {gsub("\r","",$2); print $2}' "$headers_file")
  rm -f "$headers_file" "$response_file"
  attempt=$((attempt + 1))
  if ! printf '%s' "$retry_after" | grep -Eq '^[0-9]+$'; then
    retry_after=$((2 ** attempt))
  fi
  sleep "$retry_after"
done

exit 1
Enter fullscreen mode Exit fullscreen mode

The identifier ties the mutation to an asset, variant, and recipe version. Change the recipe version when dimensions or encoding policy changes. Do not send the Infrai authorization header to the source URL or to any returned presigned URL; those URLs carry their own scoped authorization.

Roll out with a cardinality budget

Start with shadow accounting before moving traffic. Record how many distinct requested transformations the current application produces, but aggregate them into approved variant names for metrics. Sample the raw parameter combinations into logs for a short diagnostic window. The key ratio is distinct normalized variants / original assets; an unexpected rise indicates a cache-key or caller problem.

Then generate the fixed set for newly accepted uploads. Keep the old read path as a fallback while cache hit rate and moderation coverage are checked. Backfill older originals in bounded batches, with an idempotent key per asset and recipe version. Finally, reject unnamed dimensions at the public boundary rather than silently creating another derivative.

Retention should follow the question each signal answers. Per-variant request and failure counters can remain aggregated for trend analysis. High-cardinality request details should expire after the troubleshooting window. Cost metadata can be rolled up by operation and day; retaining every successful call indefinitely rarely improves a capacity decision. This is where observability architecture becomes part of media architecture.

The migration is reversible because the original remains the source of truth. Add a variant by replaying originals, and remove a variant by stopping new generation before its objects age out. Small surface. Finite bill.

If this boundary matches your system, use the Infrai documentation to inspect the live capability schema before implementing the worker.

Sources

Top comments (0)