Bildkomprimierung optimieren: Website-Performance steigern (Core Web Vitals & WebP/JPEG)

1) So optimierst du Bildkomprimierung für maximale Website-Performance
Schritt 1: Miss den Status quo (Core Web Vitals + PageSpeed Insights)
Bevor du Bilder anfässt, prüfe, welche Metriken leiden: In PageSpeed Insights siehst du typischerweise Hinweise wie „Properly size images“, „Serve images in next-gen formats“ oder „Efficiently encode images“. Priorisiere nach Core Web Vitals:
Interessiert an diesem Thema?
Kontaktieren Sie uns für eine kostenlose Beratung →- LCP (Largest Contentful Paint): Häufig ist das LCP-Element ein großes Hero- oder Artikelbild. Datei- und Decodiergröße wirken direkt.
- INP (Interaction to Next Paint): Zu große Bilder können Main-Thread/Decode-Last erhöhen (besonders mobil), was Interaktion verzögert.
- CLS (Cumulative Layout Shift): Fehlende width/height bzw. falsches Responsive Sizing kann Layout-Sprünge verursachen.
Schritt 2: Wähle das richtige Format (JPEG vs. WebP) nach Bildtyp
- Fotos / komplexe Verläufe: JPEG oder WebP (lossy). WebP ist oft kleiner bei ähnlicher Qualität.
- Grafiken mit wenigen Farben / Text / UI: WebP (lossless) oder PNG; häufig ist WebP lossless kleiner als PNG.
- Transparenz: WebP unterstützt Alpha; JPEG nicht.
Schritt 3: Setze klare Ziele (Budget) für Gewicht & Qualität
- Hero/LCP-Bild: oft 80–200 KB (je nach Layout), als WebP/JPEG, korrekt dimensioniert.
- Thumbnails: 10–40 KB.
- Max Breite: nur so groß exportieren, wie im Layout wirklich benötigt (plus DPR-Varianten).
2) JPEG-Kompressionsalgorithmen: Was du wirklich optimieren kannst
JPEG ist ein verlustbehaftetes Verfahren, das (vereinfacht) so arbeitet: Farbraumumwandlung → ggf. Chroma Subsampling → Blockbildung (8×8) → DCT (Diskrete Kosinustransformation) → Quantisierung → Entropie-Codierung (Huffman/arithmetisch). Performance-relevant sind vor allem diese Stellschrauben:
Qualitätsfaktor (Quantisierung) richtig wählen
Der „Quality“-Wert steuert die Quantisierung: niedrigere Qualität = stärkere Quantisierung = weniger Details = kleinere Datei. Für Web-Content sind häufig Q 60–85 ein guter Bereich; Hero-Bilder eher höher, Thumbnails niedriger.
Chroma Subsampling (z. B. 4:2:0 vs. 4:4:4)
Da das menschliche Auge Helligkeitsdetails stärker wahrnimmt als Farbdetails, wird Farbe oft reduziert. 4:2:0 spart merklich Dateigröße, kann aber bei scharfer Typo/Charts Farb-Artefakte erzeugen. Für Fotos meist ok, für UI/Infografiken vorsichtig.
Progressive JPEG
Progressive JPEGs werden in mehreren „Scans“ aufgebaut und können gefühlt schneller laden (frühe Vorschau). Sie ändern nicht zwingend LCP, können aber subjektive Wahrnehmung verbessern und manchmal sogar kleiner sein. Teste das Ergebnis in PageSpeed Insights und im Real-User-Monitoring.
3) WebP Format: Warum es für Performance oft gewinnt
WebP bietet lossy und lossless Kompression und ist für viele Websites ein direktes „Win“ bei Download-Größe. In der Praxis bedeutet das:
- Weniger Transfer → schnellerer LCP (insb. mobil/3G/4G).
- Oft bessere Qualität pro Byte als JPEG bei Fotos.
- Transparenz möglich (gegenüber JPEG).
Wichtig: WebP kann eine andere Decoding-Charakteristik haben; selten kann extrem aggressives Encoding zu höherer CPU-Last führen. Deshalb nicht nur „kleinste Datei“ optimieren, sondern LCP/INP mitmessen.
4) Umsetzung: Responsive Images + richtige Auslieferung
Schritt 1: Immer korrekt dimensionieren (kein „Downscale im Browser“)
Erzeuge mehrere Breiten (z. B. 320/640/960/1280/1600) und liefere sie via srcset/sizes aus. Das reduziert Bytes massiv und verbessert LCP.
Schritt 2: Setze auf moderne Auslieferung (Content Negotiation)
Nutze WebP für unterstützende Browser, JPEG als Fallback. Auf Server/CDN-Ebene kann man basierend auf Accept-Header ausliefern. (Wenn du eine zentrale Landingpage zu deinen Maßnahmen brauchst, verlinke intern konsistent auf Performance Optimierung.)
Schritt 3: Lazy Loading nur unterhalb des Folds
Lazy Loading spart initiale Bandbreite, kann aber das LCP-Bild verschlechtern, wenn du es fälschlich lazy lädst. LCP-Asset: nicht lazy laden und ggf. priorisieren.
5) Automatisierung: Python Script für Image Compression
Das folgende Python-Skript nimmt Bilder aus einem Input-Ordner, erzeugt mehrere Größen und exportiert WebP sowie JPEG-Fallback. Es ist bewusst „produktionstauglich“: mit Fehlerbehandlung, sauberer Ordnerstruktur und konfigurierbaren Qualitäten.
Python Script for Image Compression
from __future__ import annotations
import os
from pathlib import Path
from typing import Iterable, Tuple
from PIL import Image, ImageOps
INPUT_DIR = Path("./images_in")
OUTPUT_DIR = Path("./images_out")
# Common responsive widths; adjust to your layout
WIDTHS = [320, 640, 960, 1280, 1600]
# Tuning knobs
WEBP_QUALITY = 78
JPEG_QUALITY = 82
JPEG_PROGRESSIVE = True
JPEG_SUBSAMPLING = "4:2:0" # often best for photos; use "4:4:4" for sharp text/graphics
def iter_images(folder: Path) -> Iterable[Path]:
exts = {".jpg", ".jpeg", ".png", ".tif", ".tiff"}
for p in folder.rglob("*"):
if p.is_file() and p.suffix.lower() in exts:
yield p
def ensure_dir(path: Path) -> None:
path.mkdir(parents=True, exist_ok=True)
def resize_to_width(img: Image.Image, target_width: int) -> Image.Image:
if img.width <= target_width:
return img.copy()
ratio = target_width / float(img.width)
target_height = int(img.height * ratio)
# High-quality downscale
return img.resize((target_width, target_height), resample=Image.Resampling.LANCZOS)
def normalize_mode(img: Image.Image) -> Image.Image:
# Fix common issues: orientation + mode
img = ImageOps.exif_transpose(img)
# Convert paletted or CMYK images to RGB for consistent exports
if img.mode in ("P", "CMYK"):
img = img.convert("RGB")
return img
def save_webp(img: Image.Image, out_path: Path, quality: int) -> None:
ensure_dir(out_path.parent)
# method=6 is slower but usually smaller
img.save(
out_path,
format="WEBP",
quality=quality,
method=6,
optimize=True,
)
def save_jpeg(img: Image.Image, out_path: Path, quality: int, progressive: bool, subsampling: str) -> None:
ensure_dir(out_path.parent)
# JPEG doesn't support alpha; flatten if needed
if img.mode in ("RGBA", "LA"):
background = Image.new("RGB", img.size, (255, 255, 255))
background.paste(img, mask=img.split()[-1])
img = background
elif img.mode != "RGB":
img = img.convert("RGB")
img.save(
out_path,
format="JPEG",
quality=quality,
optimize=True,
progressive=progressive,
subsampling=subsampling,
)
def build_outputs(input_file: Path, widths: Iterable[int]) -> None:
rel = input_file.relative_to(INPUT_DIR)
stem = rel.with_suffix("")
with Image.open(input_file) as img:
img = normalize_mode(img)
for w in widths:
resized = resize_to_width(img, w)
# webp
webp_path = OUTPUT_DIR / stem.parent / f"{stem.name}-{w}w.webp"
save_webp(resized, webp_path, WEBP_QUALITY)
# jpeg fallback
jpg_path = OUTPUT_DIR / stem.parent / f"{stem.name}-{w}w.jpg"
save_jpeg(resized, jpg_path, JPEG_QUALITY, JPEG_PROGRESSIVE, JPEG_SUBSAMPLING)
def main() -> None:
if not INPUT_DIR.exists():
raise SystemExit(f"Input folder not found: {INPUT_DIR.resolve()}")
ensure_dir(OUTPUT_DIR)
count = 0
for img_path in iter_images(INPUT_DIR):
try:
build_outputs(img_path, WIDTHS)
count += 1
except Exception as exc:
print(f"Failed: {img_path} -> {exc}")
print(f"Processed {count} image(s). Output: {OUTPUT_DIR.resolve()}")
if __name__ == "__main__":
main()
6) Runtime-Optimierung: Node.js Middleware for Image Optimization
Wenn du Bilder on-the-fly optimieren willst (z. B. bei CMS-Uploads oder dynamischen Größen), ist Middleware sinnvoll. Das Beispiel unten nutzt sharp, liest Parameter (w, q), verhandelt WebP/JPEG per Accept-Header, setzt Caching-Header und liefert die optimierte Datei aus.
Node.js Middleware for Image Optimization
import express from "express";
import path from "path";
import fs from "fs/promises";
import crypto from "crypto";
import sharp from "sharp";
const app = express();
const IMAGES_DIR = path.resolve("./public/images");
const CACHE_DIR = path.resolve("./.image-cache");
function clampInt(value, min, max, fallback) {
const n = Number.parseInt(value, 10);
if (Number.isNaN(n)) return fallback;
return Math.max(min, Math.min(max, n));
}
function acceptsWebp(req) {
const accept = req.headers.accept || "";
return accept.includes("image/webp");
}
function cacheKey(parts) {
return crypto.createHash("sha1").update(parts.join("|"), "utf8").digest("hex");
}
async function ensureDir(dir) {
await fs.mkdir(dir, { recursive: true });
}
app.get("/img/*", async (req, res) => {
try {
const relPath = req.params[0] || "";
const sourcePath = path.join(IMAGES_DIR, relPath);
// Basic path traversal guard
if (!sourcePath.startsWith(IMAGES_DIR)) {
return res.status(400).send("Bad request");
}
const width = clampInt(req.query.w, 160, 2400, 960);
const quality = clampInt(req.query.q, 40, 90, 78);
const useWebp = acceptsWebp(req);
const format = useWebp ? "webp" : "jpeg";
await ensureDir(CACHE_DIR);
const key = cacheKey([relPath, String(width), String(quality), format]);
const cachedPath = path.join(CACHE_DIR, `${key}.${format}`);
// Serve from cache if present
try {
const file = await fs.readFile(cachedPath);
res.setHeader("Content-Type", useWebp ? "image/webp" : "image/jpeg");
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
res.setHeader("Vary", "Accept");
return res.status(200).send(file);
} catch {
// Cache miss: continue
}
const inputBuffer = await fs.readFile(sourcePath);
let pipeline = sharp(inputBuffer, { failOn: "none" }).rotate();
pipeline = pipeline.resize({ width, withoutEnlargement: true });
if (useWebp) {
pipeline = pipeline.webp({ quality, effort: 5 });
} else {
pipeline = pipeline.jpeg({ quality, progressive: true, mozjpeg: true });
}
const outputBuffer = await pipeline.toBuffer();
await fs.writeFile(cachedPath, outputBuffer);
res.setHeader("Content-Type", useWebp ? "image/webp" : "image/jpeg");
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
res.setHeader("Vary", "Accept");
return res.status(200).send(outputBuffer);
} catch (err) {
return res.status(404).send("Not found");
}
});
app.listen(3000, () => {
console.log("Image optimizer running on http://localhost:3000");
});
7) Checkliste: Was sich am stärksten auf Core Web Vitals auswirkt
| Feature | Details |
|---|---|
| Richtiges Sizing | Mehrere Breiten erzeugen (srcset/sizes), keine Oversized-Downloads; wirkt direkt auf LCP/Transfer. |
| WebP-Auslieferung | Für Fotos meist deutlich weniger KB als JPEG; Fallback via Accept/Vary. |
| JPEG-Parameter | Quality + Subsampling + Progressive JPEG optimieren; Artefakte gegen Dateigröße abwägen. |
| Caching | Immutable + lange TTL für versionierte Assets; reduziert Wiederhol-Downloads und stabilisiert UX. |
| Lazy Loading | Nur unterhalb des Folds; LCP-Asset nicht lazy laden. |
| Messung | PageSpeed Insights + Real User Monitoring, um LCP/INP/CLS und Bild-Hinweise zu validieren. |
8) Ablaufdiagramm: Optimierungs-Pipeline
flowchart TD
A[PageSpeed Insights / RUM Daten] --> B{LCP Bild identifizieren}
B --> C[Dimensionen & srcset planen]
C --> D[Batch-Encode: WebP + JPEG]
D --> E{Auslieferung}
E -->|Accept: image/webp| F[WebP liefern]
E -->|Fallback| G[JPEG liefern]
F --> H[Cache: immutable + Vary: Accept]
G --> H
H --> I[Core Web Vitals erneut messen]


