Cara Setup Image Optimization di Hugo dengan WebP dan Responsive Images
Optimasi gambar adalah salah satu faktor terpenting untuk performa website. Hugo memiliki image processing built-in yang powerful untuk membuat gambar optimal secara otomatis.
Mengapa Image Optimization Penting?
Impact pada Performa
- Page Size: Gambar sering 60-80% dari total page size
- Loading Time: Gambar besar memperlambat LCP (Largest Contentful Paint)
- Bandwidth: User dengan koneksi lambat akan mengalami delay signifikan
- SEO: Google menggunakan Core Web Vitals sebagai ranking factor
- UX: Website yang lambat meninggalkan pengunjung
Statistik Penting
Before Optimization:
- Total page size: 4.2MB
- Image size: 3.6MB (85%)
- LCP: 4.8 detik
After Optimization:
- Total page size: 1.1MB
- Image size: 0.4MB (36%)
- LCP: 1.2 detik
Hugo Image Processing Overview
Supported Formats
Hugo mendukung processing untuk format:
– Input: JPG, PNG, GIF, WebP, SVG
– Output: JPG, PNG, GIF, WebP (tergantung imagemagick)
Key Features
- Resizing: Resize gambar ke ukuran spesifik
- Format Conversion: Convert ke WebP untuk kompresi lebih baik
- Quality Control: Atur kompresi quality
- Filter: Lanczos, CatmullRom, MitchellNetravali
- Batch Processing: Process multiple images sekaligus
Setup Image Processing
1. Konfigurasi Hugo
File hugo.toml:
[imaging]
# Default resample filter
resampleFilter = "lanczos"
# Default JPEG quality (0-100)
quality = 80
# Default hint about what type of image (photo/graphics)
hint = "photo"
# Default background color ( untuk PNG dengan transparency)
bgColor = "#ffffff"
Resample Filters:
– nearest: Fastest, lowest quality
– box: Good for downscaling
– linear: Linear interpolation
– gaussian: Smooth
– lanczos: Best quality (default, recommended)
– catmullrom: Sharp
– mitchellnetravali: Balanced
2. Store Images sebagai Page Resources
Struktur yang Benar (Page Bundles):
content/
└── posts/
└── my-post/
├── index.md # Content file
├── featured.jpg # Page resource
├── screenshot-1.png # Page resource
└── diagram.svg # Page resource
File content/posts/my-post/index.md:
---
title: "Judul Post"
date: 2026-02-03T10:00:00+07:00
image: featured.jpg
description: "Deskripsi post"
---
Content di sini...
Keuntungan Page Bundles:
– Images dianggap sebagai page resources
– Bisa di-process dengan Hugo Pipes
– Asset co-location dengan content
– Mudah untuk manage
3. Global Resources (Static Images)
Jika gambar digunakan di banyak halaman, simpan di assets/:
assets/
└── images/
├── logo.png
├── hero-bg.jpg
└── icons/
├── facebook.svg
└── twitter.svg
Implementasi di Templates
3.1 Basic Image Processing
Template untuk Page Resources:
{{ $image := .Resources.GetMatch .Params.image }}
{{ if $image }}
{{ $resized := $image.Resize "800x" }}
{{ $webp := $image.Resize "800x webp" }}
<picture>
<source srcset="{{ $webp.RelPermalink }}" type="image/webp">
<img src="{{ $resized.RelPermalink }}"
alt="{{ .Title }}"
width="{{ $resized.Width }}"
height="{{ $resized.Height }}"
loading="lazy">
</picture>
{{ end }}
Template untuk Global Resources:
{{ $image := resources.Get "images/hero-bg.jpg" }}
{{ if $image }}
{{ $resized := $image.Resize "1920x webp q80" }}
<div style="background-image: url('{{ $resized.RelPermalink }}');
background-size: cover;
background-position: center;">
</div>
{{ end }}
3.2 Responsive Images dengan Srcset
Implementasi responsive images untuk berbagai ukuran layar:
{{ $image := .Resources.GetMatch .Params.image }}
{{ if $image }}
{{ $small := $image.Resize "400x" }}
{{ $medium := $image.Resize "800x" }}
{{ $large := $image.Resize "1200x" }}
{{ $smallWebp := $image.Resize "400x webp" }}
{{ $mediumWebp := $image.Resize "800x webp" }}
{{ $largeWebp := $image.Resize "1200x webp" }}
<picture>
<!-- WebP versions -->
<source
srcset="{{ $smallWebp.RelPermalink }} 400w,
{{ $mediumWebp.RelPermalink }} 800w,
{{ $largeWebp.RelPermalink }} 1200w"
sizes="(max-width: 400px) 400px,
(max-width: 800px) 800px,
1200px"
type="image/webp">
<!-- Fallback JPG/PNG -->
<source
srcset="{{ $small.RelPermalink }} 400w,
{{ $medium.RelPermalink }} 800w,
{{ $large.RelPermalink }} 1200w"
sizes="(max-width: 400px) 400px,
(max-width: 800px) 800px,
1200px">
<img src="{{ $medium.RelPermalink }}"
alt="{{ .Title }}"
width="{{ $medium.Width }}"
height="{{ $medium.Height }}"
loading="lazy"
decoding="async">
</picture>
{{ end }}
Breakpoints Umum:
– sm: 640px
– md: 768px
– lg: 1024px
– xl: 1280px
– 2xl: 1536px
3.3 Partial Reusable untuk Images
Buat partial layouts/partials/responsive-image.html:
{{ $image := .image }}
{{ $alt := .alt | default "" }}
{{ $class := .class | default "" }}
{{ $loading := .loading | default "lazy" }}
{{ if $image }}
{{ $small := $image.Resize "400x webp" }}
{{ $medium := $image.Resize "800x webp" }}
{{ $large := $image.Resize "1200x webp" }}
<picture>
<source
srcset="{{ $small.RelPermalink }} 400w,
{{ $medium.RelPermalink }} 800w,
{{ $large.RelPermalink }} 1200w"
sizes="(max-width: 400px) 400px,
(max-width: 800px) 800px,
1200px"
type="image/webp">
<img src="{{ $medium.RelPermalink }}"
alt="{{ $alt }}"
class="{{ $class }}"
width="{{ $medium.Width }}"
height="{{ $medium.Height }}"
loading="{{ $loading }}"
decoding="async">
</picture>
{{ end }}
Penggunaan:
{{ $image := .Resources.GetMatch .Params.image }}
{{ partial "responsive-image.html" (dict "image" $image "alt" .Title "class" "rounded-xl shadow-lg") }}
Advanced Image Processing
4.1 Custom Quality per Image
{{ $image := .Resources.GetMatch .Params.image }}
{{ if $image }}
{{ $optimized := $image.Resize "800x webp q70" }}
<img src="{{ $optimized.RelPermalink }}" alt="{{ .Title }}">
{{ end }}
Quality Guidelines:
– q50: Kompresi tinggi, file kecil, quality acceptable untuk thumbnails
– q70-q80: Balance optimal (recommended default)
– q90-q95: High quality untuk hero images
– q100: Lossless, hanya jika diperlukan
4.2 Image Filters
{{ $image := .Resources.GetMatch .Params.image }}
{{ if $image }}
{{ $grayscale := $image.Filter (images.Grayscale) }}
{{ $blur := $image.Filter (images.GaussianBlur 6) }}
{{ $contrast := $image.Filter (images.Contrast 20) }}
{{ $brightness := $image.Filter (images.Brightness 10) }}
{{ $saturation := $image.Filter (images.Saturation 50) }}
{{ $sepia := $image.Filter (images.Sepia 50) }}
{{ $pixelate := $image.Filter (images.Pixelate 8) }}
<!-- Kombinasi filters -->
{{ $filtered := $image.Filter (images.Grayscale) (images.Contrast 30) }}
{{ end }}
4.3 Crop dan Fill
{{ $image := .Resources.GetMatch .Params.image }}
{{ if $image }}
{{ $cropped := $image.Crop "600x400 center" }}
{{ $filled := $image.Fill "600x400 center" }}
{{ $fitted := $image.Fit "600x400" }}
<img src="{{ $cropped.RelPermalink }}" alt="Cropped image">
{{ end }}
Options:
– Smart: Hugo akan mendeteksi area penting
– Center: Crop dari tengah
– TopLeft, TopRight, BottomLeft, BottomRight
4.4 EXIF Data dan Rotasi
{{ $image := .Resources.GetMatch .Params.image }}
{{ if $image }}
{{ $exif := $image.Exif }}
{{ if $exif.Tags.Orientation }}
{{ $rotated := $image.Filter (images.Rotate $exif.Tags.Orientation) }}
{{ end }}
{{ end }}
Lazy Loading Implementation
5.1 Native Lazy Loading
<!-- Modern browsers support loading="lazy" -->
<img src="image.webp" alt="Deskripsi" loading="lazy" decoding="async">
<!-- Eager loading untuk hero image -->
<img src="hero.webp" alt="Hero" loading="eager" fetchpriority="high">
5.2 Intersection Observer (Fallback)
// assets/js/lazy-loading.js
document.addEventListener('DOMContentLoaded', () => {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
}, {
rootMargin: '50px 0px',
threshold: 0.01
});
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
});
5.3 Blur-up Placeholder (LQIP)
{{ $image := .Resources.GetMatch .Params.image }}
{{ if $image }}
{{ $tiny := $image.Resize "20x q20" }}
{{ $full := $image.Resize "800x webp" }}
<div class="blur-up-container">
<img src="{{ $tiny.RelPermalink }}"
class="blur-up-placeholder"
style="filter: blur(10px); transition: opacity 0.3s;"
aria-hidden="true">
<img src="{{ $full.RelPermalink }}"
alt="{{ .Title }}"
class="blur-up-image"
style="opacity: 0; transition: opacity 0.3s;"
onload="this.style.opacity=1; this.previousElementSibling.style.opacity=0;">
</div>
{{ end }}
CSS untuk Image Optimization
6.1 Prevent Layout Shift (CLS)
/* Set aspect ratio untuk prevent CLS */
.image-container {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
background-color: #f3f4f6;
}
.image-container img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
/* Atau dengan padding trick untuk older browsers */
.image-container-legacy {
position: relative;
width: 100%;
padding-top: 56.25%; /* 16:9 aspect ratio */
}
.image-container-legacy img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
6.2 Art Direction (Picture Element)
<picture>
<!-- Desktop -->
<source media="(min-width: 1024px)"
srcset="large.webp 1200w"
type="image/webp">
<!-- Tablet -->
<source media="(min-width: 768px)"
srcset="medium.webp 800w"
type="image/webp">
<!-- Mobile -->
<source srcset="small.webp 400w"
type="image/webp">
<img src="fallback.jpg" alt="Description">
</picture>
Performance Monitoring
7.1 Lighthouse CI Integration
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install -g @lhci/cli
- run: hugo --minify
- run: lhci autorun
File lighthouserc.json:
{
"ci": {
"collect": {
"staticDistDir": "./public"
},
"assert": {
"assertions": {
"categories:performance": ["error", {"minScore": 0.9}],
"categories:accessibility": ["error", {"minScore": 0.9}],
"first-contentful-paint": ["error", {"maxNumericValue": 2000}],
"largest-contentful-paint": ["error", {"maxNumericValue": 2500}]
}
}
}
}
7.2 Image Size Budget
File budget.json:
[
{
"path": "/*",
"resourceSizes": [
{
"resourceType": "image",
"budget": 500000
}
]
}
]
CDN Integration
8.1 Cloudflare Image Optimization
# hugo.toml
[imaging]
quality = 85
resampleFilter = "lanczos"
<!-- Dengan Cloudflare Polish -->
<img src="/cdn-cgi/image/quality=80,format=auto/https://example.com/image.jpg"
alt="Description">
8.2 Cloudinary Integration
{{ $image := .Resources.GetMatch .Params.image }}
{{ if $image }}
{{ $cloudinaryUrl := printf "https://res.cloudinary.com/your-cloud/image/upload/q_auto,f_auto,w_800/%s" $image.Name }}
<img src="{{ $cloudinaryUrl }}" alt="{{ .Title }}">
{{ end }}
Troubleshooting
Issue: “image not found”
Solusi:
# Pastikan image di page bundle
ls content/posts/my-post/
# Atau gunakan resources.Get dengan path yang benar
{{ $image := resources.Get "images/photo.jpg" }}
Issue: “error processing image”
Solusi:
# Pastikan Hugo Extended terinstall
# Check dengan:
hugo version # Harus ada kata "extended"
# Install Hugo Extended:
# macOS
brew install hugo
# Windows
choco install hugo-extended
Issue: “slow build with many images”
Solusi:
# Gunakan caching di hugo.toml
[imaging]
resampleFilter = "box" # Faster filter untuk development
# Atau process images secara incremental
Checklist Image Optimization
- [ ] Images dalam page bundles atau assets folder
- [ ] WebP format untuk modern browsers
- [ ] JPG fallback untuk older browsers
- [ ] Responsive srcset untuk berbagai ukuran layar
- [ ] Width dan height attributes untuk prevent CLS
- [ ] Lazy loading untuk images below fold
- [ ] Eager loading untuk hero/featured images
- [ ] Alt text yang deskriptif untuk accessibility
- [ ] Quality optimization (q70-80 untuk balance)
- [ ] Image processing di Hugo Pipes
- [ ] CDN untuk global delivery
- [ ] Preload untuk critical images
Kesimpulan
Hugo image processing memberikan:
✅ Otomatis: Process saat build, tidak perlu manual work
✅ Optimal: WebP format, responsive images, lazy loading
✅ Fast: Pipeline processing, caching support
✅ Flexible: Custom filters, cropping, quality control
✅ SEO-friendly: Structured data, alt tags, performance
Dengan setup yang benar, Anda bisa mengurangi image size 60-80% tanpa quality loss yang signifikan.
Ditulis oleh
Hendra Wijaya