WebP in Production: A Failure Mode Runbook

WebP support sits above 97% globally in 2026, which has quietly changed the calculus for most teams: fallback logic that used to be mandatory is now optional for a lot of sites, and the more common production bugs today come from fallback logic that's half-implemented, not from WebP itself failing to render. This runbook covers four failure modes teams actually hit, how to recognize each one, and how to fix it.

Failure mode: stale cache serves the wrong format to the wrong browser

Trigger: Your server or CDN does content negotiation, rewriting a single URL to return WebP or the fallback format depending on the request's Accept header, but the response doesn't include a Vary: Accept header.

Symptom in production: A cache node sees a request from a WebP-capable browser, fetches and caches the WebP response under the plain URL, with no record that the response depends on the Accept header. The next visitor to hit that same cache node, on a browser without WebP support, gets served the cached WebP response anyway, broken image, because the cache had no instruction to treat requests with different Accept headers as different cache entries. The same failure runs in reverse: a non-WebP response gets cached first and WebP-capable visitors get denied the smaller format for the life of that cache entry.

Blast radius: Scoped to whichever cache nodes got "poisoned" first and however long the cache TTL runs, which in practice means it's inconsistent and hard to reproduce, some users see it, some don't, depending on which edge node they hit and what was cached there first. This exact failure mode is well documented, including cases where teams found their CDN wasn't caching optimized images correctly at all because of this missing header.

Mitigation: If you're doing server or CDN-side content negotiation on a shared URL, Vary: Accept is not optional, add it to every response that varies by format. The more reliable alternative many teams land on instead: serve each format from a distinct URL (a different filename or a query parameter), so the cache key is unambiguous and there's no header-dependent branching for the cache layer to get wrong in the first place.

Verification: Run curl -I against the same image URL twice, once with an Accept: image/webp header and once without, from both a cold cache state and a warm one. The content-type in the response should match what that specific request should receive every time, not whatever got cached first.

Failure mode: <picture> fallback doesn't cover half the places images actually load from

Trigger: A team implements <picture> with WebP and a fallback source in their main page templates and considers the fallback problem solved.

Symptom in production: <picture> only covers <img> tags that go through markup you actually control. CSS background-image declarations, images injected by a CMS field, third-party embedded widgets, and anything inside an email template or RSS feed don't go through that markup at all, they keep referencing whatever URL was hardcoded, WebP included. A browser or client that can't render WebP hits one of these paths and gets a broken image with zero fallback, since none of the picture-element logic ever touched it.

Blast radius: Small on general web traffic, since it only affects the minority without WebP support, but close to 100% for anything routed through email, since mainstream email clients render HTML/CSS very inconsistently and WebP support in that specific environment lags well behind browsers.

Mitigation: Treat <picture> as covering exactly the markup it's written into, nothing more. For CSS backgrounds, the image-set() CSS function offers equivalent format fallback logic natively. For email, RSS, and anything rendered outside a browser your team controls, use a broadly compatible format (JPEG or PNG) as the actual served file, not WebP with an assumed fallback that doesn't exist in that context.

Verification: Search your codebase and CMS content for .webp references outside your image markup, specifically in CSS files and any HTML sent through email or feed generation. If you find one, that's a spot with no fallback at all, not a partial one.

Failure mode: skipping fallback entirely, without checking whether your actual traffic matches the global average

Trigger: A team reads that WebP support is above 97% globally and decides that's low enough risk to serve WebP directly, with no fallback anywhere, treating the image path the same as any other static asset.

Symptom in production: For most sites, nothing, this works fine, and it's a legitimate, increasingly common simplification. This is exactly how this site's own images are served: no <picture> wrapper, no server-side content negotiation, images referenced directly by their file extension and delivered as immutable, long-cached static assets through Vercel's edge network. The risk only shows up if your actual traffic doesn't match the global average, older enterprise browser environments, certain regions with slower device upgrade cycles, or in-app webviews with unusual rendering behavior can all sit meaningfully below 97%, and unlike a CSS feature that degrades gracefully, an image format a browser can't decode fails hard: a broken icon, not a slightly-less-optimized image.

Blast radius: Zero for a userbase that matches the global average. Potentially significant for a userbase that doesn't, and the failure is silent from the server's perspective, since the request still gets a 200 response, it just can't be rendered on the receiving end.

Mitigation: Before skipping fallback, check actual analytics for the specific slice of traffic on older Safari (pre-14), legacy IE holdouts, and unusual in-app browsers, don't rely on the global figure alone. If that slice is genuinely negligible for your audience, skipping the fallback layer, the way this site does, is a reasonable simplification, not a shortcut you're getting away with. If it's not negligible, that's the signal to add <picture> specifically, rather than defaulting to it everywhere out of caution.

Verification: Pull a browser and OS-version breakdown from your real analytics, cross-reference the versions against current WebP support data, and confirm the unsupported segment is small enough to accept before deciding to skip fallback logic.

Failure mode: JavaScript feature-detection produces false negatives, wasting bandwidth silently

Trigger: A team uses a JavaScript-based WebP support check, loading a tiny test image and inspecting whether it decoded, instead of relying on <picture>'s native format negotiation.

Symptom in production: The detection script can return a false negative on privacy-hardened browsers, unusual embedded webviews, or less common user agents, causing the fallback format to load even on a browser that actually supports WebP perfectly well. Nothing visibly breaks, the image loads fine, it's just larger than it needed to be, which makes this failure mode easy to miss entirely unless someone is specifically watching bandwidth or image payload size.

Blast radius: Not a broken-image incident, a quiet, ongoing efficiency loss that scales with traffic and is easy to overlook because nothing in the user experience signals it.

Mitigation: Prefer <picture> over JavaScript-based detection wherever the choice doesn't require synchronous, script-driven logic. The browser's own format support check, built into how it evaluates <source> elements, doesn't have the heuristic edge cases a detection script does.

Verification: Cross-reference server logs of which format got served against the User-Agent on the request. If current-version Chrome, Firefox, or Safari requests are showing up receiving the fallback format, that's the signature of a detection script producing false negatives, not an actual compatibility gap. You can test and optimize all your image assets using our converter right in your browser.

Convert and validate your WebP assets

Generate production-ready WebP files or create reliable JPG and PNG fallback assets locally.