Bildoptimierung

Leg ein Bild in voller Größe in src/ oder public/ ab, richte ein <img> darauf, und sitelo skaliert es, wandelt es in ein modernes Format um und schreibt das Tag mit einem srcset neu. Nichts zu importieren, keine Komponente zu lernen.

Aktivieren

export default {
  images: true,
}

Die Kodierung übernimmt sharp, eine optionale Peer-Dependency — installiere sie zusammen mit sitelo, wenn du Bilder aktivierst:

npm install -D sharp

Websites, die auf Bildoptimierung verzichten, brauchen sie nie zu installieren.

Schreib ein ganz normales <img>

import { html, body, img } from 'javascript-to-html'

export default () =>
  html({ lang: 'de' },
    body(
      img({ src: '/images/hero.png', alt: 'Sonnenaufgang über dem Hafen' }),
    ),
  )

Aus einer Quelle mit 3000×2000 wird auf der anderen Seite das hier:

<img src="/assets/img/hero.a1b2c3d4-3000.webp"
     alt="Sonnenaufgang über dem Hafen"
     sizes="100vw"
     width="3000" height="2000"
     loading="lazy" decoding="async"
     srcset="/assets/img/hero.9f8e7d6c-400.webp 400w,
             /assets/img/hero.5b4a3c2d-800.webp 800w,
             /assets/img/hero.7c6d5e4f-1200.webp 1200w,
             /assets/img/hero.a1b2c3d4-3000.webp 3000w">

Ein Bildschirm bis 400px Breite bekommt die 400er-Datei, bis 800 die 800er, bis 1200 die 1200er und alles Breitere die vollen 3000 — der Browser nimmt die erste Stufe, die den Viewport abdeckt (auf einem 2×-Display verdoppelt).

Was du bekommst

Das Umschreiben läuft über das gebaute HTML und erfasst damit Bilder aus src/ und public/ gleichermaßen.

Sobald ein Tag umgeschrieben ist, zeigt nichts mehr auf das Original, also wird es standardmäßig aus dem Build entfernt. Nur wirklich unreferenzierte Dateien gehen: jede HTML-, CSS-, JS-, XML- und JSON-Datei im Build wird durchsucht, ein Original aus einem <a href>, einem og:image, einem RSS-Enclosure, einem CSS-url() oder einem data-no-optimize-Tag bleibt also. Zur Laufzeit in einem Skript zusammengesetzte oder von einer Server-Island gerenderte URLs sieht es nicht — setze prune: false, wenn du solche hast.

Optionen

export default {
  images: {
    widths: [400, 800, 1200],
    formats: ['avif', 'webp'],
    quality: { avif: 55, webp: 78, jpeg: 82 },
    exclude: ['**/og/**'],
  },
}

Zwei Formate ergeben <picture>

AVIF ist kleiner, aber jünger als WebP — beide anzugeben lässt den Browser wählen und behält einen Rückfall für ältere Browser.

<picture>
  <source type="image/avif" srcset="/assets/img/hero.*-400.avif 400w, ..." sizes="...">
  <source type="image/webp" srcset="/assets/img/hero.*-400.webp 400w, ..." sizes="...">
  <img src="/assets/img/hero.*-3000.png" alt="..." srcset="..." width="3000" height="2000">
</picture>

Eine Größe, beim Namen genannt

Karussell-Thumbnails, Avatare, ein Raster aus Karten — manche Bilder werden immer nur in einer Größe gezeigt, und eine ganze Leiter ist für sie verschwendet. Die Dateinamen der Varianten tragen einen Content-Hash, du kannst sie also nicht von Hand schreiben; nenne stattdessen die Breite in der URL, und das Tag kommt mit genau dieser einen Datei heraus:

<img src="/images/hero.png?w=400" alt="Sonnenaufgang über dem Hafen">
<img src="/images/hero.png?w=400&format=jpeg" alt="Sonnenaufgang über dem Hafen">
<img src="/images/hero.png?w=200&h=200" alt="Sonnenaufgang über dem Hafen">
<img src="/images/hero.png?w=200&h=200&fit=contain" alt="Sonnenaufgang über dem Hafen">
<img src="/images/hero.png?w=200&h=200&background=fff" alt="Sonnenaufgang über dem Hafen">
<img src="/images/hero.png?w=200&h=200&position=top" alt="Sonnenaufgang über dem Hafen">
<img src="/assets/img/hero.9f8e7d6c-400.webp"
     alt="Sonnenaufgang über dem Hafen"
     width="400" height="267"
     loading="lazy" decoding="async">

Sind mehrere formats konfiguriert, liefert eine feste Breite weiterhin ein <picture> mit einer Datei pro <source>. Die Namen sind die von vite-imagetools. Entfernte URLs werden nie geparst — ihr Query-String gehört dem Ursprung — und ist images aus, ignorieren statische Hosts den Query und liefern das Original.

Das Bild den Zuschnitt wählen lassen

Eine Kante oder Ecke ist vorhersehbar, aber in einem Karussell gemischter Fotos sitzt das Motiv selten zweimal an derselben Stelle. entropy und attention lassen sharp jedes Bild ansehen und das Zuschnittfenster dorthin schieben, wo es etwas Behaltenswertes findet:

<img src="/images/hero.png?w=300&h=300&position=entropy" alt="Sonnenaufgang über dem Hafen">
<img src="/images/team.jpg?w=300&h=300&position=attention" alt="Das Team">

Beides sind einfache Heuristiken — kein Modell, kein Training —, also eher ein sinnvoller Standard für viele Bilder als eine Garantie für eines. Bei einem Hero-Bild, das dir wichtig ist, nenne die Kante. Die Wahl hängt nur vom Bild ab, ist also deterministisch und wird wie jede andere Variante gecacht.

Ausnahmen

Tags, die bereits ein srcset haben, in einem <picture> stecken oder auf ein SVG, ein animiertes GIF oder eine entfernte URL zeigen, bleiben unangetastet. Für alles andere sag es ausdrücklich:

<img src="/images/exact.png" alt="Pixel-Art" data-no-optimize>

Bilder für Social Cards und Favicons stehen in <meta> und <link>, die hiervon nie umgeschrieben werden — sie behalten ihre feste URL.

Entfernte Bilder

Aus einem CMS importierte Inhalte zeigen oft auf den Server von jemand anderem. Aktiviere remote, und diese Bilder werden heruntergeladen, optimiert und von deiner eigenen Domain ausgeliefert:

export default {
  images: {
    remote: true,
  },
}

Ein fehlgeschlagener Abruf lässt das Tag exakt so, wie es war, samt Warnung — eine wackelige Quelle bringt deinen Build nie zu Fall. prune entfernt anschließend lokale Originale, die nichts mehr referenziert.