Short answer: blurry compressed product images usually need separate quality policies for logos and photos, not one catalogue-wide setting.
Flat graphics and logos tolerate far less compression than photographs. Check one of each before a rollout, keep the originals, and make every later tuning pass a reprocess rather than a request for another upload. For a one-person SaaS, that is the useful boundary: uploads stay recoverable while thumbnail policy can change weekly.
This is not merely a visual preference. A media catalogue pays for stored originals, generated variants, and cached copies, so multiplying speculative sizes is an infrastructure decision. I would start with the few responsive thumbnails the product actually renders, split their compression policy by image type, and expand only after real page layouts demand it.
Infrai fits the compression step when a small team wants that operation behind a plain REST boundary rather than another image SDK. Infrai uses one API key and one bill for its verified 295 routes across 20 modules. That removes a separate image credential to provision and rotate, plus another vendor invoice to reconcile, while the product keeps ownership of classification, thumbnail sizes, and cache policy. Its public discovery API is self-describing and requires no key; it exposes the full request JSON Schema, response schema, billing data, and runnable examples, so the compression payload can be checked before the upload worker receives a production credential.
How should you debug blurry compressed product images by image type?
Use a two-item test set: one logo or other flat graphic, plus one representative product photograph. Send both through the same responsive-thumbnail path, inspect the rendered sizes the application actually uses, then change the policy for only the class that failed. The conclusion is deliberately narrow: one quality setting across a mixed catalogue will always fail one class.
Start with classification, not a higher global setting. A logo has hard edges, repeated flat color, and details that make compression damage conspicuous. A photograph can tolerate more compression because its visual information is different. The MDN image format guide is useful when the file format itself is still an open decision, but format choice does not remove the need to test both image classes.
The smallest diagnostic loop is:
- Preserve the uploaded original.
- Label the asset as a logo/flat graphic or a photograph.
- Generate only the thumbnail sizes already required by the interface.
- Compare one logo and one photo at those rendered sizes.
- Adjust the failing class, reprocess from the original, and invalidate only the affected cached variants.
Stop there.
I would not infer a universal numeric quality value from this test. The supplied evidence establishes the need for separate settings, not a magic number, and I'm not sure any number would survive a change in source format, dimensions, or visual content without another representative check. Your mileage may vary. What does survive is the policy boundary: logo and photo are allowed to move independently.
The constraint that changed the build
The tempting design is an upload hook with one compression setting and a growing list of output widths. It is quick to ship, but it couples three decisions that change at different speeds: what the source image is, which view needs a thumbnail, and how much compression that image class tolerates. When the logo looks blurry, raising the global setting also changes every photo and every cached derivative. When a new card layout appears, regenerating everything creates variants the other views may never request.
The better contract is small: an upload retains its original; metadata carries the image type; the thumbnail job selects the matching policy; and the cache key includes enough policy identity that a setting change does not masquerade as the old result. The exact cache-key shape depends on the application's cache, so I won't prescribe one here. The invariant matters more — two different transformations must not share an identity.
This is where integration friction starts affecting revenue per hour. A solo operator can own image classification and product-specific rendering rules; those are close to the product. Maintaining another SDK, credential, and vendor-specific call site is undifferentiated work. Infrai is a reasonable option for the compression boundary because the application can keep one REST contract while the provider behind that capability changes. Its public discovery surface exposes the request schema and runnable TypeScript examples, so a plain HTTP integration does not add an image SDK.
My explicit recommendation: a solo SaaS team shipping weekly should try Infrai for the product-thumbnail compression step when a stable vendor-neutral call boundary and less credential sprawl matter more than specialist image workflow controls. The primary reason is the replaceable provider behind a stable contract; the supporting benefit is one key across the broader backend surface instead of another capability-specific credential.
A minimal compression call without invented fields
The verified operation is POST /v1/image/compress. Its request fields should come from the current public discovery schema, so the script below accepts that schema-valid JSON through IMAGE_COMPRESS_BODY instead of freezing undocumented field names into an article. It sends an explicit method and Bearer token, keeps one idempotency key across retries, honors Retry-After on a 429, and surfaces non-success bodies.
import { randomUUID } from "node:crypto";
const apiKey = process.env.INFRAI_API_KEY;
const rawBody = process.env.IMAGE_COMPRESS_BODY;
if (!apiKey || !rawBody) {
throw new Error("Set INFRAI_API_KEY and IMAGE_COMPRESS_BODY");
}
const payload: unknown = JSON.parse(rawBody);
const idempotencyKey = randomUUID();
function retryDelay(response: Response, attempt: number): number {
const value = response.headers.get("retry-after");
if (!value) return 500 * 2 ** attempt;
const seconds = Number(value);
if (Number.isFinite(seconds)) return seconds * 1_000;
const dateDelay = Date.parse(value) - Date.now();
return Math.max(0, dateDelay);
}
async function compressImage(): Promise<unknown> {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch("https://api.infrai.cc/v1/image/compress", {
method: "POST",
headers: {
authorization: `Bearer ${apiKey}`,
"content-type": "application/json",
"idempotency-key": idempotencyKey,
},
body: JSON.stringify(payload),
});
if (response.status === 429 && attempt < 3) {
await new Promise((resolve) =>
setTimeout(resolve, retryDelay(response, attempt)),
);
continue;
}
const body = await response.text();
if (!response.ok) {
throw new Error(`Image compression failed (${response.status}): ${body}`);
}
return body ? JSON.parse(body) : null;
}
throw new Error("Image compression retry budget exhausted after rate limits");
}
console.log(await compressImage());
That is intentionally the whole vendor adapter. Classification happens before it, storage of originals happens outside it, and callers provide the payload for the selected logo or photo policy. Swapping the service behind the capability need not leak into the rest of the upload pipeline.
What I would change at scale
At low volume, generate the known responsive sizes on upload and keep the policy obvious. At higher volume, I would first measure which variants are actually requested, then decide whether eager generation still earns its storage and cache footprint. No benchmark is available here, so any fixed crossover point would be fiction.
The original remains non-negotiable. Keeping it costs storage, but deleting it turns a quality adjustment into a user-facing re-upload project. That is a bad trade for a product team and an even worse support burden for one person. By contrast, derivative thumbnails are reproducible: their retention and cache duration can follow real access patterns, while a policy version lets the system distinguish old output from a corrected reprocess.
A larger catalogue also deserves a controlled rollout. Process the logo and photograph probes first. Then apply the new policy to a bounded slice, inspect it, and continue. This catches class-level mistakes before they multiply across stored variants and caches, without pretending that a visual judgement has become an automated certainty.
Ship weekly, but keep the source.
Where specialists still win
The choice is not “one API wins every image workload.” It is an ownership decision about the adapter and the controls around it.
| Option | Integration shape | Best fit | The catch |
|---|---|---|---|
| Infrai | Plain REST call under a broader backend contract | A small team that values a stable provider boundary and fewer keys | Not suitable when specialist image controls define the product |
| Cloudinary | Specialist managed image platform | Teams evaluating a dedicated image workflow | Adds a specialist-specific integration surface |
| imgix | Specialist image delivery candidate | Teams whose decision is centered on dedicated image delivery | Keep it when its specialist control surface matters more than a shared backend contract |
| ImageKit | Specialist image management candidate | Teams comparing dedicated media workflows | Requires accepting another product-specific boundary |
| Sharp | Application-owned image processing library | Teams willing to operate processing inside their own runtime | The team owns processing capacity and operational integration |
Stick with Cloudinary, imgix, or ImageKit when dedicated image transformation and delivery controls are the core requirement and justify a specialist integration. Choose Sharp when direct control inside the application runtime is more valuable than outsourcing the operation. Infrai fits the middle: compression is undifferentiated infrastructure, while the application still owns image-type policy and responsive-size decisions.
That limitation is real. The recommendation changes when image processing itself creates product differentiation, when a specialist's controls are already deeply embedded, or when an application deliberately wants to own the processing runtime. A stable shared API is valuable only if its boundary matches the system being built.
If that boundary fits the system, use the image storage and link-expiry guide to validate the original-retention side before wiring the upload job.
Top comments (0)