How to use Next.js Image Optimization with Cloudflare
Caching webp images with a fallback using Next.js & a CDN like Cloudflare
Recently I noticed Cloudflare didn’t cache any of our images optimized with Next.js. After a quick fix using Cloudflare’s Page Rules, all images were served properly from the CDN cache. It worked perfectly fine, but… for browsers without webp support images did not load at all. Although no image loads faster than no image, we’d like to have our images working on all browser 😅
Here’s the problem: the next/image component uses a single url to serve webp images and a fallback for browsers without webp support. Cloudflare however can only cache a single image for a specific url, hence the loading problem if webp was cached, but is not supported by the browser.
So how can we serve webp with a fallback using Cloudflare?
Note: You can most likely use a similar setup for other CDNs than Cloudflare.
How to setup Next.js and Cloudflare to cache webp with a fallback
We want…
- … Cloudflare to cache all images optimized by Next.js…
- … and serve a fallback if our browser lacks webp support
Therefore we need to serve webp images and their fallback on two separate URLs (unlike Next.js does by default). We can easily achieve this by appending some additional query parameter to the URL in case the http accept header does not allow webp. Next.js will just ignore the additional parameter, and Cloudflare will cache non-webp images on the new URL.
The idea is simple: Append an additional query parameter to serve webp images and their fallback on separate URLs
Step 1 | Add Page Rule to Cache Next.js Images
Add a Page Rule for the Next.js image route with the following settings:
URL: [your domain]/_next/image?*
Cache Level: Cache Everything
Edge Cache TTL: [appropriate TTL for your use case]
Cloudflare now caches all images optimized with Next.js, but can’t properly handle browsers that don’t support webp. We’ll fix that in the next step.
Step 2 | Add Transform Rule to Cache Fallback on separate URL
Under Transform Rules create an URL Rewrite Rule. Click on “Edit expression” to use the Expression Editor instead of the builder. Then use the following settings:
Expression Editor
(http.request.uri.path eq "/_next/image" and not any(http.request.headers["accept"][*] contains "image/webp"))
Rewrite Query to…
concat(http.request.uri.query, "&webp=false")
So what does it do? The expression makes sure that the rule is only applied on the next/image route and if the browser supports webp:
http.request.uri.path eq “/_next/image”
Only apply this rule to the next/image routenot any(http.request.headers[“accept”][*] contains “image/webp”)
Only apply this rule if the browser doesn’t support webp
The dynamic query string rewrite just appends our custom query parameter to the request’s query string. You can name the parameter and its value whatever you want (at least as long as it doesn’t collide with Next.js parameters 😉).
With a setup like this all requests from browsers without webp support will be rewritten to a separate URL. Next.js will generate a non-webp image on the first request. Cloudflare will cache the image for the rewritten url and serve it for subsequent requests. Browsers with webp support will still receive the webp image cached at the original URL.