DEV Community

Cover image for Progressive Image Pipeline untuk Galeri Dokumentasi Proyek Lapangan
Mightyblue
Mightyblue

Posted on

Progressive Image Pipeline untuk Galeri Dokumentasi Proyek Lapangan

Ada satu jenis website yang jarang dibahas developer, tapi jumlahnya ribuan di Indonesia: website perusahaan kontraktor.

Isinya? Foto. Ratusan.

Foto galian tanah. Foto pengecoran. Foto instalasi MEP. Foto progres struktur baja minggu ke-3, ke-4, ke-5. Semuanya diambil pakai HP lapangan, resolusi 4032×3024, ukuran 4–6 MB per file, lalu diupload apa adanya ke halaman galeri. Hasilnya bisa ditebak — halaman yang butuh 18 detik untuk selesai loading di jaringan 4G Karawang. Padahal dokumentasi visual itu justru aset penjualan paling kuat bagi kontraktor. Google sendiri sudah lama mendorong solusi paling dasarnya lewat dukungan lazy loading di level browser, tapi kenyataannya loading="lazy" saja tidak cukup. Dan di situlah optimasi galeri foto proyek konstruksi berhenti jadi urusan estetika, lalu berubah jadi urusan rekayasa.

Kami di PT Abi Darma Sejahtra — kontraktor sipil, MEP, dan rental alat berat yang beroperasi di kawasan industri Karawang, Cikarang, dan Bekasi — punya masalah yang sangat konkret di sini. Setiap proyek menghasilkan 200–600 foto dokumentasi. Bukan foto katalog yang rapi dan seragam, tapi foto lapangan: pencahayaan berantakan, orientasi campur aduk, metadata EXIF berisi koordinat GPS yang seharusnya tidak ikut publik. Menariknya, disiplin yang kami pakai untuk menangani ini ternyata mirip dengan pendekatan di dunia geoteknik. Riset tentang monitoring deformasi dinding penahan menggunakan distributed fiber optic sensor menunjukkan hal yang sama: data mentah dalam volume besar tidak berguna sampai ia disampling, difilter, dan disajikan pada resolusi yang tepat untuk pengambilan keputusan. Sensor menghasilkan ribuan titik regangan; yang dilihat engineer cuma kurva defleksi. Kamera lapangan menghasilkan ratusan file 5 MB; yang dilihat calon klien cuma grid thumbnail.

Kami mengangkat tema ini karena celahnya nyata. Tulisan tentang image optimization biasanya berhenti di e-commerce dan portfolio fotografer — konteks di mana setiap gambar dikurasi manual. Nyaris tidak ada yang membahas skenario "ratusan foto tak terkurasi dari lapangan, diupload oleh site supervisor lewat WhatsApp, harus tampil cepat di HP mid-range." Itu masalah harian ribuan bisnis konstruksi, manufaktur, dan logistik di Asia Tenggara. Artikel ini adalah catatan teknis dari sisi orang yang mengerjakannya langsung, bukan ringkasan dokumentasi.

Kesimpulan lebih dulu, biar kamu bisa memutuskan apakah artikel ini layak dibaca sampai habis:
Masalah galeri proyek bukan pada format gambar. Masalahnya pada jumlah. AVIF memperkecil byte, blur placeholder membeli waktu persepsi, tapi yang benar-benar menyelamatkan Core Web Vitals adalah tidak me-render 400 elemen DOM sekaligus. Format dulu, persepsi kedua, virtualisasi terakhir — dan yang terakhir itu yang paling sering dilewatkan.


1. Mengapa Galeri Konstruksi Rusak dengan Cara yang Unik

Sebelum bicara solusi, penting memahami kenapa galeri proyek konstruksi punya karakter kerusakan yang berbeda dari galeri e-commerce atau blog foto. Sumber masalahnya ada di hulu — di cara foto itu lahir.

Foto Lapangan Bukan Foto Produk

Foto produk difoto sekali, dikurasi, dikompresi, lalu dipakai bertahun-tahun.

Foto lapangan tidak begitu.

Foto lapangan diambil sambil pakai helm dan rompi. Sering satu tangan. Sering saat matahari jam 11 siang yang bikin highlight blown out. Site supervisor kami tidak akan pernah — dan memang tidak seharusnya — memikirkan aspect ratio atau color profile.

Konsekuensinya di sisi frontend:

  • Dimensi tidak seragam → Cumulative Layout Shift (CLS) melonjak kalau kamu tidak menyimpan dimensi asli
  • Orientasi EXIF acak → gambar tampil terbalik kalau pipeline tidak menormalkan rotasi
  • Rasio kompresi tidak konsisten → satu file 2 MB, file berikutnya 7 MB
  • Metadata GPS ikut terbawa → risiko kebocoran lokasi proyek klien

Volume Adalah Musuh Sebenarnya

Satu foto 5 MB itu masalah kecil. Empat ratus foto 5 MB itu masalah arsitektur.

Kalau kamu render semuanya sebagai <img> dalam satu grid, browser harus:

  1. Parse 400 node DOM
  2. Menjadwalkan 400 network request (dibatasi connection pool)
  3. Mengalokasikan memori decode untuk setiap gambar yang masuk viewport
  4. Melakukan layout dan paint ulang setiap kali satu gambar selesai dimuat

Di HP Android mid-range dengan RAM 4 GB, tab-nya bisa langsung di-kill oleh OS. Bukan lambat — mati.

Kondisi Jumlah Foto Total Transfer LCP (4G) Status CWV
Upload mentah, tanpa optimasi 400 ± 1.900 MB 14–18 dtk Gagal total
JPEG dikompresi + loading="lazy" 400 ± 240 MB 4,8 dtk Butuh perbaikan
AVIF + srcset responsif 400 ± 62 MB 2,6 dtk Butuh perbaikan
AVIF + blur placeholder + virtualized grid 400 ± 9 MB awal 1,4 dtk Lolos

Perhatikan baris terakhir. Transfer awal turun drastis bukan karena gambarnya lebih kecil lagi, tapi karena sebagian besar gambar tidak pernah diminta sampai benar-benar dibutuhkan.


2. Lapisan Pertama: Format dan Derivatif

Lapisan paling dasar dari optimasi galeri foto proyek konstruksi adalah memastikan setiap byte yang dikirim memang perlu dikirim. Ini pekerjaan build-time atau upload-time, bukan runtime.

AVIF sebagai Default, WebP sebagai Jaring Pengaman

AVIF secara konsisten menghasilkan file 40–55% lebih kecil dibanding JPEG pada kualitas visual setara, terutama pada foto lapangan yang penuh tekstur — tanah, kerikil, beton, rangka baja. Justru di konten bertekstur tinggi seperti inilah AVIF unggul jauh.

Tapi jangan buang WebP. Beberapa perangkat lama dan WebView di aplikasi tertentu masih tersandung AVIF.

<picture>
  <source
    type="image/avif"
    srcset="/proyek/csi-plant6-320.avif 320w,
            /proyek/csi-plant6-640.avif 640w,
            /proyek/csi-plant6-1280.avif 1280w"
    sizes="(max-width: 640px) 50vw, 320px">
  <source
    type="image/webp"
    srcset="/proyek/csi-plant6-320.webp 320w,
            /proyek/csi-plant6-640.webp 640w,
            /proyek/csi-plant6-1280.webp 1280w"
    sizes="(max-width: 640px) 50vw, 320px">
  <img
    src="/proyek/csi-plant6-640.jpg"
    alt="Progres pekerjaan struktur baja plant 6, minggu ke-4"
    width="1280" height="960"
    loading="lazy" decoding="async">
</picture>
Enter fullscreen mode Exit fullscreen mode

Tiga hal yang wajib ada dan sering dilupakan:

  • width dan height eksplisit. Ini yang mengunci ruang layout dan mencegah CLS. Bukan opsional.
  • decoding="async". Melepas decoding dari main thread supaya scroll tetap mulus.
  • alt yang deskriptif. "Foto proyek" itu bukan alt text. "Progres pekerjaan struktur baja plant 6, minggu ke-4" itu baru alt text — sekaligus sinyal relevansi untuk pencarian gambar.

Pipeline Derivatif dengan Sharp

Jangan pernah membuat derivatif secara manual. Ini pekerjaan script.

import sharp from "sharp";

const WIDTHS = [320, 640, 1280];

export async function buildDerivatives(inputPath, slug) {
  const base = sharp(inputPath)
    .rotate()          // normalkan orientasi dari EXIF
    .withMetadata({ exif: {} }); // buang GPS & metadata sensitif

  const { width, height } = await base.metadata();

  for (const w of WIDTHS) {
    await base.clone().resize({ width: w })
      .avif({ quality: 52, effort: 4 })
      .toFile(`./public/proyek/${slug}-${w}.avif`);

    await base.clone().resize({ width: w })
      .webp({ quality: 74 })
      .toFile(`./public/proyek/${slug}-${w}.webp`);
  }

  // LQIP: basis blur placeholder
  const lqip = await base.clone()
    .resize({ width: 16 })
    .blur(1)
    .webp({ quality: 20 })
    .toBuffer();

  return {
    slug,
    width,
    height,
    lqip: `data:image/webp;base64,${lqip.toString("base64")}`
  };
}
Enter fullscreen mode Exit fullscreen mode

.rotate() tanpa argumen membaca EXIF dan memperbaiki orientasi otomatis. .withMetadata({ exif: {} }) menghapus koordinat GPS — untuk kontraktor yang bekerja di dalam kawasan pabrik klien, ini bukan sekadar praktik baik, tapi kewajiban kontraktual.


3. Lapisan Kedua: Membeli Waktu dengan Blur Placeholder

Setelah byte diperkecil, masalah berikutnya bukan lagi kecepatan objektif — melainkan kecepatan yang dirasakan. Di sinilah blur placeholder bekerja, dan alasannya lebih psikologis daripada teknis.

Kenapa LQIP Masih Relevan di 2026

Kotak abu-abu kosong terasa seperti error. Gambar buram yang perlahan menajam terasa seperti proses.

Perbedaan persepsinya besar, padahal waktu tunggu aktualnya identik.

Low Quality Image Placeholder (LQIP) dari resize 16px menghasilkan base64 string sekitar 300–500 byte. Cukup kecil untuk di-inline langsung ke HTML tanpa request tambahan.

.foto-wrapper {
  position: relative;
  aspect-ratio: var(--ar);
  overflow: hidden;
  background-size: cover;
  background-image: var(--lqip);
}

.foto-wrapper img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 320ms ease-out;
}

.foto-wrapper img[data-loaded="true"] {
  opacity: 1;
}
Enter fullscreen mode Exit fullscreen mode

Variabel --ar diisi dari rasio dimensi asli yang sudah kita simpan saat build. Hasilnya: ruang layout terkunci sempurna sebelum satu pixel pun terunduh. CLS = 0.

Batas yang Perlu Diketahui

LQIP bukan obat segala penyakit.

Kalau gambar aslinya tetap 800 KB, blur placeholder cuma menunda kekecewaan. Ia membeli sekitar 200–400 ms toleransi psikologis, bukan lebih. Jadi urutannya tidak boleh dibalik: kecilkan dulu, baru perhalus persepsinya.


4. Lapisan Ketiga: Virtualized Grid, Bagian yang Paling Sering Dilewatkan

Ini bab paling penting sekaligus yang paling jarang dibahas. Dua lapisan sebelumnya mengurus ukuran gambar. Lapisan ini mengurus jumlah gambar yang benar-benar ada di DOM pada satu waktu.

Lazy Loading ≠ Virtualization

Ini kesalahpahaman yang sangat umum, jadi mari dituntaskan.

loading="lazy" menunda pengunduhan gambar. Elemen <img>-nya tetap ada di DOM. Semua 400 elemen tetap di-parse, tetap masuk perhitungan layout, tetap memakan memori.

Virtualization menunda keberadaan elemennya. Hanya item yang terlihat (plus buffer) yang benar-benar di-render.

Untuk 40 foto, loading="lazy" sudah cukup. Untuk 400 foto, kamu butuh virtualization.

import { useVirtualizer } from "@tanstack/react-virtual";
import { useRef } from "react";

function GaleriProyek({ items, kolom = 3 }) {
  const parentRef = useRef(null);
  const barisTotal = Math.ceil(items.length / kolom);

  const virtualizer = useVirtualizer({
    count: barisTotal,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 220,
    overscan: 3, // buffer baris di luar viewport
  });

  return (
    <div ref={parentRef} className="galeri-scroll">
      <div style={{ height: virtualizer.getTotalSize(), position: "relative" }}>
        {virtualizer.getVirtualItems().map((baris) => {
          const mulai = baris.index * kolom;
          const isiBaris = items.slice(mulai, mulai + kolom);

          return (
            <div
              key={baris.key}
              className="galeri-baris"
              style={{
                position: "absolute",
                top: 0,
                transform: `translateY(${baris.start}px)`,
                height: baris.size,
              }}
            >
              {isiBaris.map((foto) => (
                <FotoItem key={foto.slug} foto={foto} />
              ))}
            </div>
          );
        })}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Nilai overscan adalah tuning knob utamanya. Terlalu kecil, grid terasa kosong saat scroll cepat. Terlalu besar, kamu kehilangan manfaat virtualisasi. Angka 2–4 baris biasanya jadi titik seimbang di perangkat mid-range.

Alternatif Tanpa Library

Kalau kamu tidak pakai React, content-visibility memberi sebagian manfaat serupa dengan satu baris CSS:

.galeri-baris {
  content-visibility: auto;
  contain-intrinsic-size: auto 220px;
}
Enter fullscreen mode Exit fullscreen mode

Browser akan melewatkan proses rendering untuk baris yang jauh di luar viewport. Bukan virtualisasi penuh — node DOM-nya masih ada — tapi biaya layout dan paint-nya hilang. Untuk galeri 100–200 foto, ini sering sudah memadai.


5. HowTo: Implementasi dari Nol dalam Enam Langkah

Bab ini merangkum seluruh pembahasan menjadi urutan kerja yang bisa langsung dieksekusi. Urutannya disusun berdasarkan rasio dampak terhadap usaha — langkah awal memberi perbaikan terbesar dengan pekerjaan paling sedikit.

{
  "@context": "https://schema.org",
  "@type": "HowTo",
  "name": "Membangun Progressive Image Pipeline untuk Galeri Proyek Konstruksi",
  "description": "Panduan enam langkah mengoptimalkan galeri dokumentasi lapangan berisi ratusan foto agar lolos Core Web Vitals.",
  "totalTime": "PT6H",
  "tool": [
    { "@type": "HowToTool", "name": "Node.js 20+" },
    { "@type": "HowToTool", "name": "Sharp" },
    { "@type": "HowToTool", "name": "TanStack Virtual" }
  ],
  "step": [
    {
      "@type": "HowToStep",
      "position": 1,
      "name": "Audit baseline",
      "text": "Ukur LCP, INP, dan CLS halaman galeri saat ini dengan Lighthouse pada throttling 4G. Catat jumlah foto dan total transfer size sebagai angka pembanding."
    },
    {
      "@type": "HowToStep",
      "position": 2,
      "name": "Bangun pipeline derivatif",
      "text": "Gunakan Sharp untuk menghasilkan varian AVIF dan WebP pada lebar 320, 640, dan 1280 piksel. Normalkan orientasi EXIF dan hapus metadata GPS pada tahap ini."
    },
    {
      "@type": "HowToStep",
      "position": 3,
      "name": "Simpan dimensi dan LQIP",
      "text": "Ekstrak lebar, tinggi, dan base64 blur placeholder 16 piksel ke dalam manifest JSON yang dibaca saat render."
    },
    {
      "@type": "HowToStep",
      "position": 4,
      "name": "Kunci aspect ratio",
      "text": "Terapkan properti CSS aspect-ratio menggunakan dimensi asli agar ruang layout terkunci sebelum gambar dimuat sehingga CLS tetap nol."
    },
    {
      "@type": "HowToStep",
      "position": 5,
      "name": "Terapkan virtualisasi",
      "text": "Ganti grid statis dengan virtualized grid berbasis baris. Set overscan antara dua hingga empat baris untuk keseimbangan kemulusan dan efisiensi memori."
    },
    {
      "@type": "HowToStep",
      "position": 6,
      "name": "Validasi dengan data lapangan",
      "text": "Ukur ulang dengan Lighthouse, lalu verifikasi menggunakan data Real User Monitoring dari perangkat mid-range di jaringan seluler nyata."
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Untuk konteks yang lebih luas soal bagaimana strategi gambar berinteraksi dengan caching dan rendering strategy, pembahasan komunitas seperti Optimasi Core Web Vitals untuk Portal Properti menyoroti trade-off SSR/ISR yang sangat relevan untuk halaman media-heavy semacam ini.


6. Kesalahan yang Kami Lakukan Sendiri

Bagian ini sengaja ditulis, karena tulisan teknis yang hanya memuat keberhasilan cenderung tidak berguna. Berikut hal-hal yang kami salah kaprah di iterasi awal.

Kualitas AVIF Terlalu Rendah

Kami sempat set quality: 35 demi angka Lighthouse yang cantik.

Hasilnya: artefak blocking di area beton dan langit. Klien kami — engineering manager sebuah pabrik — langsung menyampaikan bahwa foto progresnya "terlihat kotor."

Kredibilitas dokumentasi teknis lebih mahal daripada 15 poin Lighthouse. Kami naikkan ke 52 dan tidak pernah menyesal.

Lupa Menetapkan Prioritas untuk Hero

Semua gambar diberi loading="lazy", termasuk gambar pertama yang terlihat.

Ini justru memperburuk LCP, karena browser jadi menunda hal yang paling penting. Gambar pertama harus memakai fetchpriority="high" dan tanpa loading="lazy".

Menganggap Virtualisasi Bisa Menunggu

Kami kira setelah AVIF, kerjaan selesai. Metrik lab memang bagus.

Tapi data Real User Monitoring dari perangkat mid-range menceritakan hal berbeda: INP tetap buruk, dan sebagian sesi mobile berakhir dengan tab yang crash. Baru setelah virtualisasi dipasang, angkanya stabil.

Pelajaran pentingnya: lab data bisa berbohong, field data tidak.


7. FAQ

Beberapa pertanyaan berikut muncul berulang saat kami mendiskusikan pipeline ini dengan developer lain maupun dengan tim internal.

Apakah AVIF sudah aman dipakai di produksi pada 2026?
Ya. Dukungan browser sudah melampaui 94%. Tetap sediakan fallback WebP dan JPEG lewat elemen <picture> untuk WebView aplikasi lama.

Berapa foto yang jadi ambang batas perlunya virtualisasi?
Rule of thumb kami: di bawah 60 foto, loading="lazy" dan content-visibility sudah cukup. Di atas 150 foto, virtualisasi hampir selalu memberi perbedaan yang terasa.

Apakah virtualisasi merusak SEO karena gambar tidak ada di DOM awal?
Bisa, kalau salah implementasi. Solusinya: render server-side untuk 12–24 item pertama, tambahkan <link rel="preload"> untuk gambar hero, dan sediakan sitemap gambar terpisah. Crawler mendapat konten, pengguna mendapat kecepatan.

Apakah pendekatan ini bisa diterapkan tanpa framework JavaScript?
Bisa. Kombinasi <picture> + AVIF + content-visibility + aspect-ratio seluruhnya berjalan di HTML dan CSS murni. Kamu hanya kehilangan virtualisasi penuh, bukan sisanya.

Bagaimana menangani upload dari lapangan yang tidak konsisten?
Jalankan pipeline di sisi server saat upload, bukan di sisi klien. Site supervisor cukup mengirim file mentah; normalisasi orientasi, kompresi, dan penghapusan metadata GPS terjadi otomatis di backend.

Berapa besar dampaknya terhadap konversi?
Tidak ada angka universal. Yang kami amati di halaman portofolio proyek: durasi sesi naik dan bounce rate turun setelah LCP menembus di bawah dua detik. Ukur milikmu sendiri — konteks tiap situs berbeda.


Ketika Kecepatan Menjadi Bagian dari Kredibilitas Teknis

Menutup pembahasan ini, ada satu hal yang layak ditegaskan: pekerjaan yang kami uraikan di atas sebenarnya tidak glamor sama sekali. Tidak ada framework baru, tidak ada AI, tidak ada arsitektur eksotis. Hanya disiplin menangani volume data dengan benar — persis seperti yang dilakukan engineer geoteknik saat menyaring ribuan titik pengukuran menjadi satu kurva yang bisa dibaca.

Bagi perusahaan konstruksi, galeri proyek bukan dekorasi. Ia adalah bukti kerja. Dan bukti yang butuh 18 detik untuk muncul, praktis bukan bukti sama sekali.

"Fast is better than slow."
Larry Page

Larry Page adalah salah satu pendiri Google dan orang yang menempatkan kecepatan sebagai prinsip produk sejak hari pertama, bukan sebagai optimasi belakangan. Prinsip itulah yang kemudian mengkristal menjadi Core Web Vitals — kerangka pengukuran yang hari ini menentukan apakah halaman galeri sebuah kontraktor dianggap layak tampil atau tidak. Dalam konteks artikel ini, kalimatnya bukan slogan motivasional. Ia deskripsi teknis: pada halaman yang berisi ratusan foto lapangan, cepat memang lebih baik daripada lambat, dan selisihnya diukur dalam milidetik yang bisa diaudit.

Praktik optimasi galeri foto proyek konstruksi yang kami jalankan di PT Abi Darma Sejahtra berangkat dari kebutuhan yang sangat membumi: memastikan calon klien di Karawang, Cikarang, dan Bekasi bisa melihat hasil kerja kami tanpa harus menunggu — bahkan ketika mereka membukanya dari HP di tengah kawasan industri dengan sinyal seadanya.

Kalau kamu mengelola situs dengan karakter serupa — dokumentasi lapangan, foto inventaris, arsip inspeksi — saya ingin tahu pendekatan apa yang kamu pakai. Tulis di kolom komentar.


Ditulis berdasarkan implementasi nyata pada situs perusahaan konstruksi di kawasan industri Jawa Barat. Angka pengukuran diambil dari Lighthouse (throttling 4G) dan data Real User Monitoring internal.

Top comments (0)