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.

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.

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: