Hyvä Schema.org-Markup direkt im Theme einbinden
AI generated
Hyvä
phtml
Hyvä · Schema.org · SEO · Magento 2
Hyvä Schema.org-Markup direkt im Theme einbinden
statt per Plugin oder generischem SEO-Modul

Generische SEO-Erweiterungen erzeugen Schema.org-Markup oft doppelt oder unvollständig, weil sie keinen Zugriff auf die tatsächlichen Hyvä-ViewModel-Daten haben. Wer Hyvä Schema.org-Markup direkt im phtml-Template über ein eigenes ViewModel ausgibt, erhält volle Kontrolle über Product-, Breadcrumb- und Organization-Daten, ohne zusätzlichen Modul-Overhead und ohne Konflikte mit dem CSP-Setup.

14 Min. Lesezeit JSON-LD · ViewModel · CSP-kompatibel Hyvä Themes · Magento 2.4.8 · Schema.org

1. Warum Schema.org-Markup direkt im Template statt per Plugin oder generischem SEO-Modul

Ein generisches SEO-Modul kennt die konkrete Datenstruktur eines Hyvä-Themes nicht. Es liest Produktdaten meist über einen eigenen, zusätzlichen Datenabruf und erzeugt Schema.org-Markup, das mit dem, was das Theme ohnehin schon über sein ViewModel geladen hat, nichts zu tun hat. Wer stattdessen Hyvä Schema.org-Markup direkt im phtml-Template ausgibt, greift auf exakt die Daten zu, die der Block bereits für die sichtbare Darstellung nutzt: Preis, Verfügbarkeit, Bewertungen, Bildpfade. Es entsteht keine zweite Datenquelle, die bei einem Preis-Update oder einer Attributänderung aus dem Takt geraten kann.

Der zweite Grund ist die Vermeidung doppelter Ausgabe. Viele Magento-Erweiterungen für SEO bringen ein eigenes Product-Schema mit, das per Plugin oder Observer in die Seite injiziert wird. Ist gleichzeitig eigenes Schema.org-Markup im Template aktiv, landen zwei <script type="application/ld+json">-Blöcke mit demselben @type auf derselben Seite. Google wertet in solchen Fällen unvorhersehbar aus, welches der beiden Markups gilt, und Rich-Results-Tests melden Warnungen wegen widersprüchlicher Werte. Direkt im Theme eingebundenes Markup lässt sich hingegen gezielt an- und ausschalten, ohne ein fremdes Modul deaktivieren zu müssen.

Der dritte Grund ist der fehlende Overhead. Ein generisches SEO-Modul lädt häufig eigene Collections, eigene Repository-Aufrufe und eigene Konfigurationswerte, nur um Schema-Properties zu befüllen. Im Hyvä-ViewModel sind diese Daten in der Regel bereits vorhanden, weil sie für die reguläre Seitendarstellung gebraucht werden. Hyvä Schema.org-Markup im Template zu erzeugen bedeutet in der Praxis: keine zusätzliche Datenbankabfrage, kein zusätzlicher Modul-Layer, sondern eine reine Formatierungsaufgabe auf bereits geladenen Objekten.

2. JSON-LD-Grundstruktur in Hyvä-phtml-Templates einbinden

Die Grundstruktur für Hyvä Schema.org-Markup ist in jedem Template identisch: ein <script type="application/ld+json">-Tag, dessen Inhalt aus einem PHP-Array mit json_encode() erzeugt wird. Wichtig ist die Platzierung am Ende des jeweiligen phtml-Templates, direkt vor dem schließenden Root-Element des Blocks, damit das Markup dem Content zugeordnet bleibt und nicht versehentlich mehrfach durch verschachtelte Block-Includes gerendert wird. Für Flags empfiehlt sich JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, damit URLs lesbar bleiben und Umlaute nicht als Escape-Sequenzen erscheinen.

Bei der CSP-Kompatibilität gibt es eine Besonderheit, die in der Praxis oft übersehen wird: Browser führen application/ld+json nicht als JavaScript aus, weshalb ein script-src-Directive diesen Inhalt normalerweise nicht blockiert. Trotzdem prüft das Hyvä-CSP-Modul standardmäßig jeden Inline-<script>-Tag unabhängig vom type-Attribut, sobald default-src restriktiv konfiguriert ist. Bevor Hyvä Schema.org-Markup produktiv geschaltet wird, sollte die Seite deshalb im CSP-Report-Only-Modus getestet werden, um sicherzugehen, dass kein Verstoß gemeldet wird.

Konflikte mit dem Hyvä-CSP-Modul entstehen fast ausschließlich dann, wenn JSON-LD-Ausgabe mit echter Inline-Logik vermischt wird, etwa wenn zusätzlich ein <script>-Block mit Alpine-Initialisierung im selben Template folgt. Für reine JSON-LD-Blöcke ist in der Regel keine Nonce-Registrierung über $hyvaCsp notwendig, weil kein ausführbarer Code enthalten ist. Sobald aber im selben Template ein echter JavaScript-Inline-Block folgt, muss dieser wie gewohnt per $hyvaCsp->registerInlineScript() registriert werden, damit das Hyvä-CSP-Modul ihn nicht blockiert.


<?php
/** @var \Magento\Catalog\Block\Product\View $block */
/** @var \Mironsoft\Schema\ViewModel\SchemaViewModel $schemaViewModel */
$schemaViewModel = $viewModels->require(\Mironsoft\Schema\ViewModel\SchemaViewModel::class);
$productSchema = $schemaViewModel->getProductSchema($block->getProduct());
?>

<div class="product-info-main">
  <!-- Regular Hyvä product markup above -->

  <script type="application/ld+json">
<?= /* @noEscape */ json_encode($productSchema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>
  </script>
</div>

3. Product-Schema direkt in product/view.phtml integrieren

Beim Product-Schema liegt der größte Nutzen von Hyvä Schema.org-Markup direkt im Template darin, dass Offer und AggregateRating aus denselben Objekten befüllt werden, die auch die sichtbare Preis- und Bewertungsanzeige speisen. Ein separates SEO-Modul müsste diese Werte erneut laden und riskiert dabei Abweichungen, etwa wenn Preisregeln oder Sonderpreise nicht identisch berücksichtigt werden. Die Properties sku, name, image, brand und offers.price lassen sich direkt aus dem im Block bereits geladenen Product-Objekt ableiten.

Für offers.availability ist die verlässlichste Quelle $product->isSalable() beziehungsweise die Stock-Item-Daten, nicht ein statischer Wert. Fehlt diese Property oder ist sie falsch gesetzt, meldet der Google Rich Results Test eine Warnung, und das Produkt kann in Shopping-Ergebnissen benachteiligt werden. aggregateRating sollte nur ausgegeben werden, wenn tatsächlich Bewertungen vorliegen, da ein leeres oder erfundenes Rating gegen die Schema.org-Richtlinien von Google verstößt.


{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Hyvä Performance Sneaker Pro",
  "sku": "HYVA-SNK-001",
  "image": [
    "https://mironsoft.de/media/catalog/product/h/y/hyva-snk-001-1.jpg"
  ],
  "brand": {
    "@type": "Brand",
    "name": "Mironsoft Gear"
  },
  "offers": {
    "@type": "Offer",
    "url": "https://mironsoft.de/hyva-performance-sneaker-pro.html",
    "priceCurrency": "EUR",
    "price": "129.00",
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.6",
    "reviewCount": "38"
  }
}

Die Hyvä-Breadcrumbs-Vorlage hält die Krumen bereits als strukturiertes Array bereit, üblicherweise mit label und link pro Eintrag. Für Hyvä Schema.org-Markup in Form von BreadcrumbList muss dieses Array lediglich in itemListElement überführt werden, wobei position bei 1 beginnt und mit jedem Eintrag um eins erhöht wird. Der letzte Eintrag, meist die aktuelle Seite, sollte kein eigenes item mit URL erhalten, weil Google für die letzte Position keine anklickbare Ziel-URL erwartet.

Wichtig ist, dass dieses Markup nur einmal pro Seite gerendert wird. Da breadcrumbs.phtml in Hyvä-Themes teils in mehreren Bereichen eingebunden werden kann, etwa in Kategorie- und Produktseiten mit leicht unterschiedlichem Layout, sollte die JSON-LD-Ausgabe an genau einer Stelle im Block-Baum stehen, nicht in jeder aufrufenden Instanz erneut.


{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Startseite",
      "item": "https://mironsoft.de/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Schuhe",
      "item": "https://mironsoft.de/schuhe.html"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "Hyvä Performance Sneaker Pro"
    }
  ]
}

5. Organization- und WebSite-Schema global im default.phtml

Organization- und WebSite-Schema gehören, anders als Product- oder BreadcrumbList-Schema, nicht in einzelne Content-Blöcke, sondern genau einmal auf jede Seite. Der richtige Ort für dieses Hyvä Schema.org-Markup ist entweder direkt im default.phtml des Root-Templates oder, sauberer, in einem eigenen Block, der per Layout-XML im head-Container platziert wird. Damit lässt sich die Ausgabe zentral steuern und bei Bedarf ohne Template-Änderung deaktivieren.

WebSite mit potentialAction vom Typ SearchAction ermöglicht Google, im Suchergebnis eine Sitelinks-Suchbox anzuzeigen, sofern die interne Suchroute korrekt als URL-Template mit {search_term_string}-Platzhalter hinterlegt ist. Die sameAs-Liste im Organization-Schema sollte ausschließlich echte, aktiv gepflegte Social-Media-Profile enthalten, da veraltete oder falsche Links die Knowledge-Graph-Zuordnung eher verschlechtern als verbessern.


{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://mironsoft.de/#organization",
      "name": "Mironsoft",
      "url": "https://mironsoft.de",
      "logo": "https://mironsoft.de/media/logo/mironsoft-logo.png",
      "sameAs": [
        "https://www.linkedin.com/company/mironsoft",
        "https://github.com/mironsoft"
      ]
    },
    {
      "@type": "WebSite",
      "@id": "https://mironsoft.de/#website",
      "url": "https://mironsoft.de",
      "name": "mironsoft.de",
      "publisher": { "@id": "https://mironsoft.de/#organization" },
      "potentialAction": {
        "@type": "SearchAction",
        "target": "https://mironsoft.de/catalogsearch/result/?q={search_term_string}",
        "query-input": "required name=search_term_string"
      }
    }
  ]
}

6. Sauberes ViewModel-Pattern für Schema-Daten

Sobald Hyvä Schema.org-Markup mehr als ein Template betrifft, lohnt sich eine eigene ViewModel-Klasse, die als ArgumentInterface deklariert und per Layout-XML in den jeweiligen Block injiziert wird. Statt Array-Aufbau und Formatierungslogik im phtml-Template zu verteilen, kapselt eine SchemaViewModel-Klasse diese Logik an einer Stelle, testbar und wiederverwendbar über Product-, Kategorie- und Startseiten-Templates hinweg.

Der Zugriff auf Stock-, Preis- und Review-Daten läuft über injizierte Repository- und Service-Interfaces, nicht über direkte Modell-Instanzen im Template. Das hält die phtml-Datei frei von Geschäftslogik und macht das Hyvä Schema.org-Markup unabhängig von späteren Änderungen an der Datenquelle, etwa wenn Ratings künftig aus einem externen Review-Service statt aus Magento_Review kommen.


<?php

declare(strict_types=1);

namespace Mironsoft\Schema\ViewModel;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\View\Element\Block\ArgumentInterface;
use Magento\Review\Model\ResourceModel\Review\Summary\CollectionFactory as ReviewSummaryCollectionFactory;
use Magento\Store\Model\StoreManagerInterface;

/**
 * Provides Schema.org structured data for product templates.
 */
final class SchemaViewModel implements ArgumentInterface
{
    /**
     * @param StoreManagerInterface $storeManager Store manager for base URLs and currency
     * @param ReviewSummaryCollectionFactory $reviewSummaryCollectionFactory Factory for rating summaries
     */
    public function __construct(
        private readonly StoreManagerInterface $storeManager,
        private readonly ReviewSummaryCollectionFactory $reviewSummaryCollectionFactory
    ) {
    }

    /**
     * Builds a Product schema array ready for json_encode().
     *
     * @param ProductInterface $product Loaded product entity
     * @return array<string, mixed>
     */
    public function getProductSchema(ProductInterface $product): array
    {
        // @phpstan-ignore-next-line StoreInterface::getBaseUrl() missing on interface
        $baseUrl = $this->storeManager->getStore()->getBaseUrl();

        $schema = [
            '@context' => 'https://schema.org',
            '@type' => 'Product',
            'name' => $product->getName(),
            'sku' => $product->getSku(),
            'offers' => [
                '@type' => 'Offer',
                'url' => $baseUrl . $product->getUrlKey() . '.html',
                'priceCurrency' => $this->storeManager->getStore()->getCurrentCurrencyCode(),
                'price' => number_format((float) $product->getFinalPrice(), 2, '.', ''),
                'availability' => $product->isSalable()
                    ? 'https://schema.org/InStock'
                    : 'https://schema.org/OutOfStock',
            ],
        ];

        $rating = $this->getAggregateRating((int) $product->getId());
        if ($rating !== null) {
            $schema['aggregateRating'] = $rating;
        }

        return $schema;
    }

    /**
     * Loads an aggregate rating summary for a product without an extra per-request query.
     *
     * @param int $productId Product entity id
     * @return array<string, string>|null Aggregate rating schema or null when no reviews exist
     */
    private function getAggregateRating(int $productId): ?array
    {
        $summary = $this->reviewSummaryCollectionFactory->create()
            ->addFieldToFilter('entity_pk_value', ['eq' => $productId])
            ->getFirstItem();

        if (!$summary->getReviewsCount()) {
            return null;
        }

        return [
            '@type' => 'AggregateRating',
            'ratingValue' => (string) $summary->getRatingSummary(),
            'reviewCount' => (string) $summary->getReviewsCount(),
        ];
    }
}

7. Testing und Validierung von Schema.org-Markup

Jede Änderung an Hyvä Schema.org-Markup sollte vor dem Deployment mit dem Google Rich Results Test und ergänzend mit dem allgemeinen Schema.org-Validator geprüft werden. Der Rich Results Test zeigt konkret, für welche Rich-Result-Typen eine Seite qualifiziert ist und welche Properties als fehlend gelten, während der Schema.org-Validator strenger auf die reine Spezifikationskonformität prüft, unabhängig davon, ob Google die Property überhaupt für Rich Results verwendet.

In der Praxis fehlen am häufigsten priceCurrency bei Offer, availability bei nicht lagernden Varianten und reviewCount bei AggregateRating. Alle drei Fehler werden vom Rich Results Test typischerweise als Warnung, nicht als Fehler gemeldet, was leicht übersehen wird. Ein sinnvoller Test-Workflow prüft daher nicht nur, ob überhaupt gültiges JSON ausgegeben wird, sondern auch stichprobenartig einzelne Produktseiten mit und ohne Bewertungen, mit und ohne Sonderpreis, um Randfälle abzudecken.

8. Performance-Aspekte

Da das Schema-Markup Teil des regulären Block-HTMLs ist, wird es automatisch über die vorhandenen Block-Cache-Tags des jeweiligen Blocks mitgecacht. Wird der Cache-Tag für ein Produkt invalidiert, etwa bei einer Preisänderung, invalidiert dasselbe Ereignis auch das darin enthaltene Schema-Markup, ohne dass eine gesonderte Cache-Logik notwendig wäre.

Kritisch wird es bei Rating-Daten, wenn die SchemaViewModel-Klasse pro Seitenaufruf eine eigene Datenbankabfrage für die Review-Zusammenfassung ausführt, statt auf bereits im Produkt-Collection-Load enthaltene Werte zurückzugreifen. Wird die Rating-Summary ohnehin schon für die sichtbare Sternebewertung geladen, sollte das ViewModel dieselbe Instanz weiterverwenden, statt sie ein zweites Mal per eigener Collection abzufragen. Bei Kategorie- und Listing-Seiten mit vielen Produkten summiert sich eine zusätzliche Query pro Produkt schnell zu einer messbaren Latenzsteigerung.

9. Häufige Fallstricke bei Schema.org-Markup in Hyvä

Der häufigste Fehler ist doppeltes Markup: Ein generisches SEO-Modul bleibt aktiv, während gleichzeitig eigenes Hyvä Schema.org-Markup im Theme ausgegeben wird. Beide Quellen erzeugen denselben @type mit teils abweichenden Werten, was Google im schlechtesten Fall dazu bringt, beide Markups zu ignorieren. Vor der Einführung eigenen Schema-Markups sollte deshalb geprüft werden, welche installierten Extensions bereits JSON-LD ausgeben, und diese Funktion dort gezielt deaktiviert werden.

Weitere klassische Fehler betreffen die Preisangabe ohne priceCurrency, fehlende availability-Werte bei ausverkauften Varianten und ungültiges JSON durch manuelle String-Konkatenation statt json_encode(). Die folgende Tabelle stellt die unsicheren Ansätze den empfohlenen Mustern gegenüber.

Aufgabe Unsicher / Fehleranfällig Empfohlenes Pattern Vorteil
Schema-Markup einbinden Generisches SEO-Modul und eigenes Template gleichzeitig aktiv Modul-Funktion deaktivieren, volles Markup im ViewModel Kein doppeltes JSON-LD
Preisangabe Price als String ohne priceCurrency priceCurrency explizit aus Store-Konfiguration Kompatibel mit Google Merchant
Verfügbarkeit Fehlende availability Property availability aus isSalable() ableiten Rich Results ohne Warnung
Breadcrumbs Statisches Markup hardcodiert itemListElement dynamisch aus Breadcrumb-Block Bleibt bei Kategorieumbau korrekt
Rating-Daten laden Eigene DB-Query pro Seitenaufruf Vorhandene Summary aus Block-Cache wiederverwenden Keine zusätzliche Latenz
JSON-Ausgabe Manuelle String-Konkatenation json_encode() mit JSON_UNESCAPED_SLASHES Immer valides JSON

Die Tabelle zeigt ein durchgängiges Muster: Fast jeder Fehlerfall entsteht dadurch, dass eine Property nicht aus einer verlässlichen, bereits vorhandenen Datenquelle abgeleitet wird, sondern statisch, unvollständig oder doppelt gepflegt ist. Wer Hyvä Schema.org-Markup konsequent aus ViewModel-Methoden statt aus Templates heraus befüllt, vermeidet die meisten dieser Fallstricke von vornherein.

Mironsoft

Hyvä-Theme-Entwicklung, Structured Data und technisches SEO für Magento 2

Schema.org-Markup, das in Rich Results wirklich ankommt?

Wir prüfen bestehendes Schema.org-Markup in eurem Hyvä-Shop, entfernen doppelte Ausgaben aus generischen SEO-Modulen und implementieren Product-, Breadcrumb- und Organization-Schema sauber über ein eigenes ViewModel-Pattern.

Schema-Markup-Audit

Rich-Results-Test, Validator-Check und Analyse auf doppeltes JSON-LD

Structured-Data-Implementierung

Product-, BreadcrumbList- und Organization-Schema als ViewModel-Pattern

SEO-Technik-Beratung

CSP-Kompatibilität, Caching-Strategie und Testing-Workflow für Schema-Daten

10. Zusammenfassung

Hyvä Schema.org-Markup direkt im Theme statt per generischem Plugin einzubinden, löst gleich mehrere Probleme auf einmal: Es verhindert doppelte JSON-LD-Ausgabe, greift auf dieselben Daten zu, die ohnehin schon im Block geladen sind, und lässt sich sauber über Block-Cache-Tags mitcachen. Product-, BreadcrumbList- und Organization-Schema lassen sich mit überschaubarem Aufwand direkt in product/view.phtml, breadcrumbs.phtml und default.phtml integrieren, sobald die Datenquelle klar definiert ist.

Der nachhaltigste Weg führt über ein eigenes SchemaViewModel, das als ArgumentInterface injiziert wird und die Formatierungslogik zentral kapselt, statt sie über mehrere Templates zu verteilen. So bleibt Hyvä Schema.org-Markup testbar, wartbar und unabhängig von künftigen Änderungen an der zugrunde liegenden Datenquelle.

Wer regelmäßig mit dem Google Rich Results Test und dem Schema.org-Validator prüft, welche Properties tatsächlich ausgegeben werden, erkennt fehlende priceCurrency- oder availability-Werte frühzeitig, bevor sie sich negativ auf Rich Results auswirken. In Kombination mit einer klaren Abgrenzung zu parallel aktiven SEO-Modulen entsteht so ein robustes, performantes Schema-Setup für den gesamten Hyvä-Shop.

Hyvä Schema.org-Markup direkt im Theme, das Wichtigste auf einen Blick

Direkt statt generisch

Schema.org-Markup aus vorhandenen ViewModel-Daten statt aus einem generischen SEO-Modul, kein Datenversatz.

Kein doppeltes Markup

Parallel aktive Erweiterungen mit eigenem JSON-LD deaktivieren, bevor eigenes Schema-Markup live geht.

ViewModel-Pattern

SchemaViewModel als ArgumentInterface kapselt die Logik, testbar und wiederverwendbar über alle Templates.

Caching & Testing

Block-Cache-Tags übernehmen die Invalidierung automatisch, Rich Results Test vor jedem Deployment prüfen.

11. FAQ: Hyvä Schema.org-Markup direkt im Theme

1Was bedeutet Hyvä Schema.org-Markup direkt im Theme?
JSON-LD wird direkt in den phtml-Templates aus denselben ViewModel-Daten erzeugt, statt per generischem SEO-Modul oder Plugin nachträglich injiziert.
2Warum nicht einfach ein fertiges SEO-Modul verwenden?
Generische Module kennen die Theme-Struktur nicht, laden Daten doppelt und erzeugen bei parallelem Theme-Markup widersprüchliche JSON-LD-Blöcke.
3Beeinflusst das Hyvä-CSP-Modul JSON-LD-Skripte?
Browser führen application/ld+json nicht als JavaScript aus. Bei restriktivem default-src trotzdem im CSP-Report-Only-Modus testen.
4Welche Properties fehlen am häufigsten?
priceCurrency, availability bei nicht lagernden Varianten und reviewCount bei AggregateRating fehlen am häufigsten, meist nur als Warnung gemeldet.
5Wie baue ich BreadcrumbList-Schema auf?
Vorhandenes Breadcrumb-Array in itemListElement überführen, position bei 1 beginnend, letzter Eintrag ohne item-URL.
6Wo gehört Organization- und WebSite-Schema hin?
Genau einmal pro Seite in default.phtml oder einen eigenen Layout-Block im head-Container, nicht in einzelne Content-Templates.
7Was bringt ein SchemaViewModel?
Kapselt Formatierungslogik als ArgumentInterface an einer Stelle, testbar und über mehrere Templates wiederverwendbar.
8Wie teste ich Schema.org-Markup vor Live-Gang?
Google Rich Results Test für Rich-Result-Eignung, Schema.org-Validator für Spezifikationskonformität, jeweils mit und ohne Bewertungen prüfen.
9Verursacht Schema-Markup zusätzliche DB-Abfragen?
Nicht, wenn bereits geladene Rating-Daten wiederverwendet werden. Eigene Query pro Seitenaufruf sollte auf Listing-Seiten vermieden werden.
10Häufigster Fehler bei parallel aktiven SEO-Modulen?
Doppeltes Markup mit demselben @type und abweichenden Werten. Modul-Funktion vor eigenem Schema-Markup gezielt deaktivieren.