WebP
An image format from Google, specified in RFC 9649 and registered as image/webp. One container, two codecs: lossy VP8 intra-frame and a lossless codec, plus transparency and animation. Google measures lossless WebP 26% smaller than PNG and lossy WebP 25-34% smaller than JPEG at equal SSIM.
Full Explanation
WebP is an image file format from Google. It is based on the Resource Interchange File Format (RIFF) container. It supports lossless and lossy compression, plus alpha (transparency) and animation. It is specified in RFC 9649 (Informational, November 2024). IANA has registered the image/webp media type for it (RFC 9649, section 6).
Three things WebP is not. It is not one codec. A WebP file carries either VP8 lossy data or WebP lossless data. The two behave differently enough that "convert to WebP" is an ambiguous instruction. It is not a content coding like gzip or Brotli. Those compress a representation without losing the identity of its underlying media type (RFC 9110, section 8.4). WebP replaces the media type outright instead. So it is negotiated with Accept, not Accept-Encoding. It is not AVIF either. AVIF compresses harder, but it costs far more CPU to encode. Unlike WebP, it is not yet as widely supported (MDN image types guide).
For a CDN, that makes WebP the low-risk modern format. It is cheap to encode and negotiable per request. It is already the most-used of the modern formats, at roughly 12 percent of images crawled on mobile. AVIF trails at 1.0 percent. WebP ranks behind only JPEG, PNG and GIF (Web Almanac 2024, Media).
How it works
A WebP file MUST begin with a RIFF header with the FourCC WEBP (RFC 9649, section 2.4). Inside that container, chunks carry the image bitstream. In the extended format, chunks can also carry an optional color profile, animation control data, alpha data, and Exif or XMP metadata (RFC 9649, section 2.7). Animation is not a separate format. It is the same container, driven by ANIM and ANMF chunks (RFC 9649, section 2.7.1.1).
The container holds one of two compression algorithms. Lossy compression uses VP8 intra-frame encoding. This is predictive coding, the same method the VP8 video codec uses on keyframes. It uses the values in neighboring blocks of pixels to predict the values in a block, then encodes only the difference. The lossless algorithm stores and restores the pixel values exactly, including the color values for fully transparent pixels. It does this using a universal algorithm for sequential data compression (LZ77), prefix coding, and a color cache for the bulk data (RFC 9649, section 1; Google WebP documentation).
Two hard limits follow from the bitstreams. VP8 defines 14 raw bits for width and height. So the maximum pixel dimensions of a WebP image are 16383 x 16383 (Google WebP FAQ; RFC 6386, section 18.1). Lossy WebP also inherits VP8's color model. It works exclusively with an 8-bit Y'CbCr 4:2:0 image format. Lossless WebP works exclusively with RGBA (Google WebP FAQ). There is no 10-bit or wide-gamut lossy WebP. There is also no progressive or interlaced decoding refresh in the JPEG or PNG sense.
On Google's own measurements, lossless WebP images are 26% smaller in size compared to PNGs. Lossy WebP images are 25-34% smaller than comparable JPEG images, at equivalent SSIM quality index (Google WebP documentation). Treat those as lab figures, not a guarantee. Cloudflare reports WebP cutting PNG file sizes by approximately 26 percent but JPEG by only around 17 percent in production, and "this depends on several factors" (Cloudflare Polish compression).
Delivery is proactive content negotiation. The Accept header field lets user agents specify their preferences for response media types (RFC 9110, section 12.5.1). A WebP-capable browser sends something like Accept: image/avif,image/webp,image/*,*/*;q=0.8, and the server picks a format. One URL can then resolve to several formats. So the response needs Vary: Accept. RFC 9110 puts this as a SHOULD, not a MUST. An origin server SHOULD generate a Vary header field on a cacheable response when it wishes that response to be selectively reused. Vary might be elided when variance matters less than Vary's performance impact on caching (RFC 9110, section 12.5.5). The caching consequence is defined separately. A cache MUST NOT reuse a stored response with a Vary header field unless the nominated request header fields match. That is what expands the cache key into one entry per format (RFC 9111, section 4.1).
Why it matters for a CDN
Images dominate page weight. HTTP Archive reports that images have always been the largest contributor to page weight overall, in past page weight chapters. The median mobile homepage carried about 900 KB of images in 2024 (Web Almanac 2024, Page Weight). Converting JPEG and PNG to WebP at the edge therefore cuts bandwidth and egress. It also improves render time, and moves encoding CPU off the origin and into the delivery path, where the result is cached once and reused. It is the byte-reduction half of image optimization.
The catch is that format conversion multiplies the cache. Every negotiated format is a separate stored variant of the same URL. So a CDN that offers JPEG, WebP and AVIF stores three objects where the origin has one, and each starts cold. That is why edge conversion is worth it for long-tail image libraries. It is close to worthless for images requested once.
What CDNs do
- Cloudflare. Polish is a one-click image optimization product. It automatically optimizes images from your origin. It is not available on the Free plan (Pro and above only). With the WebP setting enabled, it creates WebP versions. It serves them only when two conditions both hold: the
Acceptheader from the browser includes WebP, and the WebP image is significantly smaller than the lossy or lossless recompression of the original format. It will not convert images the origin already serves as WebP (Polish compression docs, Polish overview). - Akamai. Image & Video Manager supports WebP as both an input and an output format. Akamai announced in October 2022 that it was moving to the HTTP
Acceptheader for image format selection (Oct 10, 2022 changelog). But its default is bytes, not modernity. The default behavior is to serve the image with the fewest bytes possible that is of acceptable quality as defined in the policy. In some instances that means an older format like JPG or PNG. Preferring AVIF or WebP is a separate switch. Prefer Modern Formats is set to False by default (Optimize your images, Supported formats). - Fastly. Image Optimizer opts in per URL with a query parameter. With auto=webp, if the browser's
Acceptheader indicates compatibility, it delivers a WebP image. auto=avif does the same for AVIF. Fastly documents the usual chain as AVIF, then WebP, then JPEG. Fastly also notes that AVIF is a premium feature that will increase your bill, but WebP is not (Fastly auto reference). - Cloudinary. Setting fetch_format to auto (f_auto in URLs) performs automatic format selection based on the requesting browser. It delivers AVIF, JPEG XL or WebP, including animated WebP. Cloudinary warns that format selection has to be resolved per request at the CDN during delivery. So f_auto must not be baked into an upload or a named transformation (Cloudinary image optimization docs).
The pattern to take away is that none of these convert unconditionally. Every one of them can decide to keep the original format. Three of the four require you to turn conversion on. Check what your CDN actually returns. Do not assume WebP is being delivered.
Watch out for
- Vary and the vendor can conflict. Polish may not be applied to origin responses that contain a Vary header. The only Vary header Cloudflare accepts there is
Vary: Accept-Encoding(Polish compression docs). So the generic advice "always send Vary: Accept" can silently disable the very optimization you wanted. Send Vary from whichever layer performs the negotiation, and verify. - Omitting Vary poisons the variant. RFC 9111 flags this directly. Some resources mistakenly omit the Vary header field from their default response. The effect is that this response gets chosen for subsequent requests to that resource, even when more preferable responses are available (RFC 9111, section 4.1). In practice that means one client's JPEG or WebP served to everyone behind that cache.
- Conversion can make the file bigger. Google is explicit that a WebP can grow larger than its source, mainly because of the YUV420-versus-ARGB colorspace difference. Converting a JPEG saved at quality 80 to a WebP at quality 95 will usually produce a larger file (Google WebP FAQ). Cloudflare says the same from the operator side. WebP compresses better than JPEG on average, but there are exceptions. In some occasions JPEG compresses better than WebP (Polish compression docs). A pipeline that does not compare sizes and keep the smaller output will ship regressions.
- Bad input, bad output. If you serve low-quality JPEG images at the origin (quality setting 60 or lower), it may not be beneficial to convert them to WebP. This is because blocky edges and compression noise actually increase the size of the WebP. Cloudflare recommends serving high-quality JPEG images at quality 80 to 90 at the origin. It also does not make sense to apply lossy optimizations twice. Quality degradation will then be larger than the savings in file size (Polish compression docs).
- "Supports WebP" is not one capability. The container's chunks were extended in the format's early stages. So some older readers may not support lossless or animated image decoding, even though they read baseline WebP (RFC 9649, section 5). WebP is natively supported in Google Chrome, Safari, Firefox, Edge, the Opera browser, and by many other tools and software libraries (Google WebP documentation). Cloudflare puts the exceptions as Internet Explorer and KaiOS (Polish compression docs).
- Decoders are an attack surface. RFC 9649 warns that implementations face integer overflows, out-of-bounds reads and writes, and resource exhaustion. Implementations are likely to take input from unknown and possibly unsafe sources. That may result in arbitrary code execution (RFC 9649, section 4). This is not theoretical. CVE-2023-4863 was a heap buffer overflow in libwebp, rated 8.8 High. It allowed a remote attacker to perform an out of bounds memory write via a crafted HTML page (NVD, CVE-2023-4863). A CDN that transcodes visitor-supplied images runs that decoder on untrusted input. So keep libwebp patched.
- Do not overstate AVIF. Cloudflare measured AVIF as typically 50% smaller than comparable JPEGs, against WebP's reduction of only 30%. But that comparison dates from September 2021 (Optimizing images on the web). The cost is real. AVIF encoding can be an order of magnitude slower than encoding to other formats. If an image is too large to be encoded quickly, Cloudflare falls back to WebP or JPEG (Cloudflare transform docs). WebP is the fallback that actually gets served.
Best practice
- Negotiate on
Accept. Prefer AVIF, then WebP, then JPEG or PNG. Generate AVIF ahead of time, or let the CDN decide. Do not put an order-of-magnitude-slower encode on the cache-miss path. - Keep origin images at high quality (JPEG 80 to 90) and convert once. Never chain two lossy passes. Never convert an already-optimized origin image a second time.
- Compare output sizes. Keep the smaller file per image. Assume nothing about the direction of the change, especially for PNGs with few colors and for low-quality JPEG sources.
- Decide which layer owns the negotiation. Set
Vary: Acceptthere. Confirm your CDN honours it rather than ignores it. NormaliseAcceptinto a small set of buckets if you control the cache key. RFC 9111's matching rules only tolerate whitespace, field-line combining and semantically identical normalisation. So two near-identicalAcceptstrings miss each other and fragment the cache. - Declare fallbacks in markup with the picture element, rather than relying only on the edge. That way, a client that cannot decode WebP, or cannot decode animated or lossless WebP, still gets an image.
- Check the source dimensions against the 16383 x 16383 ceiling and the 8-bit 4:2:0 constraint. Do this before making WebP the target format for photography or wide-gamut artwork.
- Keep libwebp and your image pipeline patched. Treat visitor-uploaded images as untrusted input to the encoder.
Examples
# HTML: serve WebP with fallback
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.jpg" type="image/jpeg">
<img src="image.jpg" alt="example">
</picture>
# Nginx: serve WebP based on Accept header
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
location ~* \.(jpg|png)$ {
try_files $uri$webp_suffix $uri =404;
add_header Vary Accept;
}
# Convert with cwebp CLI
$ cwebp -q 80 input.jpg -o output.webp
# input.jpg: 245KB -> output.webp: 162KB (34% smaller)
Frequently Asked Questions
An image format from Google, specified in RFC 9649 and registered as image/webp. One container, two codecs: lossy VP8 intra-frame and a lossless codec, plus transparency and animation. Google measures lossless WebP 26% smaller than PNG and lossy WebP 25-34% smaller than JPEG at equal SSIM.
# HTML: serve WebP with fallback
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.jpg" type="image/jpeg">
<img src="image.jpg" alt="example">
</picture>
# Nginx: serve WebP based on Accept header
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
location ~* \.(jpg|png)$ {
try_files $uri$webp_suffix $uri =404;
add_header Vary Accept;
}
# Convert with cwebp CLI
$ cwebp -q 80 input.jpg -o output.webp
# input.jpg: 245KB -> output.webp: 162KB (34% smaller)
Related CDN concepts include:
- Vary Header — Vary is the response header that names the request headers the origin used to select …
- Compression — An HTTP content coding that shrinks a message body, almost always a response body, so …
- Content Negotiation — The HTTP mechanism that lets one URL serve several representations of a resource: the client …
- Image Optimization — Image optimization is delivering each image in the fewest bytes that still look right: re-encoding …