0 min left

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 to max-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:

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 to max-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:

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 to max-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:

The site builder that gets design

The site builder that gets design

The site builder that gets design