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.
![](https://framerusercontent.com/images/zbVADHY1tUWhsrZt4TyINU8v3kw.png)
![](https://framerusercontent.com/images/zbVADHY1tUWhsrZt4TyINU8v3kw.png)
![](https://framerusercontent.com/images/zbVADHY1tUWhsrZt4TyINU8v3kw.png)
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.