Serve images as WebP across all listing and content templates #2915
fabiograsso
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Summary
Blowfish currently serves featured and inline images without converting them to WebP in any of its core templates, even though Hugo's image pipeline has supported WebP conversion natively since v0.83.0 during build time.
This causes significant and unnecessary performance penalties flagged by PageSpeed Insights under "Serve images in next-gen formats".
Affected files and proposed changes
1.
layouts/partials/article-link/simple.htmlCurrent code:
{{ if not $featuredURL }} {{ with $featured }} {{ $featuredURL = .RelPermalink }} {{ if not (or $disableImageOptimization (eq .MediaType.SubType "svg")) }} {{ $featuredURL = (.Resize "600x").RelPermalink }} {{ end }} {{ end }} {{ end }}The image is resized but served in its original format (PNG, JPG, etc.).
Proposed change:
{{ if not $featuredURL }} {{ with $featured }} {{ $featuredURL = .RelPermalink }} {{ if not (or $disableImageOptimization (eq .MediaType.SubType "svg")) }} {{ $featuredURL = (.Resize "600x webp q80").RelPermalink }} {{ end }} {{ end }} {{ end }}One-liner fix — acceptable given WebP's 97%+ browser support in 2025.
2.
layouts/partials/article-link/card.htmlCurrent code (from official Blowfish docs):
{{ with .Resize "600x" }} <div class="w-full thumbnail_card nozoom" style="background-image:url({{ .RelPermalink }});"></div> {{ end }}Proposed change:
{{ $img := .Resize "600x webp q80" }} <div class="w-full thumbnail_card nozoom" style="background-image:url({{ $img.RelPermalink }});"></div>Note:
background-imagein CSS does not support<picture>, so the one-liner is the correct approach here.3.
layouts/partials/hero/basic.htmlandhero/big.htmlCurrent behavior: the hero templates find the featured image via
Resources.GetMatchand render it without any format conversion or responsive srcset. The image is served at its original format and size regardless of the device.Proposed change for
hero/basic.html— generate responsive WebP srcset:{{ if and (not $disableImageOptimization) (not $isSVG) }} {{ $small := $featured.Resize "360x webp q80" }} {{ $medium := $featured.Resize "600x webp q80" }} {{ $large := $featured.Resize "1280x webp q80" }} <div class="overflow-hidden h-36 md:h-56 lg:h-72"> <img srcset="{{ $small.RelPermalink }} 360w, {{ $medium.RelPermalink }} 600w, {{ $large.RelPermalink }} 1280w" sizes="100vw" src="{{ $medium.RelPermalink }}" role="presentation" loading="eager" decoding="async" fetchpriority="high" class="w-full h-full nozoom object-cover"> </div> {{ end }}Proposed change for
hero/big.html— same principle with 4 breakpoints:{{ $webp400 := $featured.Resize "400x webp q80" }} {{ $webp600 := $featured.Resize "600x webp q80" }} {{ $webp800 := $featured.Resize "800x webp q80" }} {{ $webp1200 := $featured.Resize "1200x webp q80" }} <img srcset="{{ $webp400.RelPermalink }} 400w, {{ $webp600.RelPermalink }} 600w, {{ $webp800.RelPermalink }} 800w, {{ $webp1200.RelPermalink }} 1200w" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 90vw, 1200px" src="{{ $webp1200.RelPermalink }}" decoding="async" fetchpriority="high">Both hero templates should keep the existing fallback path for SVGs and remote hotlinked images, which cannot be processed by Hugo's pipeline.
4.
layouts/_default/_markup/render-image.htmlThe current implementation renders inline markdown images with a simple
<img>tag at original resolution and format, with no srcset and no WebP conversion. This affects every image embedded in post content.Proposed change: replace with a responsive WebP pipeline that also treats the first image in an article as the LCP candidate (
fetchpriority="high",loading="eager"):{{- $isFirstImage := false }} {{- if not (.Page.Scratch.Get "firstImageRendered") }} {{- .Page.Scratch.Set "firstImageRendered" true }} {{- $isFirstImage = true }} {{- end }} {{ if and (not $isSVG) (not $isRemote) $resource }} {{- $priority := cond $isFirstImage "high" "low" }} <img loading="{{ if eq $priority "high" }}eager{{ else }}lazy{{ end }}" decoding="async" fetchpriority="{{ $priority }}" alt="{{ $altText }}" srcset=" {{ ($resource.Resize "330x webp").RelPermalink }} 330w, {{ ($resource.Resize "660x webp").RelPermalink }} 660w, {{ ($resource.Resize "960x webp").RelPermalink }} 960w, {{ ($resource.Resize "1280x webp").RelPermalink }} 1280w, {{ ($resource.Resize "1920x webp").RelPermalink }} 1920w " sizes="(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, (min-width: 960px) 960px, (min-width: 768px) 660px, 100vw" src="{{ $resource.RelPermalink }}"> {{ else }} {{/* fallback: remote images and SVGs served as-is */}} <img loading="lazy" alt="{{ $altText }}" src="{{ $urlStr }}"> {{ end }}Backward compatibility
Most of the proposed changes respect the existing
disableImageOptimizationparameter and exclude SVGs and remote/hotlinked images, which cannot be processed by Hugo's image pipeline. No breaking changes to configuration or frontmatter.References
fetchpriorityand LCP: https://web.dev/articles/fetch-priorityHappy to open a PR with the complete changes if the maintainer agrees with the approach.
Beta Was this translation helpful? Give feedback.
All reactions