Making assets fly
and images a breeze with Image CDNs
Alistair Shepherd - Front-End Developer
SeriesEight
Images on the web
<img
src="/our-image.jpg"
alt="our image"
>
Images change size depending on screen width
HTML Responsive Images
<img
src="/wp-content/uploads/our-image.jpg"
srcset="
/wp-content/uploads/our-image-300x200.jpg 300w,
/wp-content/uploads/our-image-768x512.jpg 768w,
/wp-content/uploads/our-image-1024x683.jpg 1024w,
/wp-content/uploads/our-image-1536x1024.jpg 1536w
"
sizes="(min-width: 800px) 768px, 100vw"
...
>
Optimisation and Image Format
Size | |
---|---|
Source JPEG | 300kB |
Optimised JPEG | 60kB |
WebP 🙂 | 36kB |
AVIF * 😀 | 28kB |
JPEG XL * 😍 | 24kB |
Size | Chrome | Firefox | Safari | |
---|---|---|---|---|
Source JPEG | 300kB | yes | yes | yes |
Optimised JPEG | 60kB | yes | yes | yes |
WebP 🙂 | 36kB | yes | yes | recent |
AVIF * 😀 | 28kB | yes | recent | no |
JPEG XL * 😍 | 24kB | no | no | no |
Size | |
---|---|
Source JPEG | 300kB |
Optimised JPEG | 60kB |
WebP 🙂 | 36kB |
AVIF * 😀 | 28kB |
JPEG XL * 😍 | 24kB |
Optimisation and Image Format
Hi DPI displays
<picture>
<source
media="(-webkit-min-device-pixel-ratio: 1.5)"
srcset="https://example.com/images/our-image_width1200_quality40.jpg 1200w"
sizes="...">
<img
srcset="https://example.com/images/our-image_width600_quality80.jpg 600w"
sizes="..."
alt="">
</picture>
iPhone 12 Pro (right) screen:
- Screen width: 1170 pixels
- 'Viewport' width: 390 pixels
- Device Pixel Ratio of 3
The browser could decide to load an image with resolution 3x larger than it's displayed - 9x increase in file size!!!
We can mitigate increase in file size by increasing the image compression without visible effect.
Lazy Loading and Decoding
- "loading" attribute set to "lazy" or "eager" depending on position on page
- decoding="async" when images shouldn't delay text rendering
<img
loading="lazy"
decoding="async"
>
<picture>
<source
type="image/avif"
media="(-webkit-min-device-pixel-ratio: 1.5)"
srcset="/assets/image/otter-300-q40.avif 300w, /assets/image/otter-450-q40.avif 450w, /assets/image/otter-600-q40.avif 600w,
/assets/image/otter-800-q40.avif 800w, /assets/image/otter-1000-q40.avif 1000w, /assets/image/otter-1200-q40.avif 1200"
sizes="(min-width: 87rem) 40rem, (min-width: 768px) calc(50vw - 4rem), calc(100vw - 2rem)">
<source
type="image/avif"
srcset="/assets/image/otter-300-q80.avif 300w, /assets/image/otter-450-q80.avif 450w, /assets/image/otter-600-q80.avif 600w,
/assets/image/otter-800-q80.avif 800w, /assets/image/otter-1000-q80.avif 1000w, /assets/image/otter-1200-q80.avif 1200"
sizes="(min-width: 87rem) 40rem, (min-width: 768px) calc(50vw - 4rem), calc(100vw - 2rem)">
<source
type="image/webp"
media="(-webkit-min-device-pixel-ratio: 1.5)"
srcset="/assets/image/otter-300-q40.webp 300w, /assets/image/otter-450-q40.webp 450w, /assets/image/otter-600-q40.webp 600w,
/assets/image/otter-800-q40.webp 800w, /assets/image/otter-1000-q40.webp 1000w, /assets/image/otter-1200-q40.webp 1200"
sizes="(min-width: 87rem) 40rem, (min-width: 768px) calc(50vw - 4rem), calc(100vw - 2rem)">
<source
type="image/webp"
srcset="/assets/image/otter-300-q80.webp 300w, /assets/image/otter-450-q80.webp 450w, /assets/image/otter-600-q80.webp 600w,
/assets/image/otter-800-q80.webp 800w, /assets/image/otter-1000-q80.webp 1000w, /assets/image/otter-1200-q80.webp 1200"
sizes="(min-width: 87rem) 40rem, (min-width: 768px) calc(50vw - 4rem), calc(100vw - 2rem)">
<source
media="(-webkit-min-device-pixel-ratio: 1.5)"
srcset="/assets/image/otter-300-q40.jpg 300w, /assets/image/otter-450-q40.jpg 450w, /assets/image/otter-600-q40.jpg 600w,
/assets/image/otter-800-q40.jpg 800w, /assets/image/otter-1000-q40.jpg 1000w, /assets/image/otter-1200-q40.jpg 1200"
sizes="(min-width: 87rem) 40rem, (min-width: 768px) calc(50vw - 4rem), calc(100vw - 2rem)">
<img
alt="otter standing on a log looking out majestically"
src="/assets/image/otter-1200-q80.jpg"
srcset="/assets/image/otter-300-q80.jpg 300w, /assets/image/otter-450-q80.jpg 450w,
/assets/image/otter-600-q80.jpg 600w, /assets/image/otter-800-q80.jpg 800w,
/assets/image/otter-1000-q80.jpg 1000w, /assets/image/otter-1200-q80.jpg 1200"
sizes="(min-width: 87rem) 40rem, (min-width: 768px) calc(50vw - 4rem), calc(100vw - 2rem)"
width="1200"
height="784"
loading="lazy"
decoding="async"
>
</picture>
That's a lot going on!
Image processing wishlist
- Convert to several different formats, update to support new ones like AVIF and JPEG XL for browsers that support them
- Resize images to many different sizes for responsive devices
- Handle differing quality levels depending on density
- Bonus: Perform quality analysis comparing image sizes between different formats
- Optimise images and compress where possible
To display a few images on a webpage!
Common Image systems
Manual
Custom code and process
Package or component in CMS, template or build tooling
- Jpeg, Webp, AVIF
- Many image sizes
- Quality levels for 1x and 2x dpi
Provided or custom tooling
- May not prioritise compression, resizing or may not convert formats
- Can be expensive and time-consuming for developers to maintain
- May use large amounts of resources manipulating images on upload/build, storing every image
HTTP Archive data
Source and image credit:
HTTP Archive Web Almanac, 2021 - Media Chapter
Image issues in the wild
- Fresh redesign and rebuild
- Client-managed content
- Jamstack build tool with headless WordPress CMS
- Very visual featuring lots of images
- Was in a reasonably rural area so we wanted to ensure performance was very good
- Around 20 artists
- Up to 40-50 pieces each
- Hundreds of studio-quality images
"The art on the new website has to be as high quality as possible"
I built my own image process
- Converted to Webp, was easy to add AVIF in future
- Resize images to many different sizes
- Handle differing quality levels depending on density
- Bonus: Perform quality analysis comparing image sizes between different formats
✅
✅
❎
❎
Results
- Created additional costs of servers, services and storage
- Created technical debt
- Increased time for site updates to go live from ~30 seconds to almost 10 minutes
- At first it was okay, improved performance slightly
Image
CDNs to
the rescue
What is an Image CDN?
- Content Delivery Network (CDN) specialising in image processing
- Sits between the web server of your site and the user, 'transforming' images on the fly by changing image URLs
- Third-party services selling solutions to make dev experience of working with images easier and improving performance
and many more
Web Server
HTML
CSS
JS
Images
Images
User's browser
Optimised
Source
Web Server
User's browser
HTML
CSS
JS
Images
Images
Web Server
Optimised
Source
Web Server
User's browser
HTML
CSS
JS
Images
Images
Media Storage
How do we use them?
https://example.imagecdn.com
Start with our image
CDN domain
https://example.imagecdn.com
/https://oursite.com
/src/assets
/example.jpg
Add the URL to our
source image
https://example.imagecdn.com
/example.jpg
Many CDN providers allow
you to make presets/aliases
https://example.imagecdn.com
/example.jpg
?w=600&h=400
Add parameters to
'transform' the image
https://example.imagecdn.com/fetch
/example.jpg
/w_600,h_400
Different CDNs have different formats,
but work on the same concepts
- Handled separately from your main site or build
- Managed by image experts
- Automatic format detection
.../w_300,h_600,c_thumb,g_auto/...
Tall banner with automatic gravity
.../w_1000,h_400,c_fill,g_north,
e_colorize:30,co_rgb:dd14d1/...
Wide banner anchored to top, coloured pink
Implementing
Image CDNs
<picture>
<source
media="(-webkit-min-device-pixel-ratio: 1.5)"
srcset="
https://example.imagecdn.com/images/w_300,q_40/otter.jpg 300w,
https://example.imagecdn.com/images/w_450,q_40/otter.jpg 450w,
https://example.imagecdn.com/images/w_600,q_40/otter.jpg 600w,
https://example.imagecdn.com/images/w_800,q_40/otter.jpg 800w,
https://example.imagecdn.com/images/w_1000,q_40/otter.jpg 1000w,
https://example.imagecdn.com/images/w_1200,q_40/otter.jpg 1200w"
sizes="(min-width: 87rem) 40rem, (min-width: 768px) calc(50vw - 4rem), calc(100vw - 2rem)">
<img
alt="otter standing on a log looking out majestically"
src="https://example.imagecdn.com/images/w_800,q_80/otter.jpg"
srcset="
https://example.imagecdn.com/images/w_300,q_80/otter.jpg 300w,
https://example.imagecdn.com/images/w_450,q_80/otter.jpg 450w,
https://example.imagecdn.com/images/w_600,q_80/otter.jpg 600w,
https://example.imagecdn.com/images/w_800,q_80/otter.jpg 800w,
https://example.imagecdn.com/images/w_1000,q_80/otter.jpg 1000w,
https://example.imagecdn.com/images/w_1200,q_80/otter.jpg 1200w"
sizes="(min-width: 87rem) 40rem, (min-width: 768px) calc(50vw - 4rem), calc(100vw - 2rem)"
width="1200"
height="784"
loading="lazy"
decoding="async"
>
</picture>
Cloudinary WordPress plugin
CMS' plugins or platform integrations
Imagekit.io integrations
<img src="{{ image | img_url: '800x300', crop: 'center' }}" ...>
Shopify Image CDN
{% image {
name: 'otter.jpg'
alt: 'an otter standing on a log looking majestic'
srcset: [300, 450, 600, 800, 1000, 1200]
sizes: '100vw'
width: 1200
height: 800
loading: 'lazy'
class: 'image-class mb-xl'
} %}
accud.io/imagecdn-11ty
Templating language shortcodes
React Component
import Image from 'components/Image'
export default function () {
return (
<Image
src="otter.jpg"
alt="an otter standing on a log looking majestic"
srcset={[300, 450, 600, 800, 1000, 1200]}
sizes="100vw"
width="1200"
height="800"
loading="lazy"
className="image-class mb-xl"
/>
)
}
accud.io/imagecdn-react
PHP Function
<?php
echo image([
'image' => 'otter.jpg',
'alt' => 'an otter standing on a log looking majestic',
'srcset' => [300, 450, 600, 800, 1000, 1200],
'sizes' => '100vw',
'width' => 1200,
'height' => 800,
'loading' => 'lazy',
'class' => 'image-class'
]);
accud.io/imagecdn-php
Image CDNs make images
easier—everywhere!
Moving to using an Image CDN
What we did
- Used the CloudImage Image CDN
- Set up a custom code solution using a Liquid shortcode like in previous example
- Removed custom toolchain
Lighthouse performance score improved by ~19 points
Increased speed of development
Reduced site build time by 80%
Chrome Image bytes
decreased by 27%
No free lunch!
Cost
- As an external service, there is a cost implication
- Many have a generous free tier, small site might not cost anything
- Larger sites will cost, pricing model depends on the provider
- Unless you have hundreds of thousands of images, likely cheaper than developers maintaining image processing or faffing with plugins
- Can be offset by reduced server load, storage requirements or build infrastructure
- Disable CDN for local development
- Offload images to cloud storage
- Push images to your site/secret dev URL when needed
Local dev
For very high performing sites different origin has an impact
Solutions for nginx and Netlify
location /cdn/ {
proxy_pass https://example.imagecdn.com/;
}
[[redirects]]
from = '/cdn/:image'
to = 'https://example.imagecdn.com/:image'
status = 200
The scores are in...
Improve the performance of images on your websites
Make working with images easier during development
Reduce resources required for server/build
Try an Image CDN in your next project!
Thank You!
These slides, my sources and helpful links on image CDNs available at accud.io/imagecdns
On twitter and most social media @accudio
My website and blog posts alistairshepherd.uk
Making assets fly and images a breeze with Image CDNs
By Alistair Shepherd
Making assets fly and images a breeze with Image CDNs
- 402