Making assets fly on the Jamstack 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
Source jpeg - 300kB
Webp - 18kB 🙂
JPEG XL* - 12kB 😍
Optimised jpeg - 30kB 😮
AVIF - 14kB 😀
Hi DPI images
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
<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>
<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>
To display a few images on a webpage!
Images on the Jamstack
Manual
Custom code and process
Package or component like
next/image or 11ty-image
- Jpeg, Webp, AVIF
- Many image sizes
- Quality levels for 1x and 2x dpi
Image processing wishlist
- Convert to several different formats, update to support new ones like AVIF and JPEG XL
- Resize images to many different sizes
- Handle differing quality levels depending on density
- Bonus: Perform quality analysis comparing image sizes between different formats
> eleventy-base-blog@5.0.2 build
> eleventy
[...]
Copied 957 files / Wrote 19 files in 188.07 seconds (9898.4ms each, v0.12.1)
3 minutes, 8 seconds!
> eleventy-base-blog@5.0.2 build
> eleventy
[...]
Copied 2 files / Wrote 19 files in 0.59 seconds (31.1ms each, v0.12.1)
Jamstack image issues in the wild
- New site design and build
- Client-managed content
- Jamstack using 11ty and headless WordPress
- 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 process using Sharp
- 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
✅
✅
❎
❎
Builds went from ~30 seconds to almost
12 minutes
Image
CDNs to
the rescue
What is an Image CDN?
- Content Delivery Network (CDN) specialising in image processing
- Sits between your server and the user 'transforming' images on the fly by customising URLs
- Third-party services selling solutions to make dev experience of working with images easier and improving performance
and many more
User's browser
Server
HTML
CSS
JS
Images
Images
User's browser
Server
HTML
CSS
JS
Images
Image CDN
Optimised
Images
Source
Server
User's browser
Server
HTML
CSS
JS
Images
Image CDN
Optimised
Images
Source
Media Storage
How do we use them?
https://example.imagecdn.com
Start with our image
CDN domain
https://example.imagecdn.com
/https://oursite.com
/images/example.jpg
Add the URL to our
source image
https://example.imagecdn.com
/images/example.jpg
Many CDN providers allow
you to make presets/aliases
https://example.imagecdn.com
/images/example.jpg
?w=600&h=400
Add parameters to
'transform' the image
https://example.imagecdn.com/fetch
/images/example.jpg
/w_600,h_400
Different CDNs have different formats,
but work on the same concepts
- Handled outwith site 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/...
Wide banner anchored to top
Combining images and text
Circles and rounded corners
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>
{% 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
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
Image CDNs makes working with images as easy as formatting a URL
Not just Jamstack!
Moving to using an Image CDN
Build times
reduced by 80%
LCP improved by 19%
Chrome Image bytes
decreased by 27%
Multiple image sources combined into a single endpoint
cms.1896gallery.com/wp-content/uploads/2019/08/artwork-img.jpg
1896gallery.com/assets/internal-graphic.jpg
Image
CDN
1896gallery.imagecdn.com/wp/artwork-img.jpg
1896gallery.imagecdn.com/assets/internal-graphic.jpg
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
- Can be offset by reduced build minutes if paying for build infrastructure, eg Netlify
- Disable CDN for local dev
- Push images to secret public dev URL
For very high performing sites different origin has an impact
Easy solution with Netlify
[[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 Jamstack build times
Try an Image CDN in your next project!
Thank You!
These slides, my sources and helpful links on image CDNs available at accud.io/jamstack-imagecdns
On twitter and most social media @accudio
My website and blog posts alistairshepherd.uk
Making assets fly on the Jamstack with Image CDNs
By Alistair Shepherd
Making assets fly on the Jamstack with Image CDNs
- 598