How Framer Integrated AVIF for Faster, Smaller, and Sharper Images
This article is part of a series that explores the technologies that keep your Framer site fast and operational.
In May 2024, we shipped AVIF support. All images on Framer are now served as AVIF, which makes them ~20% smaller. However, integrating this format was challenging, partly because converting images to AVIF is slow. Here’s how we solved this.
Challenge: AVIF Encoding Is Slow
At Framer, we optimize every image on the first request. The optimized image is then cached on a CDN.
This is a common approach, and it works well, but it comes with a drawback. Because the first uncached request has to convert and resize the image, it takes longer than subsequent ones. With WebP, “longer” is noticeable but acceptable: in our infrastructure, WebP conversion typically adds 100-300 milliseconds. However, with AVIF, this grows to 1-2 seconds.
Sidenote: “1-2 seconds? Isn’t that still fast?” — It’s fast, but only outside computer contexts. Research shows that users start feeling things aren’t instant after just 100 milliseconds.
Framer’s cache hit for images is ~98%. If we ignored this issue and switched to AVIF, every 50th image would take several seconds to load. We felt this was unacceptable, so Framer’s Jacob came up with, and Piotr shipped a clever strategy that avoided that – the stale-while-revalidate
header.
Solution: Stale-While-Revalidate
stale-while-revalidate
is a caching setting. It’s a parameter in the Cache-Control
header, and it tells CDNs how long they can keep serving the image after it expires:
Cache-Control: max-age=3600, stale-while-revalidate=60
↑ how long a file can be cached for
↑ how long a CDN can keep serving
the file after max-age expires
Here’s how we used it to make sure AVIF never makes image responses slow:
1. First Request: WebP
On the first request, we serve the image as WebP, not as AVIF.
We also set the
Cache-Control
header tomax-age=0, stale-while-revalidate=31536000
2. Immediate Expiry
Because
max-age
is set to 0, the WebP image expires immediately. This prompts the CDN to forward the second request to us.
3. Second Request: AVIF
When the second request arrives, we serve the image as AVIF.
Responding to the second request can take several seconds because AVIF conversion is slow. But thanks to
stale-while-revalidate
, our CDN (CloudFront) keeps serving the WebP image until the conversion is complete.We recognize the second request from the first by the
If-None-Match
header. Only the second request has it.
When the AVIF image is ready, we return it with
Cache-Control: max-age=31536000
. This allows the CDN to cache and serve it for a long time.
This works surprisingly well – and, best of all, allows us to keep our infrastructure simple. If not for this trick, we’d need to implement a separate queueing system for converting images in the background. But with this trick, we don’t need to – so there’s no extra complexity and no extra bugs.
When We Don’t Use AVIF
AVIF is now the default format for most images. But there are still a few scenarios where we continue to use WebP:
Lossless Images: AVIF’s lossless compression is not truly lossless and also worse than WebP’s. So, for lossless images, we keep using WebP.
Animated Images: The library we rely on for image optimization doesn’t support animated AVIF images, so we continue using WebP for these.
Challenge: AVIF Encoding Is Slow
At Framer, we optimize every image on the first request. The optimized image is then cached on a CDN.
This is a common approach, and it works well, but it comes with a drawback. Because the first uncached request has to convert and resize the image, it takes longer than subsequent ones. With WebP, “longer” is noticeable but acceptable: in our infrastructure, WebP conversion typically adds 100-300 milliseconds. However, with AVIF, this grows to 1-2 seconds.
Sidenote: “1-2 seconds? Isn’t that still fast?” — It’s fast, but only outside computer contexts. Research shows that users start feeling things aren’t instant after just 100 milliseconds.
Framer’s cache hit for images is ~98%. If we ignored this issue and switched to AVIF, every 50th image would take several seconds to load. We felt this was unacceptable, so Framer’s Jacob came up with, and Piotr shipped a clever strategy that avoided that – the stale-while-revalidate
header.
Solution: Stale-While-Revalidate
stale-while-revalidate
is a caching setting. It’s a parameter in the Cache-Control
header, and it tells CDNs how long they can keep serving the image after it expires:
Cache-Control: max-age=3600, stale-while-revalidate=60
↑ how long a file can be cached for
↑ how long a CDN can keep serving
the file after max-age expires
Here’s how we used it to make sure AVIF never makes image responses slow:
1. First Request: WebP
On the first request, we serve the image as WebP, not as AVIF.
We also set the
Cache-Control
header tomax-age=0, stale-while-revalidate=31536000
2. Immediate Expiry
Because
max-age
is set to 0, the WebP image expires immediately. This prompts the CDN to forward the second request to us.
3. Second Request: AVIF
When the second request arrives, we serve the image as AVIF.
Responding to the second request can take several seconds because AVIF conversion is slow. But thanks to
stale-while-revalidate
, our CDN (CloudFront) keeps serving the WebP image until the conversion is complete.We recognize the second request from the first by the
If-None-Match
header. Only the second request has it.
When the AVIF image is ready, we return it with
Cache-Control: max-age=31536000
. This allows the CDN to cache and serve it for a long time.
This works surprisingly well – and, best of all, allows us to keep our infrastructure simple. If not for this trick, we’d need to implement a separate queueing system for converting images in the background. But with this trick, we don’t need to – so there’s no extra complexity and no extra bugs.
When We Don’t Use AVIF
AVIF is now the default format for most images. But there are still a few scenarios where we continue to use WebP:
Lossless Images: AVIF’s lossless compression is not truly lossless and also worse than WebP’s. So, for lossless images, we keep using WebP.
Animated Images: The library we rely on for image optimization doesn’t support animated AVIF images, so we continue using WebP for these.
Challenge: AVIF Encoding Is Slow
At Framer, we optimize every image on the first request. The optimized image is then cached on a CDN.
This is a common approach, and it works well, but it comes with a drawback. Because the first uncached request has to convert and resize the image, it takes longer than subsequent ones. With WebP, “longer” is noticeable but acceptable: in our infrastructure, WebP conversion typically adds 100-300 milliseconds. However, with AVIF, this grows to 1-2 seconds.
Sidenote: “1-2 seconds? Isn’t that still fast?” — It’s fast, but only outside computer contexts. Research shows that users start feeling things aren’t instant after just 100 milliseconds.
Framer’s cache hit for images is ~98%. If we ignored this issue and switched to AVIF, every 50th image would take several seconds to load. We felt this was unacceptable, so Framer’s Jacob came up with, and Piotr shipped a clever strategy that avoided that – the stale-while-revalidate
header.
Solution: Stale-While-Revalidate
stale-while-revalidate
is a caching setting. It’s a parameter in the Cache-Control
header, and it tells CDNs how long they can keep serving the image after it expires:
Cache-Control: max-age=3600, stale-while-revalidate=60
↑ how long a file can be cached for
↑ how long a CDN can keep serving
the file after max-age expires
Here’s how we used it to make sure AVIF never makes image responses slow:
1. First Request: WebP
On the first request, we serve the image as WebP, not as AVIF.
We also set the
Cache-Control
header tomax-age=0, stale-while-revalidate=31536000
2. Immediate Expiry
Because
max-age
is set to 0, the WebP image expires immediately. This prompts the CDN to forward the second request to us.
3. Second Request: AVIF
When the second request arrives, we serve the image as AVIF.
Responding to the second request can take several seconds because AVIF conversion is slow. But thanks to
stale-while-revalidate
, our CDN (CloudFront) keeps serving the WebP image until the conversion is complete.We recognize the second request from the first by the
If-None-Match
header. Only the second request has it.
When the AVIF image is ready, we return it with
Cache-Control: max-age=31536000
. This allows the CDN to cache and serve it for a long time.
This works surprisingly well – and, best of all, allows us to keep our infrastructure simple. If not for this trick, we’d need to implement a separate queueing system for converting images in the background. But with this trick, we don’t need to – so there’s no extra complexity and no extra bugs.
When We Don’t Use AVIF
AVIF is now the default format for most images. But there are still a few scenarios where we continue to use WebP:
Lossless Images: AVIF’s lossless compression is not truly lossless and also worse than WebP’s. So, for lossless images, we keep using WebP.
Animated Images: The library we rely on for image optimization doesn’t support animated AVIF images, so we continue using WebP for these.
TLDR
TLDR
Framer now supports AVIF, reducing image sizes by ~20% compared to WebP. But AVIF is not perfect.
AVIF encoding is slow, so we implemented a clever solution using
stale-while-revalidate
. Initial image requests serve WebP for fast loading; subsequent requests trigger AVIF conversion while still serving WebP.AVIF is sometimes worse than WebP, so we still use WebP for some images.
Framer now supports AVIF, reducing image sizes by ~20% compared to WebP. But AVIF is not perfect.
AVIF encoding is slow, so we implemented a clever solution using
stale-while-revalidate
. Initial image requests serve WebP for fast loading; subsequent requests trigger AVIF conversion while still serving WebP.AVIF is sometimes worse than WebP, so we still use WebP for some images.
Framer now supports AVIF, reducing image sizes by ~20% compared to WebP. But AVIF is not perfect.
AVIF encoding is slow, so we implemented a clever solution using
stale-while-revalidate
. Initial image requests serve WebP for fast loading; subsequent requests trigger AVIF conversion while still serving WebP.AVIF is sometimes worse than WebP, so we still use WebP for some images.