Bilder in React kommen entweder als Import aus dem /src-Verzeichnis (vom Bundler verarbeitet, mit hash-basiertem Cache-Busting) oder als direkter Pfad aus dem /public-Verzeichnis (unverändert ausgeliefert). Die Wahl bestimmt, wie der Bundler die Datei behandelt, wie sich der finale URL-Pfad zusammensetzt und wie Caching greift. Dazu kommen die üblichen HTML-Themen: das alt-Attribut für Accessibility, loading="lazy" für Performance, srcset für responsive Bilder, und das Behandeln defekter Bilder mit onError.

Zwei wichtige Regeln

  • Der <img />-Tag muss in JSX selbstschließend sein (oder mit explizitem </img>-Close — selbstschließend ist üblicher).
  • Lokale Bilder aus /src müssen importiert werden, damit der Build-Prozess sie erkennt und in der finalen dist/-Struktur korrekt platziert.
TypeScript MinimalImage.jsx
import bild from './assets/foto.jpg';

const MinimalImage = () => {
    return <img src={bild} alt="Beispielfoto" />;
};

Import aus /src — vom Bundler verarbeitet

Build-Tools wie Vite, Webpack oder Parcel transformieren Bilder, die per import referenziert werden: sie kopieren die Datei in den Output-Ordner, hängen einen Content-Hash an den Dateinamen (foto.a1b2c3.jpg) und ersetzen den Import-Wert durch den finalen URL.

TypeScript ImageImport.jsx
import sampleImage from '../../assets/sample_image.jpg';

const ImageImport = () => {
    return (
        <div className="sample-image-wrapper">
            <img src={sampleImage} alt="Beispielbild" />
        </div>
    );
};

export default ImageImport;

Vorteile dieses Pattern:

  • Cache-Busting: Hash im Dateinamen → Browser lädt das Bild bei Änderungen neu.
  • Tree-Shaking: Nicht importierte Bilder landen nicht im Bundle.
  • Inline für kleine Bilder: Bei Bedarf inlinet der Bundler kleine Bilder als Data-URLs (konfigurierbar).

Die generierten Dateinamen ändern sich pro Build:

React Component - Generated file names

Direkter Pfad aus /public

Dateien im public/-Ordner werden unverändert in das Build-Output kopiert. Der Pfad ist absolut und stabil — kein Import nötig.

TypeScript ImagePublic.jsx
const ImagePublic = () => {
    return <img src="/images/react_project.jpg" alt="React Projekt" />;
};

Wann /public statt /src?

  • Bilder, die dynamisch per externer URL/CMS gewählt werden und nicht vorab bekannt sind.
  • Dateien, die mit stabilem Pfad referenziert werden müssen (z.B. og:image-Meta-Tags, favicon).
  • Assets, die explizit ohne Hash ausgeliefert werden sollen (z.B. robots.txt, sitemap.xml).

Nachteil: kein Cache-Busting bei Änderungen — Browser cachen das alte Bild ggf. lange.

alt-Attribut — Accessibility-Pflicht

Jedes <img> braucht ein alt-Attribut. Es beschreibt das Bild für Screen-Reader und wird angezeigt, wenn das Bild nicht lädt.

TypeScript Alt.jsx
// Inhaltliches Bild: aussagekräftiges alt
<img src={logo} alt="mibeon Logo" />

// Dekoratives Bild: leerer alt — Screen-Reader überspringt
<img src={ornament} alt="" />

ESLint mit eslint-plugin-jsx-a11y warnt bei fehlendem alt. Faustregel:

  • Inhaltliches Bild: kurze, aussagekräftige Beschreibung.
  • Dekoratives Bild: alt="" (leer, aber vorhanden).
  • Bild als Link: das alt beschreibt das Ziel des Links.

Lazy Loading

Mit loading="lazy" lädt der Browser ein Bild erst, wenn es in den Viewport scrollt — Performance-Boost auf bilderlastigen Seiten.

TypeScript Lazy.jsx
const Gallery = ({ images }) => {
    return (
        <div>
            {images.map((img) => (
                <img
                    key={img.id}
                    src={img.url}
                    alt={img.title}
                    loading="lazy"
                    width={400}
                    height={300}
                />
            ))}
        </div>
    );
};

width/height sind bei lazy-loaded Bildern wichtig — sie reservieren den Platz im Layout und vermeiden Cumulative Layout Shift (CLS, ein Core Web Vital).

Responsive Bilder mit srcset

Für mobile-vs-desktop unterschiedliche Bilder liefert srcset dem Browser eine Auswahl, aus der er die passende Auflösung wählt.

TypeScript Responsive.jsx
import small from './hero-small.jpg';
import medium from './hero-medium.jpg';
import large from './hero-large.jpg';

const Hero = () => {
    return (
        <img
            src={medium}
            srcSet={`${small} 480w, ${medium} 800w, ${large} 1200w`}
            sizes="(max-width: 600px) 480px, (max-width: 1024px) 800px, 1200px"
            alt="Hero-Bild"
        />
    );
};

Beachte: in JSX heißt das Attribut srcSet (camelCase), nicht srcset. Build-Tools können das srcset auch automatisch generieren (z.B. vite-imagetools).

Dynamische Bilder — Import in der Schleife

Wenn die Datei-Namen erst zur Laufzeit feststehen, kann man Bilder nicht per import einbinden. Drei Optionen:

TypeScript DynamicImports.jsx
// Option 1: alle Bilder vorab importieren, dann per Schlüssel auswählen
import imgA from './a.jpg';
import imgB from './b.jpg';
const imageMap = { a: imgA, b: imgB };

const ImageA = ({ name }) => <img src={imageMap[name]} alt="" />;

// Option 2: import.meta.glob (Vite-spezifisch) — alle passenden Dateien auf einmal
const modules = import.meta.glob('./images/*.jpg', { eager: true });
const images = Object.fromEntries(
    Object.entries(modules).map(([path, mod]) => [path, mod.default])
);

// Option 3: /public und direkter String-Pfad
const PublicImage = ({ name }) => (
    <img src={`/images/${name}.jpg`} alt="" />
);

Variante 1 funktioniert mit beliebigem Bundler. Variante 2 ist Vite-spezifisch. Variante 3 verzichtet auf Bundler-Optimierung, aber ist einfach.

Defekte Bilder behandeln

Mit onError lässt sich auf Lade-Fehler reagieren — z.B. Fallback-Bild oder Placeholder.

TypeScript OnError.jsx
import fallback from './fallback.png';

const SafeImage = ({ src, alt }) => {
    const handleError = (e) => {
        e.currentTarget.src = fallback;
        // Wichtig: onError abklemmen, sonst Endlos-Loop falls auch fallback nicht lädt
        e.currentTarget.onerror = null;
    };

    return <img src={src} alt={alt} onError={handleError} />;
};

Häufige Stolperfallen

Bilder in /src — IMMER importieren, nie als String-Pfad.

<img src="../assets/bild.jpg" /> funktioniert in Dev evtl. zufällig, scheitert aber in Production — der Bundler kennt den String nicht und kopiert das Bild nicht ins Build-Output. Lösung: import bild from './bild.jpg'.

alt-Attribut ist Pflicht — kein Auslassen.

Fehlt das alt, warnt ESLint mit jsx-a11y/alt-text. Inhaltliche Bilder bekommen eine sinnvolle Beschreibung, dekorative bekommen alt="". Niemals einfach weglassen.

loading='lazy' erst seit nativer Browser-Unterstützung sinnvoll.

Native lazy-loading ist in Chrome, Firefox, Safari seit ~2020 verfügbar. Davor brauchte man Intersection Observer oder Library-Lösungen. Heute reicht das HTML-Attribut. Für Browser ohne Support gibt es Fallback-Verhalten (sofortiges Laden).

width/height am img — verhindert Layout-Shift (CLS).

Ohne width/height weiß der Browser die Bild-Größe erst beim Laden — Inhalt darunter springt. Mit width/height (auch als JSX-Props mit Pixel-Zahl) reserviert er den Platz vorab. Wichtig für Core Web Vitals.

JSX nutzt camelCase: srcSet, crossOrigin, useMap.

HTML-Attribute mit Bindestrich oder Klein-Schreibweise werden in JSX zu camelCase. srcSet, crossOrigin, useMap, referrerPolicy. Linter erkennt das.

public-Bilder haben kein Cache-Busting.

Ändert sich ein Bild in /public, behalten Browser oft die alte Version. Manuelle Cache-Busting: Query-Parameter mit Versions-Hash ?v=2026-05 oder das Bild umbenennen.

img-Tag MUSS in JSX selbst-schließend sein.

<img> ohne /> ist ein JSX-Syntax-Fehler. <img /> oder <img></img> sind beide gültig — selbst-schließend ist Konvention.

onError ohne Loop-Schutz kann endlos feuern.

Wenn der Fallback im onError selbst nicht lädt, feuert onError erneut. Lösung: in onError e.currentTarget.onerror = null setzen, BEVOR der Fallback-src zugewiesen wird.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Components

Zur Übersicht