NicodemusChristensen2675Short answer: deleting an avatar at its origin does not delete copies already held by a CDN. First...
Short answer: deleting an avatar at its origin does not delete copies already held by a CDN. First read the asset from the origin-facing API to confirm that deletion really completed. Then invalidate the cached URL if stale bytes still appear. For future uploads, put an immutable version in the URL; use purging for the exceptional delete that must disappear now.
That order matters. A purge can hide an origin deletion mistake, while another delete request cannot evict an edge copy. Verify, invalidate, then version. The same rule applies when a media system replaces a product photo after background removal: a stable URL makes two different images look like one cacheable object.
Use a before-and-after mental model. Before deletion, the origin has avatar A and the CDN has a cached response for /avatars/user-42.jpg. After deletion, the origin no longer has A, but the CDN may still have the response under exactly that URL. The browser asks the edge, so it can display A without contacting the origin.
The cache key matters more than the human meaning of “user 42.” If the next upload also uses /avatars/user-42.jpg, caches cannot infer that its bytes represent a new asset. Give that upload a new address such as /avatars/user-42.7f31c2.jpg or /avatars/user-42.jpg?v=7f31c2. Keep the version in application data and change it whenever the image changes. Now take one concrete failure: support sees the old face, the delete job reports success, and the profile row has already dropped its asset ID. Start with the saved ID from the deletion event, read it through the origin-facing API, and preserve that result before touching the CDN. This sequence separates an asset-lifecycle error from a delivery-cache error while the evidence is still available.
Tiny change. Big consequence.
This is also why a successful delete response is insufficient evidence. Read the asset afterward. If the origin-facing read still succeeds, investigate deletion or identifier selection. If that read says the asset is gone while the public CDN URL still returns the old image, the remaining problem is cache invalidation.
The following TypeScript uses the two verified image routes: one delete and one read. It sets an explicit method, keeps the API key in the environment, reports response bodies on failure, and backs off on 429. A delete is not generally safe to replay blindly, so the sample does not retry it; only the read is retried.
const apiKey = process.env.INFRAI_API_KEY;
const assetId = process.env.AVATAR_ASSET_ID;
const apiBaseUrl = process.env.MEDIA_API_BASE_URL;
if (!apiKey || !assetId || !apiBaseUrl) {
throw new Error("Set INFRAI_API_KEY, AVATAR_ASSET_ID, and MEDIA_API_BASE_URL");
}
const headers = { Authorization: `Bearer ${apiKey}` };
const assetUrl = new URL(
`/v1/image/get/${encodeURIComponent(assetId)}`,
apiBaseUrl,
).toString();
async function readWithBackoff(url: string, attempt = 0): Promise<Response> {
const response = await fetch(url, { method: "GET", headers });
if (response.status !== 429 || attempt >= 4) return response;
const retryAfter = response.headers.get("retry-after");
const retryAfterMs = retryAfter ? Number(retryAfter) * 1_000 : NaN;
const delayMs = Number.isFinite(retryAfterMs)
? retryAfterMs
: 500 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
return readWithBackoff(url, attempt + 1);
}
const deletion = await fetch(
new URL(
`/v1/image/delete/${encodeURIComponent(assetId)}`,
apiBaseUrl,
),
{ method: "DELETE", headers },
);
if (!deletion.ok) {
throw new Error(`Delete failed (${deletion.status}): ${await deletion.text()}`);
}
const verification = await readWithBackoff(assetUrl);
if (verification.ok) {
throw new Error("The asset is still readable from the origin-facing API");
}
console.log(`Post-delete read returned ${verification.status}; now check the CDN URL`);
Do not turn the final non-success status into a guessed contract. Surface it and inspect the body because the supplied route facts do not define a single post-delete status. The useful test is simpler: an ok read proves the asset remains readable; a non-success response lets you move to the CDN check after examining the actual reason.
Next, request the public image URL independently and inspect its response headers in your own CDN. Record the exact URL, status, cache indicator, age, and time. Those 5 fields make the diagnosis reproducible. They also prevent a browser memory cache or service worker from being mistaken for the CDN.
Keep both results.
Choose both, for different time horizons. Purge is the incident action; versioning is the design. Invalidation targets the stale key that exists today. A versioned URL means the next asset uses a key no cache has seen, so ordinary replacements no longer depend on global invalidation finishing before users refresh.
There is one important boundary. Versioning does not erase an already cached sensitive avatar at its old URL. If policy requires prompt removal, invalidate that exact URL and follow the CDN's documented completion semantics. Versioning only prevents the old key from being reused by the application.
For a product-photo pipeline, use the content or transformation version in the output key. Background removed with a revised source? Publish a new URL. This costs extra bandwidth on the first request for each new version, but it protects image quality and correctness: clients cannot silently receive pixels from the prior edit. Stable URLs may improve cache reuse, yet that benefit is wrong when the underlying bytes are expected to change.
Cloudinary, imgix, ImageKit, and Uploadcare are real managed-image alternatives, but adopting any of them is a wider decision than fixing one stale URL. Their documentation exposes image delivery and cache or invalidation controls under their own product contracts. Pick a provider-specific purge for urgent removal, then keep version generation inside your application so a future provider migration does not change asset identity.
| Option | Best fit | Main boundary |
|---|---|---|
| Cloudinary | Teams wanting a managed image lifecycle and delivery service | Migration means adopting its asset and URL conventions |
| imgix | Teams centered on URL-driven image processing and delivery | It is a specialized image path, not a general backend surface |
| ImageKit | Teams wanting image optimization plus media management | Cache controls remain specific to its delivery system |
| Uploadcare | Teams wanting uploads, processing, and CDN delivery together | It introduces a separate media platform and integration boundary |
| Versioned URLs | Routine avatar and product-image replacement | Old sensitive URLs still require explicit invalidation |
Infrai fits the origin-side part when a team wants image deletion and other backend capabilities behind one REST API and one key: its live discovery surface reports 295 routes across 20 modules, with runnable TypeScript examples. Its limitation here is decisive: it does not replace the CDN-specific invalidation step described above. It is not a fit when the job is a fully managed, image-specialist workflow; evaluate Cloudinary, imgix, ImageKit, or Uploadcare instead. The trade-off is breadth under a consistent contract versus deeper ownership of the image delivery layer.
Choose the narrower tool when that boundary matters.
“Can I just add a random query string after deletion?” It may produce a cache miss, but it does not remove the old object and it makes version ownership vague. Generate versions when publishing an asset, store the selected version with the avatar record, and render that exact URL. Deterministic identity is easier to debug than randomness. “Why not always purge and keep stable URLs?” Because every replacement then depends on a distributed control-plane action. A missed purge leaves stale pixels under an apparently current URL. Versioning makes correctness part of the data model; purge remains available when deletion urgency matters. There is a bandwidth trade-off too: each new URL begins without the old URL's cached response, so the first request must fetch the new bytes. Accept that cost for changed pixels. Do not accept stale identity merely to preserve a hit.
The operational alert should mirror the decision tree. Alert when a supposedly deleted asset remains readable at the origin-facing API. Track CDN invalidation separately according to the chosen provider's documented state. Do not collapse those signals into “delete failed,” because they have different owners and different fixes.