Skip to content

Feature: Serve WebP and AVIF behind Cloudflare via configurable image format option#22

Open
roboes wants to merge 3 commits into
kiboit:masterfrom
roboes:master
Open

Feature: Serve WebP and AVIF behind Cloudflare via configurable image format option#22
roboes wants to merge 3 commits into
kiboit:masterfrom
roboes:master

Conversation

@roboes

@roboes roboes commented May 26, 2026

Copy link
Copy Markdown
Contributor

Description

Phast currently disables modern image format serving entirely when a Cloudflare proxy is detected:

private function proxySupportsAccept(Request $request) {
    return !$request->isCloudflare();
}

This is a safe default - Cloudflare can cache a response and serve it to browsers that didn't send the original Accept header - but it means sites running behind Cloudflare with Polish disabled get no benefit from Phast's image format conversion, even though their server is fully capable of it.

WebP sits at roughly 97% global browser support in 2026, and AVIF has crossed 95% globally, with full support in Chrome, Firefox, Edge, and Safari 16.4+. Leaving these formats unused behind Cloudflare is a significant missed optimisation opportunity.

  • WebP compresses images 25–35% smaller than JPEG and PNG
  • AVIF goes further, achieving file sizes 50% smaller than JPEG and 20–30% smaller than WebP

Solution

This PR introduces a cloudflare-image-format config key under images with three options:

Value Behaviour
off (default) Existing behaviour - no format conversion behind Cloudflare
webp Phast serves WebP behind Cloudflare when the browser supports it
avif Phast serves AVIF (with WebP fallback) behind Cloudflare when supported

The setting is off by default, so existing behaviour is fully preserved unless the user explicitly opts in.

Changes

src/Environment/DefaultConfiguration.php

Adds the new config key to the images array:

'cloudflare-image-format' => 'off',

src/Filters/Image/Image.php

Adds the missing TYPE_AVIF constant alongside the existing TYPE_WEBP and TYPE_JPEG:

const TYPE_AVIF = 'image/avif';

src/Services/Images/Service.php

  • getParams() - replaces the hard Cloudflare gate with config-aware logic. AVIF is attempted first (better compression), WebP is the fallback.
  • makeResponse() - excludes AVIF from Vary: Accept, consistent with the existing PNG exclusion.
  • serverSupportsAvif() - runtime check across three contexts:
    • WordPress 6.5+: delegates to wp_image_editor_supports(), which internally validates both GD and Imagick and abstracts PHP version differences.
    • Non-WordPress + Imagick: checks Imagick::queryFormats() for AVIF (requires libheif).
    • Non-WordPress + GD: requires PHP 8.1+ (imageavif() was introduced in PHP 8.1; PHP 7.x and 8.0 return false cleanly via the PHP_VERSION_ID guard).
  • browserSupportsAvif() / browserSupportsWebp() - updated to use str_contains(), replacing the previous strpos() !== false pattern.

When should users enable this?

Only when Cloudflare Polish is disabled. If Polish is active, Cloudflare already handles format negotiation at the CDN edge - enabling this setting would result in double conversion with no benefit.

If Polish is disabled and Phast is handling image optimisation directly, enabling webp or avif allows Phast to serve modern formats to the ~97% and ~95% of browsers that support them respectively, without relying on the CDN.

Compatibility

Environment WebP AVIF
PHP 7.4 / 8.0 (GD) ❌ silently skipped
PHP 8.1+ (GD) ✅ if imageavif() present
Any PHP + Imagick ✅ if libheif compiled in
WordPress 6.5+ ✅ via wp_image_editor_supports()
Non-Cloudflare requests ✅ unchanged ✅ unchanged

Note: AVIF encoding can be 5-10× slower than WebP on CPU. Results are cached by Phast, so end users are not affected after the first request, but the first encode of large images will take longer.

@apeschar

apeschar commented May 27, 2026

Copy link
Copy Markdown
Member

The issue is that Cloudflare does not respect the Vary header. Therefore it does not make sense to use the Accept header to determine the image format when using Cloudflare. The first response will get cached, and future requests will be responded to with the same image.

See: https://developers.cloudflare.com/cache/advanced-configuration/vary-for-images/

Instead, when using Cloudflare, we should disregard the Accept header entirely. In this case we could decide to always allow WebP compression, as by now it is almost universally supported. Using AVIF would be more aggressive, as it would exclude some users. (Again, browser format detection will not work due to Cloudflare caching not respecting Vary.)

In other words, if Cloudflare is detected, the Accept header should not be read.

@roboes

roboes commented May 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the clarification - the PR has been updated to reflect this.

The Accept header is no longer read for Cloudflare requests, and Vary: Accept is no longer set in that path. Instead, the configured format (webp or avif) is applied unconditionally to all requests when Cloudflare is detected, which aligns with how Cloudflare's cache works.

For non-Cloudflare requests, the existing Accept header negotiation is preserved unchanged.

The avif option also includes a silent server-side fallback to WebP if AVIF encoding is unavailable (e.g. PHP < 8.1 without Imagick/libheif), so no broken output is possible from a misconfigured option.

Please let me know if this satisfies the concern or if further changes are needed.

@roboes roboes changed the title Feature: serve WebP and AVIF behind Cloudflare via configurable image format option Feature: Serve WebP and AVIF behind Cloudflare via configurable image format option May 27, 2026
Comment thread src/Services/Images/Service.php Outdated
@apeschar

Copy link
Copy Markdown
Member

The server check doesn't make sense. A remote API is used to compress images since most servers don't have the necessary tools or old versions, and image compression causes a lot of load. So that should be removed.

The preferred type image/avif needs to get forwarded to the service:

https://github.com/kiboit/phast/blob/master/src/Filters/Image/ImageAPIClient/Filter.php#L98-L100

I need to add AVIF support in the service as well before we can merge this.

@roboes

roboes commented May 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the clarification - all 3 points make sense.

Changes made:

  1. Vary: Accept for AVIF - reverted the makeResponse() change. AVIF is now treated the same as WebP, keeping Vary: Accept so caches don't incorrectly serve AVIF to unsupporting clients.
  2. serverSupportsAvif() removed - since image compression is handled by the remote API rather than locally, the local GD/Imagick/PHP version checks were not applicable and have been removed entirely.
  3. API forwarding - understood that preferredType needs to be forwarded to the image service in ImageAPIClient/Filter.php, and that AVIF support needs to be added on the service side before this can merge. Happy to wait for that and test once it's available.

Please let me know if anything else needs adjusting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants