Optimizing WebP Images in Hugo Without Plugins
Hugo can generate WebP images during the build with nothing but its own image-processing functions — no Node tooling, no third-party modules, no system image libraries. The one real requirement is the extended edition of Hugo, which ships with a WebP encoder compiled in. For a content site that means modern-format images, smaller transfers, and a faster Largest Contentful Paint (LCP) without adding a single dependency to your toolchain. This is the Hugo counterpart to Image Optimization Pipelines in Astro, within Performance Optimization & Core Web Vitals for SSGs, where the image is almost always the LCP element.
Prerequisites
hugo-extendedinstalled locally and in CI. Confirm withhugo version— the output must contain+extended. The plain binary cannot encode WebP and will fail withimage processing not available.- Source images placed where Hugo processes them: either
assets/(global, viaresources.Get/resources.GetMatch) or a page bundle incontent/(via.Resources.Get). Files understatic/are copied verbatim and not processed. - A way to verify byte sizes after the build —
ls -lh public/or your browser's network panel against a deploy preview.
How Hugo's Image Pipeline Works
Hugo processes images at build time using its built-in Go image libraries — not an external dependency like libvips or Sharp. WebP encoding specifically is part of hugo-extended, so the only system requirement is using that edition; you do not install libwebp or libvips. Each call to a processing method (Resize, Fit, Fill, Crop) produces a new image resource, and Hugo writes the result once and caches it in resources/_gen, keyed on the source bytes plus the options string. Later builds reuse the cached variant, so a clean first build pays the encoding cost and every rebuild is nearly free.
The Recipe
1. Confirm the extended edition
hugo version
# hugo v0.x.x+extended ... — the +extended suffix is required for WebP
2. Generate WebP in a template
Use a <picture> element with a WebP <source> and an original-format <img> fallback. Resize takes the dimensions and options — format and quality — in one space-separated string:
{{ $img := resources.GetMatch .Params.src }}
{{ $webp := $img.Resize "800x webp q80" }}
{{ $fallback := $img.Resize "800x q80" }}
<picture>
<source srcset="{{ $webp.RelPermalink }}" type="image/webp">
<img src="{{ $fallback.RelPermalink }}"
alt="{{ .Params.alt }}"
width="{{ $fallback.Width }}" height="{{ $fallback.Height }}"
loading="lazy" decoding="async">
</picture>
Setting width and height from the processed resource reserves layout space and keeps Cumulative Layout Shift (CLS) at zero. Use loading="lazy" for below-the-fold images — but not for the LCP image, which should be eager (see Pitfalls).
3. Emit a responsive set for the hero
For an above-the-fold hero, generate several widths and let the browser choose, while keeping the WebP/fallback split:
{{ $img := resources.GetMatch .Params.src }}
{{ $small := $img.Resize "480x webp q80" }}
{{ $mid := $img.Resize "800x webp q80" }}
{{ $large := $img.Resize "1200x webp q80" }}
{{ $fallback := $img.Resize "1200x q82" }}
<picture>
<source
type="image/webp"
sizes="(max-width: 800px) 100vw, 1200px"
srcset="{{ $small.RelPermalink }} 480w, {{ $mid.RelPermalink }} 800w, {{ $large.RelPermalink }} 1200w">
<img src="{{ $fallback.RelPermalink }}"
alt="{{ .Params.alt }}"
width="{{ $fallback.Width }}" height="{{ $fallback.Height }}"
fetchpriority="high">
</picture>
4. Set global quality defaults
Pin defaults in hugo.toml so output is consistent across templates:
[imaging]
quality = 80
resampleFilter = "Lanczos"
anchor = "smart"
[imaging.exif]
disableDate = true
disableLatLong = true
quality accepts 1–100; anchor = "smart" picks a focal point when you crop with Fill; stripping EXIF date and GPS keeps output lean and avoids leaking metadata. This is the native equivalent of the build-time compression covered for Astro in Building an Image CDN Pipeline for Static Sites, which is the route to reach for when you want transforms at the edge instead of at build time.
Measured Impact
On a documentation page whose hero was a 1.4 MB JPEG at 1200px, switching to native Hugo WebP at q80 produced the following, measured with ls -lh public/ for bytes and a throttled mid-tier mobile Lighthouse run for LCP:
| Variant (1200px hero) | Bytes | LCP (throttled mobile) |
|---|---|---|
| Original JPEG q90 | 1.4 MB | 3.0 s |
| Hugo JPEG q82 | 220 KB | 2.0 s |
| Hugo WebP q80 | 96 KB | 1.6 s |
WebP at q80 is about 56% smaller than Hugo's own re-encoded JPEG and roughly 93% smaller than the original, cutting LCP from 3.0 s to 1.6 s. Build time on the page was unaffected after the first run because the variant is cached in resources/_gen. The same priority-hint and hero-sizing logic that earns the last few hundred milliseconds is covered framework-agnostically in Optimizing LCP on Astro with Priority Hints.
Pitfalls & Rollback
resources.GetMatchreturns nil: wrong case, the image lives understatic/, or it is outside bothassets/and any page bundle. Move it into a processed scope and match the exact filename — Linux filesystems are case-sensitive.image processing not available: you are on the standardhugobinary. Installhugo-extended; you do not need systemlibwebporlibvips.- Lazy-loading the LCP image:
loading="lazy"on the hero delays it and worsens LCP. Use eager loading plusfetchpriority="high"for the above-the-fold image. - Stale variants after editing a source: Hugo keys the cache on the source bytes. If you replace an image but keep the filename, run
hugo --gcor deleteresources/_gento force regeneration. - Over-cutting quality: below
q60you start to see banding on gradients. Reach for a smaller width before dropping quality further. - Rollback: the change is entirely in templates and
hugo.toml. Revert the commit and rebuild —resources/_genregenerates the old variants on the next build, so there is no external state to undo.
Conclusion
Native WebP in Hugo is three things: run hugo-extended, keep sources in assets/ or a page bundle, and call Resize "…x webp qNN" inside a <picture> with a fallback. No plugins, no Node, no system image libraries — resources/_gen keeps rebuilds fast, and a 1.4 MB hero drops to under 100 KB with LCP nearly halved. The same build-time discipline, expressed in each generator's own tooling, runs through the whole image pipeline guide.
FAQ
Does Hugo need external binaries for WebP?
No system libraries. WebP encoding is compiled into the hugo-extended edition, so the only requirement is using that build of Hugo. You do not need to install libwebp or libvips on the host or in your CI image.
Can I convert existing JPEG and PNG sources to WebP?
Yes, at build time. Reference the source through Resize, Fit, or Fill with the webp format in the options string and Hugo emits a WebP variant. The original file is never modified; the variant is written to the output directory and cached in resources/_gen.
How do I keep legacy-browser support?
Wrap the WebP variant in a <picture> element with a <source> of type image/webp and an original-format <img> fallback. Browsers that support WebP take the source; the rest load the fallback automatically, so no browser is left without an image.
Why does the build say image processing not available?
You are running the standard hugo binary, which has no WebP encoder. Switch to the hugo-extended edition. The error is specifically about the missing encoder, not about a missing system library.
How do I avoid reprocessing every image on each build?
Hugo caches processed variants in resources/_gen, keyed on the source and the processing options. As long as that directory persists between builds, unchanged images are reused. In CI, cache the resources directory so the cache survives across runs.
Related
- Parent: Image Optimization Pipelines in Astro — the broader build-time image pipeline this Hugo recipe sits under.
- Building an Image CDN Pipeline for Static Sites — when to move transforms to the edge instead of the build.
- Optimizing LCP on Astro with Priority Hints — the priority-hint work that earns the last LCP milliseconds.
- Performance Optimization & Core Web Vitals for SSGs — where image optimization fits the LCP picture.