Responsive Images With Hugo’s Image Processing

Preparing three copies of every image by hand works until one photograph changes. Hugo can derive responsive sizes, crops, and formats during the build while keeping the original source in its page bundle.

The visitor receives static files. No production server needs to resize an image while the page is loading.

The official Hugo image processing documentation is the reference for current operations and options.

Start with a page resource

content/
└── blogs/
    └── my-article/
        ├── index.md
        └── photo.jpg

Get the image in a page template:

{{ $image := .Resources.GetMatch "photo.jpg" }}

$image now exposes its dimensions, permalink, and processing methods. In a reusable partial, handle a missing match before calling those methods; a typo should fail clearly rather than halfway through a template.

Choose the operation from the crop you want

Resize to a width while keeping the aspect ratio:

{{ $small := $image.Resize "600x" }}

Resize by height:

{{ $small := $image.Resize "x600" }}

Create an exact thumbnail, cropping as needed:

{{ $thumb := $image.Fill "600x400 webp" }}

Keep the whole image inside a bounding box:

{{ $fit := $image.Fit "1200x800 webp" }}

Fill is right for consistent card artwork. Fit is safer when cropping would remove something important. Converting during another operation is as simple as including the target format:

{{ $image := $image.Resize "1200x webp" }}

Give the browser a real choice

{{ $small := $image.Resize "480x webp" }}
{{ $medium := $image.Resize "960x webp" }}
{{ $large := $image.Resize "1440x webp" }}

<img
  src="{{ $medium.RelPermalink }}"
  srcset="
    {{ $small.RelPermalink }} 480w,
    {{ $medium.RelPermalink }} 960w,
    {{ $large.RelPermalink }} 1440w
  "
  sizes="(max-width: 800px) 100vw, 800px"
  width="{{ $medium.Width }}"
  height="{{ $medium.Height }}"
  alt="Street photograph in Riga"
>

The srcset widths describe the generated files. sizes describes the rendered slot, which lets the browser choose sensibly. Hugo’s known dimensions also reserve space and reduce layout movement.

More outputs mean more build work

Three useful widths are often better than ten barely different ones. Very low quality is not an optimisation if screenshots become unreadable or photographs fall apart.

Hugo caches processed resources, so unchanged images do not always need to be regenerated. Build time and cache storage still grow with the number and size of source files.

I put this logic in layouts/partials/image.html rather than repeating it across templates. One partial keeps width choices, quality, markup, and fallback behaviour consistent.

For a blog or portfolio, Hugo’s built-in pipeline is usually enough. Add an external image service when a real requirement appears, not because responsive images looked too straightforward for one afternoon.