Advanced (Diperbarui: 2/6/2026)

Membangun Pipeline Optimasi Gambar dengan Claude Code

Otomatiskan WebP/AVIF, gambar responsif, dan pemeriksaan budget CI dengan Claude Code.

Membangun Pipeline Optimasi Gambar dengan Claude Code

Optimasi gambar bukan sekadar mengompresi folder sebelum rilis. Saat sebuah situs memiliki hero image, screenshot artikel, thumbnail produk, diagram, dan gambar social preview, proses manual mudah terlewat. Satu PNG besar yang tidak diproses bisa menjadi elemen Largest Contentful Paint dan membuat seluruh halaman terasa lambat.

Dalam panduan ini, Claude Code dipakai sebagai partner implementasi untuk membuat pipeline yang bisa diulang. sharp membuat varian AVIF, WebP, dan JPEG; komponen responsif berbasis picture mengirimkan kandidat yang tepat ke browser; lalu pemeriksaan CI menolak output yang melewati budget ukuran. Tujuannya bukan file paling kecil dengan kualitas rusak, tetapi kualitas yang masih terbaca, fallback yang aman, penamaan yang rapi, dan proses review yang jelas.

Percobaan pertama Masa di blog teknis kecil terlalu sederhana: “buat AVIF saja”. Ukuran turun, tetapi beberapa crawler tetap membutuhkan JPEG, screenshot kode menjadi buram saat kualitas terlalu rendah, dan hero image tidak sengaja dibuat lazy load. Alur baru menjadi stabil setelah konversi, rendering, dan verifikasi dipisahkan.

Jika masih baru memakai Claude Code, mulai dari panduan awal Claude Code. Untuk optimasi performa di luar gambar, baca juga optimasi performa dengan Claude Code.

Gambaran pipeline

Jangan hanya meminta Claude Code “buat gambar lebih cepat”. Beri alur kerja yang jelas agar setiap file punya tanggung jawab tunggal.

flowchart LR
  A["original images"] --> B["sharp conversion"]
  B --> C["AVIF / WebP / JPEG variants"]
  C --> D["OptimizedImage component"]
  D --> E["browser chooses best source"]
  C --> F["manifest.json"]
  F --> G["CI size budget check"]

Script konversi membuat varian yang deterministik. Komponen memberi tahu browser kandidat yang tersedia. Pemeriksaan budget mencegah gambar berat masuk ke production. Pembagian ini juga membuat hasil Claude Code lebih mudah direview karena setiap perubahan memiliki ruang lingkup kecil.

Tentukan aturan kualitas sebelum coding

Kesalahan umum adalah memakai satu angka kualitas untuk semua gambar. Foto, screenshot UI, diagram, dan OGP image punya risiko degradasi yang berbeda. Sebelum meminta kode, saya biasanya memberi Claude Code aturan seperti ini.

KebutuhanTargetCatatan review
Hero image1280px atau lebih, AVIF/WebP dulu, JPEG fallbackKandidat LCP, perlu priority
Screenshot artikelVarian 640px/960pxTeks kecil harus tetap terbaca
Galeri/daftarVarian 320px/640pxLazy load untuk gambar di bawah fold
Social previewSimpan JPEG atau PNGBeberapa crawler belum nyaman dengan format modern

Untuk pembaruan Juni 2026 ini, referensi utama format adalah dokumentasi resmi sharp. Di sisi HTML, ikuti panduan responsive images MDN: srcset hanya efektif jika sizes menjelaskan lebar render yang sebenarnya.

Implementasi 1: membuat varian dengan sharp

Script berikut membaca jpg, jpeg, dan png dari public/images/original, menulis hasil ke public/images/optimized, dan membuat manifest.json untuk pemeriksaan CI.

npm install -D sharp glob tsx
// scripts/optimize-images.ts
import path from "node:path";
import { mkdir, writeFile } from "node:fs/promises";
import { glob } from "glob";
import sharp from "sharp";

const inputDir = process.argv[2] ?? "public/images/original";
const outputDir = process.argv[3] ?? "public/images/optimized";
const widths = [320, 640, 960, 1280, 1920] as const;
const formats = ["avif", "webp", "jpeg"] as const;
const quality = { avif: 52, webp: 76, jpeg: 82 } as const;

type ImageFormat = (typeof formats)[number];
type ManifestEntry = {
  src: string;
  width: number;
  format: string;
  bytes: number;
};

const manifest: Record<string, ManifestEntry[]> = {};

function slugFromPath(filePath: string) {
  const relative = path.relative(inputDir, filePath);
  return relative
    .replace(path.extname(relative), "")
    .split(path.sep)
    .join("-")
    .replace(/[^a-zA-Z0-9_-]/g, "-")
    .toLowerCase();
}

function extension(format: ImageFormat) {
  return format === "jpeg" ? "jpg" : format;
}

async function buildVariant(filePath: string, slug: string, width: number, format: ImageFormat) {
  let image = sharp(filePath).rotate().resize({ width, withoutEnlargement: true });

  if (format === "avif") image = image.avif({ quality: quality.avif, effort: 4 });
  if (format === "webp") image = image.webp({ quality: quality.webp, effort: 4 });
  if (format === "jpeg") image = image.jpeg({ quality: quality.jpeg, mozjpeg: true });

  const fileName = `${slug}-${width}w.${extension(format)}`;
  const target = path.join(outputDir, fileName);
  const info = await image.toFile(target);

  return {
    src: `/images/optimized/${fileName}`,
    width: info.width,
    format: extension(format),
    bytes: info.size,
  };
}

async function optimizeOne(filePath: string) {
  const metadata = await sharp(filePath).metadata();
  const sourceWidth = metadata.width ?? widths[widths.length - 1];
  const targetWidths: number[] = widths.filter((width) => width <= sourceWidth);

  if (!targetWidths.includes(sourceWidth)) targetWidths.push(sourceWidth);
  targetWidths.sort((a, b) => a - b);

  const slug = slugFromPath(filePath);
  manifest[slug] = [];

  for (const width of targetWidths) {
    for (const format of formats) {
      manifest[slug].push(await buildVariant(filePath, slug, width, format));
    }
  }

  console.log(`optimized ${slug}: ${manifest[slug].length} files`);
}

async function main() {
  await mkdir(outputDir, { recursive: true });

  const pattern = `${inputDir.replace(/\\/g, "/")}/**/*.{jpg,jpeg,png}`;
  const files = await glob(pattern, { nodir: true });

  for (const filePath of files) {
    await optimizeOne(filePath);
  }

  await writeFile(
    path.join(outputDir, "manifest.json"),
    JSON.stringify(manifest, null, 2),
  );

  console.log(`done: ${files.length} source images`);
}

void main().catch((error) => {
  console.error(error);
  process.exit(1);
});

Bagian pentingnya adalah tidak memperbesar gambar kecil. Jika screenshot 900px diberi nama 1280w, proses debugging berikutnya akan membingungkan. Manifest menyimpan lebar aktual dan ukuran byte, sehingga reviewer bisa melihat hasil nyata.

Implementasi 2: komponen gambar responsif

Setelah file dibuat, kita perlu mengirimkan kandidat yang benar ke browser. Contoh ini untuk React; di Astro, struktur picture, source, dan img yang sama bisa digunakan langsung.

// src/components/OptimizedImage.tsx
import type { ImgHTMLAttributes } from "react";

type OptimizedImageProps = Omit<
  ImgHTMLAttributes<HTMLImageElement>,
  "src" | "srcSet" | "sizes" | "width" | "height" | "loading"
> & {
  slug: string;
  alt: string;
  width: number;
  height: number;
  widths?: number[];
  sizes?: string;
  priority?: boolean;
};

function srcSet(slug: string, widths: number[], extension: "avif" | "webp" | "jpg") {
  return widths
    .map((width) => `/images/optimized/${slug}-${width}w.${extension} ${width}w`)
    .join(", ");
}

export function OptimizedImage({
  slug,
  alt,
  width,
  height,
  widths = [320, 640, 960, 1280],
  sizes = "(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 960px",
  priority = false,
  className,
  ...imgProps
}: OptimizedImageProps) {
  const fallbackWidth = widths.includes(960) ? 960 : widths[Math.floor(widths.length / 2)];
  const priorityProps = priority
    ? ({ fetchPriority: "high" } as ImgHTMLAttributes<HTMLImageElement>)
    : {};

  return (
    <picture className={className}>
      <source type="image/avif" srcSet={srcSet(slug, widths, "avif")} sizes={sizes} />
      <source type="image/webp" srcSet={srcSet(slug, widths, "webp")} sizes={sizes} />
      <img
        src={`/images/optimized/${slug}-${fallbackWidth}w.jpg`}
        srcSet={srcSet(slug, widths, "jpg")}
        sizes={sizes}
        width={width}
        height={height}
        alt={alt}
        loading={priority ? "eager" : "lazy"}
        decoding={priority ? "sync" : "async"}
        {...priorityProps}
        {...imgProps}
      />
    </picture>
  );
}

Gunakan priority hanya untuk hero image atau gambar besar yang langsung terlihat. Jika semua gambar eager, jaringan akan diperebutkan oleh CSS, JavaScript, font, dan kandidat LCP sebenarnya. Panduan LCP dari web.dev membantu menentukan prioritas.

Implementasi 3: budget gambar di CI

Pipeline belum lengkap jika tidak bisa gagal otomatis. Reviewer biasanya melihat layout, tetapi jarang memeriksa ukuran setiap output. Script berikut membaca manifest dan menghentikan build jika varian besar melewati batas.

// scripts/check-image-budget.mjs
import { readFile } from "node:fs/promises";

const manifestUrl = new URL("../public/images/optimized/manifest.json", import.meta.url);
const manifest = JSON.parse(await readFile(manifestUrl, "utf8"));
const maxBytes = Number(process.env.IMAGE_BUDGET_BYTES ?? 240_000);
const failures = [];

for (const [slug, entries] of Object.entries(manifest)) {
  for (const entry of entries) {
    const isLargeCandidate = entry.width >= 1280 && ["avif", "webp", "jpg"].includes(entry.format);
    if (isLargeCandidate && entry.bytes > maxBytes) {
      failures.push(`${slug} ${entry.width}w.${entry.format}: ${entry.bytes} bytes`);
    }
  }
}

if (failures.length > 0) {
  console.error(`Image budget exceeded. Limit: ${maxBytes} bytes`);
  for (const failure of failures) console.error(`- ${failure}`);
  process.exit(1);
}

console.log("Image budget check passed.");
{
  "scripts": {
    "images:build": "tsx scripts/optimize-images.ts",
    "images:check": "node scripts/check-image-budget.mjs"
  }
}

Mulailah dengan batas sederhana, misalnya 240KB. Setelah melihat output nyata, pisahkan budget untuk hero, screenshot, dan thumbnail.

Tiga use case praktis

Use case pertama adalah blog teknis. Screenshot sering berupa PNG besar dari layar resolusi tinggi. Jika Claude Code diberi tahu lebar konten, layout mobile, dan kebutuhan teks kecil tetap terbaca, hasil sizes dan quality lebih tepat.

Use case kedua adalah landing page SaaS. Screenshot produk atau hero image sering menjadi LCP. Gambar itu perlu width, height, priority loading, dan fallback; gambar lain tetap lazy.

Use case ketiga adalah galeri ecommerce atau portofolio. Satu original dapat muncul di card, detail page, carousel, dan OGP. Manifest membantu test dan admin mengetahui varian mana yang tersedia.

Kesalahan yang sering terjadi

Jangan menurunkan kualitas AVIF terlalu jauh. Foto mungkin masih terlihat baik, tetapi screenshot UI dengan teks kecil bisa sulit dibaca.

Jangan menghilangkan sizes. Tanpa itu, browser bisa menganggap gambar memenuhi seluruh viewport dan mengunduh file yang terlalu besar.

Jangan lazy-load hero image. Lazy loading berguna untuk konten di bawah fold, tetapi buruk untuk elemen utama yang terlihat sejak awal.

Jangan meminta Claude Code mengerjakan CDN upload, admin UI, migrasi framework, dan konversi gambar dalam satu prompt. Mulai dari script, lalu komponen, lalu CI check.

Prompt untuk Claude Code

Create an image optimization script for jpg/png files in public/images/original.
Output files to public/images/optimized.
Generate 320, 640, 960, 1280, and 1920px widths in avif, webp, and jpg.
Do not generate a width larger than the original image.
Write manifest.json with src, width, format, and bytes.
Add package scripts named images:build and images:check.
Keep the diff minimal and do not touch unrelated files.

Prompt ini menjelaskan path, format, batasan, metadata, dan ruang lingkup. Makin jelas batasnya, makin mudah hasil Claude Code direview.

Hasil verifikasi

Dalam uji Masa, mengganti screenshot PNG 1920px mentah dengan pipeline ini menurunkan transfer gambar artikel menjadi kurang dari setengah. Percobaan yang gagal adalah memakai AVIF quality 45 untuk screenshot kode: byte lebih kecil, tetapi teks tampak lembut. Konfigurasi stabilnya adalah AVIF low-50 untuk foto, cek visual WebP/JPEG untuk screenshot UI, dan priority hanya untuk hero image.

Langkah berikutnya adalah menjalankan npm run images:build dan npm run images:check pada satu kategori gambar. Setelah stabil, hubungkan dengan otomasi workflow Claude Code agar regresi gambar tertangkap di Pull Request.

#Claude Code #optimasi gambar #WebP #AVIF #performance
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.