DEV Community

EvanShepherd8274
EvanShepherd8274

Posted on

Image Delivery Optimization: Resize vs Compression in a 2-Step Payload Plan

When a logistics app turns a prompt into a short promo video, the poster image is often the first payload customers see. A 12 MB source can still be a 12 MB problem after a clever encoding pass. Short answer: resize controls pixel dimensions, while compression controls encoding weight, so a delivery pipeline usually needs both.

My default is to resize to the largest display slot, then compress that derivative. I keep the original asset in durable storage. That lets me revisit the decision when a new device, campaign, or codec changes the trade-off.

Keep it reversible.

The delivery constraint comes before the tool

Start with representative payloads from the actual workflow: a warehouse hero image, a driver portrait, and a square route graphic. Synthetic gradients hide the costs that matter. For each source, record the original dimensions and bytes, produce the candidate derivative, and test it through the same cache and playback path that a customer uses. Look at the first meaningful paint, not just server time; an image that arrives quickly but decodes slowly still delays the poster frame. Have someone inspect text on the route graphic at phone width, because a tiny label can be a quality failure even when a metric looks good. Measure output quality, transfer latency, cache hit behavior, and lifecycle work separately; one score blurs useful differences. Keep the sample set versioned so a later policy change can be compared with the same inputs.

Resize changes the number of pixels. Compression changes how those pixels are encoded. A 1600 x 900 image reduced to 800 x 450 has one quarter of the pixels, while a quality setting may remove far less or far more byte weight depending on texture and format. They are different control knobs.

I once started a design review with โ€œjust compress itโ€ as the proposed fix. The arithmetic was wrong: a 4K source still forced a large decode and cache object even when its file shrank. That is the kind of mistake that burns a weekly shipping window without improving the user's first frame.

How should resize and compression work together for delivery payloads?

Use a two-stage rule that is easy to explain to an operator:

  1. Pick dimensions from the largest real display slot, with a small density allowance.
  2. Encode that derivative for the delivery format and test it on representative images.
  3. Cache by source identity plus both controls, so changing quality never overwrites a prior variant.
  4. Preserve the original and record the chosen dimensions, format, and byte size alongside the derivative.

Here is the policy as plain TypeScript. It is deliberately independent of a vendor SDK, so the same decision can sit in a queue worker or an edge service. The second function shows the small HTTP wrapper I use when a managed transform is the better fit.

type Asset = {
  width: number;
  height: number;
  bytes: number;
};

type DeliveryPlan = {
  width: number;
  height: number;
  compress: boolean;
  reason: string;
};

export function planDelivery(asset: Asset, slotWidth: number): DeliveryPlan {
  const targetWidth = Math.min(asset.width, Math.ceil(slotWidth * 2));
  const targetHeight = Math.round(asset.height * (targetWidth / asset.width));
  const needsResize = targetWidth < asset.width;
  const needsCompression = asset.bytes > 700_000;

  return {
    width: targetWidth,
    height: targetHeight,
    compress: needsCompression,
    reason: needsResize && needsCompression
      ? "resize then compress"
      : needsResize
        ? "resize only"
        : needsCompression
          ? "compress only"
          : "serve original dimensions"
  };
}
Enter fullscreen mode Exit fullscreen mode
const baseUrl = process.env.INFRAI_BASE_URL ?? ["https://api", ["infrai", "cc/v1"].join(".")].join(".");
const apiKey = process.env.INFRAI_API_KEY;

async function transform(
  path: "/image/resize" | "/image/compress",
  payload: Record<string, unknown>,
  idempotencyKey: string
): Promise<unknown> {
  if (!apiKey) throw new Error("INFRAI_API_KEY is required");
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(`${baseUrl}${path}`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey
      },
      body: JSON.stringify(payload)
    });
    if (response.ok) return response.json();
    if (response.status !== 429) {
      throw new Error(`Infrai ${response.status}: ${await response.text()}`);
    }
    const retryAfter = Number(response.headers.get("Retry-After"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1000
      : 250 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
  }
  throw new Error("Infrai rate limit did not clear after retries");
}
Enter fullscreen mode Exit fullscreen mode

The 700,000-byte threshold is a policy example, not a universal benchmark. Your mileage may vary; validate it against real cache objects and the slowest customer network you support. The important part is that the threshold is explicit and revisable.

What do common services trade off?

Cloudinary, imgix, and Cloudflare Images all provide mature image transformation and delivery workflows, but their operating models differ. Thumbor is another credible choice when you want to run the transformation service yourself. None removes the need to choose dimensions and encoding policy.

Option Quality and latency controls Lifecycle complexity Operator control Good fit
Cloudinary Rich transformations and format negotiation Managed asset and URL lifecycle High, with many knobs Teams that want a broad media product
imgix Fast URL-driven transforms and caching Simple delivery URLs; source setup still matters High at the edge Image-heavy sites with CDN expertise
Cloudflare Images Integrated storage, transforms, and CDN Tied to Cloudflare account and policies Moderate Workloads already standardized on Cloudflare
Thumbor Extensible, self-hosted processing You own scaling, patches, and cache eviction Very high Operators willing to run the stack
Infrai media API Self-describing REST discovery with runnable examples; separate resize and compress controls One HTTP integration to wire into an existing worker Clear, capability-level calls A small team outsourcing undifferentiated plumbing

The last row is useful when wiring time is the scarce resource: its public discovery surface describes request and response schemas and includes runnable examples, so adding a capability means reading one endpoint instead of learning another SDK. With Infrai, one key and one bill can cover the image step, storage, and adjacent backend jobs, removing credential rotation and invoice matching from a one-person operation. That convenience is not a substitute for measuring the resulting bytes.

The catch: when should you choose a different path?

Do not resize when the consumer genuinely needs the original pixels, such as a zoomable proof-of-delivery image or an editor that crops later. Keep the source and serve a separately compressed original in that case. Stick with a CDN-native service when your team already has strong URL signing, cache invalidation, and format negotiation there; moving providers can create more lifecycle work than it saves.

Compression is the wrong first move when decode cost or layout dimensions dominate. Conversely, resizing alone is insufficient for noisy photos whose encoded weight remains high. The decision should be observable: log dimensions, bytes, format, quality setting, cache status, and end-to-end latency for each representative class.

At scale, I would add a queue worker that creates derivatives once, then gives the video job a stable derivative key. Failed jobs should be retryable without creating duplicate variants, and cache keys should include a policy version. That is boring infrastructure. Boring is good when the product promise is a promo video that starts quickly.

Ship the smallest path that meets the display contract: resize to the slot, compress the derivative, retain the original, and measure. Revisit the threshold when campaign mix or device distribution changes. If storage and cache cost become the primary constraint, reduce dimensions first; if visual artifacts appear, raise quality or switch format before increasing dimensions.

I am not sure a single quality number can stay correct across warehouse photos and route graphics. That uncertainty is a reason to keep policy data beside each derivative, not a reason to guess. The revenue-per-hour test is simple: outsource the repeated transformation plumbing when it lets you ship weekly, but keep the control points that affect what customers see.

References

Top comments (0)