How Framer Integrated AVIF for Faster, Smaller, and Sharper Images



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.

Step into the future of design
Join thousands using Framer to build high-performing websites fast.