Problem/Motivation

When the webp module is enabled alongside Responsive Image Preload, the rendered <picture> element serves WebP derivatives (via webp_preprocess_responsive_image()), but the preload <link rel="preload" as="image"> tags generated by this module still point at the original PNG/JPEG URLs from ImageStyle::buildUrl().

That mismatch defeats the purpose of preloading for LCP: the browser may preload a format it will not use, while the actual LCP candidate is a WebP URL the preload hints never mention.

Example (observed on a Drupal 11.4.4 site with both modules enabled):

  • <picture> sources: .../style/foo.webp?itok=... with type="image/webp"
  • Preload links: .../style/foo.png?itok=... (no type="image/webp")

This was reported previously in #3229962: Supports webp module. That issue was marked Fixed in 2021 with reference to #3232744, which addressed the 1.x codebase. The problem was reopened in May 2025 for 2.0.x-dev because responsive_image_preload 2.0.x was refactored and does not include WebP handling in PreloadGenerator. As of 2.0.1, preloads still emit non-WebP URLs when webp is enabled.

Steps to reproduce

  1. Enable responsive_image_preload and webp.
  2. Configure a responsive image field with generate_preloads: true on its formatter settings.
  3. View a page that renders a responsive hero (or any) image.
  4. Inspect HTML: compare <picture> <source type="image/webp"> URLs with <link rel="preload" as="image"> imagesrcset values.
  5. Expected: Preload URLs match the WebP URLs the browser will fetch (with type="image/webp" on the preload link).
  6. Actual: Preload URLs remain .png / .jpg / .jpeg while the picture element prefers WebP.

Environment tested: Drupal 11.4.4, PHP 8.3, responsive_image_preload 2.0.1, webp 1.x.

Proposed resolution

When the webp module is present:

  1. Optionally inject the webp.webp service into PreloadGenerator (e.g. @?webp.webp in responsive_image_preload.services.yml).
  2. After building imagesrcset, convert URLs with Webp::getWebpSrcset() — the same helper webp uses when altering responsive images — so preload URLs stay in sync with <picture> output (including preserved ?itok= tokens).
  3. Add type="image/webp" on preload <link> elements.
  4. Emit WebP-only preloads when WebP is available (do not also preload PNG/JPEG duplicates the browser will not use for LCP).

When webp is not installed, behavior should remain unchanged (backward compatible).

Credits and prior work

Thank you to everyone who already investigated this on #3229962:

  • ahmad smhan — original report and early patches
  • luke.leber — follow-up ensuring ?itok= is preserved in WebP URLs
  • nicodh — May 2025 patches for 2.0.x, including the approach of skipping JPG/PNG preloads when WebP is present (comment #14)

Thanks also to joncjordan for discovering that the previously reported issue had been mistakenly closed and that WebP images were still not getting preloaded.

This merge request is informed by that thread rather than reinventing the problem. I chose a new, smaller patch for 2.0.x because:

  • 2.0.x API: PreloadGenerator was rewritten; 1.x / early patches do not apply cleanly to 2.0.1.
  • DRY with webp: Instead of duplicating preg_replace() logic, call webp.webp → getWebpSrcset() so preload URLs always match what webp_preprocess_responsive_image() produces.
  • Optional dependency: @?webp.webp keeps a soft dependency on webp (no hard module requirement; no \Drupal:: static service calls in the generator).
  • LCP-focused behavior: Same intent as nicodh’s “without jpg/png preload if webp is present” variant — avoid double-downloading formats the browser will not use.

Remaining tasks

  • [x] Confirm reproduction on 2.0.x with webp enabled
  • [ ] Review merge request against 2.0.x
  • [ ] Consider automated/kernel test coverage if feasible
  • [ ] Merge and tag a release

User interface changes

None. Markup change only: preload <link> tags use WebP URLs and type="image/webp" when the webp module is enabled.

API changes

None for public APIs. PreloadGenerator constructor gains an optional $webp argument (injected via @?webp.webp).

Data model changes

None.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

btully created an issue. See original summary.

btully’s picture

Issue summary: View changes
Status: Active » Needs review

Merge request: https://git.drupalcode.org/project/responsive_image_preload/-/merge_requ...
Ready for review. Patch also attached in the initial report if helpful.

btully’s picture

Issue summary: View changes