Hugo Image Optimization: Cutting an 8.6MB Background to 310KB
Using Hugo’s built-in image processing pipeline to compress an 8.6MB PNG background to a 310KB WebP — a 28x size reduction, cutting slow-network load time from 5-10s to under 1s.
Categories:
1. Why Optimize Images
Images are usually the biggest culprit for slow static sites:
| Reason | Explanation |
|---|---|
| Large file size | A 2560×1440 PNG can be 8-10MB |
| Inefficient format | PNG is lossless — great for screenshots, bad for photos |
| No lazy loading | Page loads all images at once |
| No caching | Re-downloads on every visit |
Core idea: make the browser download smaller files.
2. Hugo Image Processing Pipeline
Hugo has built-in image processing that runs at build time:
$$Original image (assets/img/wall4.png)
↓ processed at build
Resize "1920x webp q75"
↓
Optimized image (public/img/wall4_hu_xxx.webp)$$
| Operation | Syntax | Description |
|---|---|---|
| Resize | Resize "800x" | Limit width to 800px |
| Crop | Crop "800x600" | Crop to exact size |
| Fill | Fill "800x600" | Fill and center-crop |
| Convert | webp / jpg / png | WebP is smallest |
| Quality | q75 | 75% quality (default 75) |
3. CSS Background Image Optimization
Step 1: Put the image in Hugo’s assets dir
mkdir -p web-site/assets/img
cp your-image.png web-site/assets/img/wall4.png
Step 2: Create a processing partial
layouts/partials/hero-bg.html:
{{ $img := resources.Get "img/wall4.png" }}
{{ $webp := "" }}
{{ if $img }}
{{ $webp = $img.Resize "1920x webp q75" }}
{{ end }}
{{ if $webp }}
<style>
:root {
--tn-hero-bg: url('{{ $webp.RelPermalink }}');
}
</style>
{{ end }}
Step 3: Inject in page templates
Add one line to the <head> of every baseof template:
<head>
{{ partial "head.html" . }}
{{ partial "hero-bg.html" . }} <!-- ← add this -->
</head>
Step 4: Reference the variable in CSS
.tn-hero {
background:
linear-gradient(rgba(18, 32, 88, 0.75), rgba(18, 32, 88, 0.85)),
var(--tn-hero-bg, url('/wall4.png')) center center / cover no-repeat;
}
Step 5: Rebuild
hugo
find public -name "*.webp"
# → public/img/wall4_hu_8de1eac147519903.webp
4. Inline Images (shortcode)
Plain  markdown does NOT go through Hugo’s pipeline. Create a shortcode:
layouts/shortcodes/img.html:
{{ $img := .Page.Resources.GetMatch (.Get "src") }}
{{ $alt := .Get "alt" | default "" }}
{{ $width := .Get "width" | default "800x" }}
{{ if $img }}
{{ $webp := $img.Resize (printf "%s webp q75" $width) }}
<figure>
<img src="{{ $webp.RelPermalink }}"
alt="{{ $alt }}"
loading="lazy"
width="{{ $webp.Width }}" height="{{ $webp.Height }}" />
{{ with .Get "caption" }}<figcaption>{{ . }}</figcaption>{{ end }}
</figure>
{{ end }}
Usage:
{{< img src="photo.jpg" alt="Product" caption="Fig 1: Product" >}}
Note: images must live next to the post (Page Bundle):
$$content/blog/my-post/
├── index.md ← the post (index.md, not xxx.md)
└── photo.jpg ← image beside the post$$
5. Lazy Loading
Add one attribute to any <img> — the browser loads it only when scrolled into view:
<img src="photo.webp" loading="lazy" />
6. Measuring Results
Browser Network panel (recommended)
- Press F12
- Go to Network tab
- Set throttling to Regular 3G or Slow 3G
- Check Disable cache
- Ctrl+Shift+R hard reload
- Look at the Time column for the image
curl
curl -o /dev/null -w "time: %{time_total}s size: %{size_download} bytes\n" \
http://localhost:1313/img/wall4_hu_8de1eac147519903.webp
Measured results
| Version | Size | Regular 3G time |
|---|---|---|
| PNG 2560×1440 | 8.6 MB | 5-10 s |
| WebP 1920px q75 | 310 KB | <1 s |
28x smaller; slow-network load dropped from 5-10s to under 1s.
7. Advanced Tips
7.1 Responsive images (srcset)
{{ $img := resources.Get "img/hero.png" }}
{{ $sm := $img.Resize "640x webp q75" }}
{{ $md := $img.Resize "1280x webp q75" }}
{{ $lg := $img.Resize "1920x webp q75" }}
<img src="{{ $lg.RelPermalink }}"
srcset="{{ $sm.RelPermalink }} 640w, {{ $md.RelPermalink }} 1280w, {{ $lg.RelPermalink }} 1920w"
sizes="100vw"
loading="lazy" />
7.2 More aggressive compression
$img.Resize "1920x webp q50" // 50% quality
$img.Resize "1920x webp q30" // 30% quality, fine for flat backgrounds
7.3 Nginx caching
location ~* \.(jpg|jpeg|png|webp|gif|svg)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
7.4 CDN
- Cloudflare: free CDN in front of your site, auto-cache + compress
- jsDelivr: free CDN for GitHub/Gitee static assets
Summary
$$1. Put images in assets/
2. Use resources.Get + Resize "xxx webp q75"
3. CSS background → inject CSS variable
4. Inline images → custom shortcode
5. Add loading="lazy"
6. Verify with browser DevTools$$