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: 1536px3.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 diperlukan4.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,BottomRight4.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 autorunFile
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 developmentAtau 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