DEV Community

catatsuy
catatsuy

Posted on

Designing and Optimizing Image Delivery with a CDN

In modern web services, it is no longer enough to handle images the way we did in the past.

A long time ago, it was common to generate a few fixed thumbnail sizes when a user uploaded an image, then serve those files as they were. That approach was often good enough. But today, high-density smartphone displays are common, and the same image may need different sizes and formats depending on the client. On top of that, CDNs and image optimization services can now generate image variants dynamically and cache them efficiently.

Because of this, image delivery is no longer just a frontend concern. It is part of system design.

What matters most to me is this:

  • image size matters more than quality
  • image URL design should be controlled by the backend, not the frontend
  • the backend should return multiple candidate URLs for the same image, and the browser should choose one
  • modern formats and image optimization services are useful, but they need to be used with a good understanding of their behavior

In this article, I will explain how I think image delivery should be designed for modern web services.

Image size matters more than quality

For JPEG-like images, there are two major parameters: image size and quality. But if your goal is visual quality, image size is more important than quality.

When an image is too small, people notice it immediately. A difference in quality is often more subtle. Because of that, the first step should not be fine-tuning quality. The first step should be making sure the image is large enough.

Today, devices like iPhones with Retina displays are normal. On smartphones, an image often needs to be 2x larger than its display size, and sometimes 3x larger, to look good. The visual quality of thumbnail images directly affects user experience. In some services, it can even affect click-through rate or sales.

Of course, larger images increase transfer size. That affects CDN cost, response time, and mobile data usage. But even with that trade-off, the right order is still important: first make sure the image is large enough, then tune quality and format.

For photo-like images, this is especially important. If you want the same visual result, using a larger image with slightly lower quality can sometimes be more efficient than trying to compensate for an image that is too small by increasing quality. If the image is too small, raising quality does not solve the real problem.

The backend should control image URL design

A CDN or image optimization service can generate different sizes and formats from the original image. That is very useful. But it does not mean the frontend should build image URLs freely.

If the frontend builds URLs on its own, long-term operation becomes harder.

For example, later you may want to:

  • change parameter names
  • change how quality is handled
  • introduce signed URLs
  • move to another image optimization service

If the client owns the URL format, backward compatibility becomes painful. This is especially true for native apps, because old versions remain in use for a long time.

This also matters for CDN cache efficiency. Image transformation works best when the same request conditions lead to the same URL. If the frontend calculates width and quality freely, many slightly different URLs will appear, and the cache will fragment. Even changing query parameter order can create a different URL. That lowers cache hit rate, increases transformation work, and adds more origin load and cost.

Because of that, image URL design should be controlled by the backend. The backend should define the allowed sizes, quality policy, and format policy, then return valid candidate URLs. The frontend should use those candidates.

Return multiple candidate URLs for the same image, then let the browser choose

Even if the backend controls image URLs, that does not mean the application has to decide the exact image for every device by itself.

With picture and srcset, browsers can choose images based on display width and pixel density. Those are standard browser features and they are already good enough for this job.

https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/picture

https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Responsive_images

The important design is this:

  • for the same image, the backend returns multiple candidate URLs
  • the browser chooses the best one

For example, for one product image, the backend can prepare several URLs for different sizes. Then picture or srcset can let the browser choose the right one for the current layout and display density.

This keeps URL design under backend control while still using the browser’s built-in image selection features.

A low-effort starting point: use the Accept header

If you want to introduce modern image formats, you do not need to start with full picture and srcset support everywhere.

A lower-effort starting point is to use the Accept header.

The Accept header tells the server what content types the client can receive. If your CDN or image optimization service supports it, the image delivery side can look at that header and return AVIF or WebP for supported clients, and JPEG or PNG for others, while keeping the same image URL.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept

This is practical because it does not require large template changes.

But this method has an important limitation: it mainly solves format negotiation, not size selection.

It also depends on where the logic runs. This approach works when the CDN or image transformation service that actually serves the image supports Accept-based negotiation. It is difficult to complete this logic only in the API that returns image URLs, because that API does not directly handle the final Accept header of the browser’s image request.

Many image optimization services already support this kind of format switching, so it is often a very practical first step.

A stronger approach: use picture and srcset

If you want to optimize not only format but also size, picture and srcset are much more powerful.

Here is a simple example:

<picture>
  <source
    type="image/avif"
    srcset="
      /images/example-640.avif 640w,
      /images/example-1280.avif 1280w
    "
    sizes="(max-width: 640px) 100vw, 640px"
  >
  <img
    src="/images/example-1280.jpg"
    alt="example"
  >
</picture>
Enter fullscreen mode Exit fullscreen mode

This lets the browser choose both by format and by size.

If you only want a low-effort improvement, Accept-based format switching is a good start. If you want a more complete solution that also handles responsive size selection, picture and srcset are the right tools.

They do require template changes, so the cost is higher. But for thumbnails, product images, and other places where image quality and transfer size matter a lot, the benefit is worth that cost.

WebP is very useful, but it is not magic

WebP is still a very useful format. For photo-like images, it can often provide smaller files than JPEG while keeping good visual quality.

A key advantage of WebP is that it tends to avoid some of the visible artifacts that appear when JPEG quality is pushed too low. Because of that, WebP can often use lower quality values than JPEG.

But that does not mean you should convert everything to WebP without thinking.

A common mistake is to focus too much on quality and forget size. Even with WebP, the better approach is still to secure enough image size first, then lower quality as needed.

Some images need extra care with WebP

WebP is not equally good for every kind of image.

Text images and pixel-art-like images can be tricky. The default settings of cwebp are tuned for photo compression, and that can produce a slightly blurred look. For photos, this is often acceptable. For text and pixel art, it can look like visible degradation.

In some cases, cwebp presets such as text or icon can improve the result. But in a real service, it is not always easy to choose different conversion parameters for every image type.

Because of that, it is often reasonable not to force WebP conversion for images that are already very small, such as text-heavy images or simple pixel art. WebP should be used where it has clear value.

Also, WebP does not behave exactly like JPEG progressive rendering or PNG/GIF interlace behavior. If your service depends on those details, you should verify the difference before rollout.

AVIF is a strong choice for new support

AVIF is another important modern image format. Today, browser support for AVIF is already broad, and the support gap between AVIF and WebP is much smaller than it used to be.

https://caniuse.com/avif

In practice, there are image types where WebP needs extra care, but AVIF tends to show fewer obvious issues. AVIF also works naturally with Accept-based content negotiation.

AVIF is often said to have heavier encoding cost, but when you use a CDN or image optimization service, service users usually do not need to care about that cost directly. What matters more is whether the format works naturally in real browser environments and whether it behaves well for your images.

Because of that, for a new implementation, it is often realistic to keep JPEG or another traditional format as the fallback and support only AVIF as the modern format.

In other words:

  • return AVIF for clients that support it
  • return JPEG or another fallback for the rest

Today, in many cases, it is no longer necessary to support WebP as well just because it is a modern image format.

That said, quality numbers are not directly comparable between JPEG, WebP, and AVIF. You still need to tune them by looking at real output.

The main way to detect WebP support is the Accept header

If you want to return WebP only to supported clients, the main method is to look at the Accept header.

Browsers include supported content types in that header. If image/webp is present, the image delivery side can treat the client as WebP-capable.

The important point is that this logic should not be forced into the API that returns image URLs. API requests often use Accept values for JSON and do not directly reflect the final image request. That is why this method works naturally at the image-serving CDN or transformation layer.

JavaScript detection is not the main path, but it is useful for stricter checks

In some cases, header-based detection is not enough.

JavaScript can detect support more strictly by loading a small test image. This is useful if you want to check not only general WebP support but also features like lossless, alpha, or animation.

Google’s WebP FAQ shows code like this:

https://developers.google.com/speed/webp/faq

function check_webp_feature(feature, callback) {
    var kTestImages = {
        lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
        lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
        alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
        animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"
    };
    var img = new Image();
    img.onload = function () {
        var result = (img.width > 0) && (img.height > 0);
        callback(feature, result);
    };
    img.onerror = function () {
        callback(feature, false);
    };
    img.src = "data:image/webp;base64," + kTestImages[feature];
}
Enter fullscreen mode Exit fullscreen mode

This is helpful when you need very strict compatibility checks. But it is not the main path for normal WebP rollout. In most cases, Accept-based handling plus picture when needed is enough.

There used to be compatibility issues that affected delivery strategy

In the past, image delivery strategy was affected by compatibility issues such as Safari not supporting WebP and Android 4.3 and below being difficult to support reliably. On Android 4.3 and below, even if surface-level detection suggested WebP support, real rendering could still break.

That situation has changed a lot. Safari 14 added WebP support, and the impact of very old Android environments has become much smaller as more services dropped support for TLS versions below 1.2.

Today, this is more of a historical compatibility concern than a core design issue.

You should monitor whether large images are being served by mistake

When people talk about image optimization, they often focus on formats and transformation logic. But in real operation, it is also important to monitor whether oversized images are being served by mistake.

Even with a good design, mistakes happen. Configuration issues or unexpected input images can result in large objects being delivered. That directly affects bandwidth, response time, and CDN cost.

Because of that, you should use metrics from your CDN or monitoring system to observe object size ranges and transfer volume continuously.

In practice, bugs often appear in edge cases. For example, a very tall or very wide image with an unusual aspect ratio can trigger an unexpected resize result and cause a much larger image to be served than intended. You will not catch that by thinking only about format choice. You need to observe the actual object sizes being delivered.

Converting images is easy, but operating an image transformation service is not

If you only want to convert JPEG to WebP on your own machine, that is easy. If you use Homebrew on macOS, you can install the tools like this:

brew install webp
Enter fullscreen mode Exit fullscreen mode

cwebp converts images to WebP, and dwebp converts WebP images to other formats.

But that does not mean building and operating your own image transformation service is easy.

In real services, you will receive images that technically contain problems but are still uploaded by users all the time. For example, JPEG files with broken ICC profiles still appear in real systems. Even then, you need to transform them while keeping color changes as small as possible.

That is why image optimization services and image CDNs are valuable. The hard part is not running a conversion command. The hard part is operating the full system safely with broken files, strange inputs, and edge cases.

Conclusion

If I were designing image delivery for a modern web service, I would think about it like this.

First, image size matters more than quality. You should deliver images that are large enough for the real display environment, especially now that high-density displays are normal.

Second, image URL design should be controlled by the backend. The backend should return grouped candidate URLs for the same image, and the browser should choose one.

Third, if you want a low-effort starting point, use Accept-based format negotiation. If you want a more complete solution, use picture and srcset so that size selection is also handled properly.

Fourth, modern formats are useful, but they are not interchangeable in every case. WebP is still valuable, but some image types need extra care. AVIF is now broadly supported and can be a very practical choice when paired with a traditional fallback format.

Finally, do not stop at conversion logic. You also need monitoring, and you need to think about real operational bugs such as oversized outputs caused by unusual aspect ratios.

Understanding these points and using a CDN or image optimization service well is one of the most practical ways to improve image delivery and user experience today.

Top comments (0)