Advanced (Aktualisiert: 2.6.2026)

Eine Bildoptimierungs-Pipeline mit Claude Code bauen

Automatisiere WebP/AVIF, responsive Bilder und CI-Budgets mit Claude Code.

Eine Bildoptimierungs-Pipeline mit Claude Code bauen

Bildoptimierung ist mehr als ein Komprimierungslauf kurz vor dem Release. Sobald eine Website Hero-Bilder, Screenshots in Artikeln, Produkt-Thumbnails, Diagramme und Social-Preview-Bilder enthält, wird Handarbeit unzuverlässig. Ein einziges unbearbeitetes PNG kann zum Largest-Contentful-Paint-Element werden und die wahrgenommene Geschwindigkeit der ganzen Seite verschlechtern.

In diesem Leitfaden nutzen wir Claude Code als Implementierungspartner für eine wiederholbare Pipeline. sharp erzeugt AVIF-, WebP- und JPEG-Varianten, eine responsive picture-Komponente liefert sie an den Browser aus, und ein CI-Check stoppt zu große Dateien. Es geht nicht darum, jede Datei maximal klein zu machen. Es geht um lesbare Qualität, Browser-Fallbacks, stabile Dateinamen und einen Review-Prozess, der Probleme sichtbar macht.

Masas erster Versuch in einem kleinen Technikblog war zu grob: “AVIF erzeugen und fertig.” Die Bytes wurden weniger, aber manche Crawler brauchten weiterhin JPEG, Code-Screenshots wurden bei zu niedriger Qualität unscharf, und das Hero-Bild war versehentlich lazy-loaded. Erst die Trennung von Konvertierung, Darstellung und Prüfung machte den Ablauf wartbar.

Wenn du Claude Code noch nicht sicher nutzt, lies zuerst den Claude Code Einstieg. Für breitere Performance-Arbeit passt dazu Performance-Optimierung mit Claude Code.

Pipeline im Überblick

Bitte Claude Code nicht nur um “schnellere Bilder”. Gib einen konkreten Datenfluss vor, damit jede Datei eine klare Aufgabe hat.

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"]

Das Konvertierungsskript erzeugt deterministische Varianten. Die Komponente beschreibt dem Browser die verfügbaren Kandidaten. Der Budget-Check verhindert, dass schwere Bilder veröffentlicht werden. Diese Aufteilung macht auch Claude Code besser kontrollierbar, weil jede Anfrage einen kleineren, prüfbaren Diff erzeugt.

Qualitätsregeln vor dem Code

Ein häufiger Fehler ist ein einziger Qualitätswert für alle Bilder. Fotos, UI-Screenshots, Diagramme und OGP-Bilder haben unterschiedliche Schwachstellen. Vor dem Coding gebe ich Claude Code normalerweise solche Regeln.

EinsatzZielReview-Punkt
Hero-Bildmindestens 1280px, AVIF/WebP zuerst, JPEG als FallbackLCP-Kandidat, daher priorisiert laden
Artikel-ScreenshotVarianten mit 640px/960pxkleiner UI-Text muss lesbar bleiben
Galerie oder ListeVarianten mit 320px/640pxunterhalb des ersten Viewports lazy loaden
Social PreviewJPEG oder PNG behaltenKompatibilität mit älteren Crawlern

Für diese Aktualisierung im Juni 2026 ist die offizielle sharp-Dokumentation die wichtigste Referenz für Formate. Für HTML solltest du die MDN-Anleitung zu responsiven Bildern beachten: srcset hilft nur dann wirklich, wenn sizes die tatsächliche Darstellungsbreite beschreibt.

Implementierung 1: Varianten mit sharp erzeugen

Dieses Skript liest jpg, jpeg und png aus public/images/original, schreibt optimierte Dateien nach public/images/optimized und erzeugt ein manifest.json für spätere Prüfungen.

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);
});

Der wichtigste Punkt: kleine Originale werden nicht hochskaliert. Wenn ein 900px-Screenshot als 1280w benannt wird, ist spätere Fehlersuche unnötig schwer. Das Manifest speichert echte Breite und Dateigröße, damit der Review auf Daten basiert.

Implementierung 2: Responsive Komponente

Nach der Erzeugung brauchen wir eine Komponente, die dem Browser die Kandidaten korrekt anbietet. Dieses Beispiel ist für React geschrieben; in Astro kannst du dieselbe Struktur aus picture, source und img direkt verwenden.

// 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>
  );
}

Setze priority nur für das wichtigste Bild im ersten Viewport. Wenn alle Bilder eager laden, konkurrieren sie mit CSS, JavaScript, Fonts und dem eigentlichen LCP-Element. Der LCP-Leitfaden von web.dev hilft bei dieser Entscheidung.

Implementierung 3: CI-Budget prüfen

Eine Pipeline ist erst vollständig, wenn sie automatisch scheitern kann. Reviews finden Layoutprobleme, aber nicht jede Dateigröße. Das folgende Skript liest das Manifest und stoppt den Build bei zu schweren Varianten.

// 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"
  }
}

Starte mit einer einfachen Grenze, etwa 240KB. Sobald echte Ausgabedaten vorliegen, kannst du getrennte Budgets für Hero-Bilder, Screenshots und Thumbnails definieren.

Drei praktische Einsatzfälle

Erstens: der Technikblog. Screenshots kommen oft als große PNGs aus hochauflösenden Displays. Wenn Claude Code die Content-Breite, mobile Darstellung und Lesbarkeit kleiner Schrift kennt, werden sizes und Qualitätswerte realistischer.

Zweitens: die SaaS-Landingpage. Das Produktbild im Hero ist oft der LCP-Kandidat. Es braucht width, height, priorisiertes Laden und klare Fallbacks; alle späteren Bilder bleiben lazy.

Drittens: E-Commerce- oder Portfolio-Galerien. Dasselbe Original erscheint in Karten, Detailansichten, Karussells und OGP-Bildern. Das Manifest zeigt, welche Varianten existieren, und hilft bei Tests.

Häufige Fallen

Drücke AVIF-Qualität nicht zu weit herunter. Fotos können noch gut wirken, während UI-Screenshots unlesbar werden.

Lasse sizes nicht weg. Ohne diese Angabe kann der Browser annehmen, dass das Bild die ganze Viewport-Breite einnimmt, und zu große Varianten laden.

Setze das Hero-Bild nicht auf lazy loading. Lazy loading ist für Inhalte unterhalb des ersten Viewports sinnvoll, aber nicht für das sichtbare Hauptelement.

Bitte Claude Code nicht in einem Prompt um CDN-Upload, Admin-Oberfläche, Framework-Migration und Bildkonvertierung. Erst Script, dann Komponente, dann CI-Check: so bleiben Reviews belastbar.

Claude-Code-Prompt

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.

Dieser Prompt legt Pfade, Formate, Regeln, Metadaten und Grenzen fest. Je klarer die Grenzen sind, desto einfacher lässt sich das Ergebnis von Claude Code prüfen.

Verifiziertes Ergebnis

In Masas Test sank der Bildtransfer in Artikeln auf weniger als die Hälfte, nachdem rohe 1920px-PNG-Screenshots durch diese Pipeline ersetzt wurden. Der Fehlschlag war AVIF-Qualität 45 bei Code-Screenshots: weniger Bytes, aber weicher Text. Stabil war AVIF im niedrigen 50er-Bereich für Fotos, zusätzliches Sichtprüfen von WebP/JPEG für UI-Screenshots und priority nur für das Hero-Bild.

Der nächste Schritt ist, npm run images:build und npm run images:check zunächst für eine Bildkategorie auszuführen. Wenn der Ablauf stabil ist, verbinde ihn mit Claude Code Workflow-Automatisierung, damit Pull Requests Bildregressionen früh melden.

#Claude Code #Bildoptimierung #WebP #AVIF #Performance
Kostenlos

Kostenloses PDF: Claude-Code-Cheatsheet

E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.

Wir schützen Ihre Daten und senden keinen Spam.

Masa

Über den Autor

Masa

Engineer für praktische Claude-Code-Workflows und Team-Einführung.