How to cache responses with dynamic image formats?

Hello, I’m running into the same issue as https://community.cloudflare.com/t/how-to-cache-optimized-images-from-nextjs/407772: I’m using Next.js’ own image optimization, which in summary works by exposing assets on URLs such as /_next/image/*.PNG but then responding with WEBP or AVIF images based on the request’s Accept header value.

The issue is that Cloudflare only caches the first response, with whatever format it happened to contain, it doesn’t care about the Accept header of following requests. This results in AVIF images being served to Edge or Safari, which don’t support AVIF, or unoptimized images being served to Firefox or Chrome, which do support the “latest and greatest” AVIF format.

When the server responds with webp or avif rather than the requested images’ initial format, it appends the accept value to the Vary response header. This led me to believe that Vary for images (https://developers.cloudflare.com/cache/about/vary-for-images/) could be the solution to this issue. However, it didn’t work, and it seems that it’s not really for “images”, rather it’s only compatible with Cloudflare’s own “Images™” image optimization service. I hope my understanding is wrong but if it is correct then I must say that I find Cloudflare’s documentation to be a bit misleading.

Either way, I’m at the end of my wits. Does anybody know how to make Cloudflare play nice with Next’s built-in image optimization? Or am I forced to use Cloudflare’s own image optimization?

You should be able to use Vary for Images without using the Cloudflare for Images product itself - Vary for Images is a cache feature that can standalone.

It’s also worth reading https://blog.cloudflare.com/vary-for-images-serve-the-correct-images-to-the-correct-browsers/ to understand some more about this approach.

If you have enabled Vary - share the API command you used & the response (redact your sensitive information!) here and perhaps share a cURL command that illustrates Vary “not working”. Then I’m sure we can help troubleshoot with you what the issue might be.

1 Like

(re-posting since my formatting was off)
Hi Simon, thank you very much for the quick reply. Here’s the current state of my cache variants configuration:

This request:

"https://api.cloudflare.com/client/v4/zones/MY_ZONE/cache/variants" \
	-H "X-Auth-Email: [email protected]" \ 
	-H "X-Auth-Key: example"

Gets this response:

{
    "result": {
        "editable": true,
        "id": "variants",
        "modified_on": "2022-12-12T11:28:45.545467Z",
        "value": {
            "png": [
                "image/webp",
                "image/png",
                "image/avif"
            ]
        }
    },
    "success": true,
    "errors": [],
    "messages": []
}

However, requests for image assets, which are under a “Cache everything” page rule, don’t seem to care about the Accept header. Disclaimer: I have fully purged the cache before each experiment with cache variants.

Here’s a CURL showing “cache variants not working”:

curl 'https://mydomain.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmyimage.927148e0.png&w=640&q=75' \
> -X 'GET' \
> -H 'Accept: image/webp,video/*;q=0.8,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5' \
> -H 'Cache-Control: no-cache' \
> -H 'Pragma: no-cache' \
> -H 'Accept-Encoding: gzip, deflate' \
> -H 'Accept-Language: en-US,en;q=0.90' \
> -H 'Connection: Keep-Alive'

Which gets these response headers:

content-type: image/avif
age: 391
cache-control: public, max-age=315360000
content-disposition: inline; filename="myimage.avif"
content-encoding: gzip
expires: Sun, 12 Dec 2032 14:50:21 GMT
last-modified: Thu, 15 Dec 2022 14:43:50 GMT
strict-transport-security: max-age=15552000; includeSubDomains
vary: Accept-Encoding, Accept
cf-cache-status: HIT
access-control-allow-credentials: true
referrer-policy: same-origin
x-content-type-options: nosniff
x-nextjs-cache: MISS
x-xss-protection: 1; mode=block
server: cloudflare

Let me DM you to get the domain.

Francesco and I chatted in DM and we have a ticket open with the support team to look at this.

1 Like

Ticket outcome: Vary for images reads the file extension from the path. Upon close inspection, Next.js’ default loader for images uses /image as the path and keeps the actual image name and extension in the query part of the URL.

The Cloudflare team is aware of this and will be working on updating their documentation.

Meanwhile I could either stop caching image URLs (not great), try to change Next’s Image URL structure (not sure how at the moment) or more likely start using Cloudflare Images via a custom loader.

Thanks again to Simon and the Cloudflare team!

1 Like

Thanks for sharing what we learnt here @francesco.mastellone - a very warm welcome to the Cloudflare community :slight_smile: .

Based on that, I’ve submitted some updates to the docs here:

1 Like