Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Produktbilder und Lagerbestand korrekt anzeigen

Produktbilder und Lagerbestand korrekt anzeigen

~14 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026

Zwei der praktisch wichtigsten Datenpunkte fehlen der Detailseite noch: ein echtes Produktbild und der aktuelle Lagerbestand – beide stecken an eher unerwarteter Stelle in der API-Antwort.

Ein Produkt kann MEHRERE Bilder haben – deshalb ist media_gallery_entries ein Array, kein einzelnes Feld:

"media_gallery_entries": [
  {
    "id": 12,
    "file": "/w/s/wsh12-blue_1.jpg",
    "position": 1,
    "types": ["image", "small_image", "thumbnail"]
  },
  {
    "id": 13,
    "file": "/w/s/wsh12-blue_2.jpg",
    "position": 2,
    "types": []
  }
]

types markiert, WOFÜR ein Bild in Magento verwendet wird – "image" ist das Hauptbild der Produktseite. file ist KEINE vollständige URL, sondern ein relativer Pfad, den wir selbst zusammensetzen müssen.

Die vollständige Bild-URL zusammensetzen

src/api/magentoApi.js
// ... vorheriger Code ...

const MEDIA_BASE_URL = import.meta.env.VITE_MAGENTO_MEDIA_URL;

export function ermittleProduktbild(produkt) {
  const hauptbild = produkt.media_gallery_entries?.find((eintrag) =>
    eintrag.types?.includes('image')
  );
  const irgendeinBild = produkt.media_gallery_entries?.[0];
  const eintrag = hauptbild ?? irgendeinBild;

  if (!eintrag) {
    return null;
  }

  return `${MEDIA_BASE_URL}/catalog/product${eintrag.file}`;
}
.env
VITE_MAGENTO_BASE_URL=https://IHR-SHOP.de/rest/V1
VITE_MAGENTO_MEDIA_URL=https://IHR-SHOP.de/media
VITE_MAGENTO_ACCESS_TOKEN=IHR_INTEGRATION_ACCESS_TOKEN

Wir ergänzen VITE_MAGENTO_MEDIA_URL als eigene Variable (statt sie aus VITE_MAGENTO_BASE_URL abzuleiten), da die Medien-URL bei manchen Magento-Installationen abweichend konfiguriert ist (z. B. eine separate CDN-Domain) – denken Sie daran, .env.example entsprechend zu aktualisieren und den Dev-Server neu zu starten.

Achtung: hauptbild ?? irgendeinBild ist bewusst als Fallback gebaut: Nicht JEDES Produkt in Ihrem Test-Shop hat zwingend ein Bild mit types: ["image"] markiert – ohne Fallback würde ermittleProduktbild() für solche Produkte null zurückgeben, obwohl durchaus IRGENDEIN Bild existiert.

Lagerbestand aus extension_attributes.stock_item

"extension_attributes": {
  "stock_item": {
    "qty": 42,
    "is_in_stock": true
  }
}
src/api/magentoApi.js
// ... vorheriger Code ...

export function ermittleLagerbestand(produkt) {
  const stockItem = produkt.extension_attributes?.stock_item;
  return {
    aufLager: stockItem?.is_in_stock ?? false,
    menge: stockItem?.qty ?? 0,
  };
}

Achtung: extension_attributes.stock_item ist NICHT bei jeder Magento-Konfiguration automatisch in der Produktantwort enthalten – standardmäßig schon, aber manche individuellen ACL-/API-Anpassungen können das einschränken. Fehlt es bei Ihnen, prüfen Sie im Admin unter Stores → Configuration → Catalog → Inventory, ob "Manage Stock" aktiviert ist.

ProductDetailPage.jsx: Bild und Bestand integrieren

src/pages/ProductDetailPage.jsx
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import {
  fetchProductBySku,
  leseCustomAttribute,
  ermittleProduktbild,
  ermittleLagerbestand,
} from '../api/magentoApi';

function ProductDetailPage() {
  const { sku } = useParams();
  const [produkt, setProdukt] = useState(null);
  const [ladend, setLadend] = useState(true);

  useEffect(() => {
    setLadend(true);
    async function laden() {
      const data = await fetchProductBySku(sku);
      setProdukt(data);
      setLadend(false);
    }
    laden();
  }, [sku]);

  if (ladend) {
    return <p>Lade Produkt...</p>;
  }

  const beschreibung = leseCustomAttribute(produkt, 'description');
  const bildUrl = ermittleProduktbild(produkt);
  const { aufLager, menge } = ermittleLagerbestand(produkt);

  return (
    <div>
      {bildUrl && <img src={bildUrl} alt={produkt.name} width="300" />}
      <h1>{produkt.name}</h1>
      <p>SKU: {produkt.sku}</p>
      <p>{produkt.price} €</p>
      <p>
        {aufLager ? `Auf Lager (${menge} Stück)` : 'Ausverkauft'}
      </p>
      {beschreibung && (
        <div dangerouslySetInnerHTML={{ __html: beschreibung }} />
      )}
    </div>
  );
}

export default ProductDetailPage;

Tipp: Damit zeigt die Detailseite jetzt WIRKLICH alle wesentlichen Produktdaten: Bild, Name, SKU, Preis, Lagerbestand und Beschreibung – ein guter Moment, mehrere Ihrer echten Produkte durchzuklicken und zu prüfen, ob alle Felder plausibel aussehen.