Compare-Seite mit Alpine.js und der nativen GraphQL-Compare-API
Hyvä liefert Minicart und Wishlist mit, eine fertige Compare-Funktionalität sucht man im Standard-Theme jedoch vergeblich. Wer trotzdem eine Vergleichsseite braucht, sollte nicht bei null anfangen, sondern auf Magentos native Compare-Product-API setzen und sie mit einem sauberen Alpine-Store und einer responsiven Tabelle verbinden.
Inhaltsverzeichnis
- 1. Warum Hyvä keine fertige Compare-Lösung mitbringt
- 2. Die native Compare-Product-API statt Eigenbau
- 3. Alpine-Store für die Vergleichsliste über mehrere Seiten hinweg
- 4. Compare-Widget im Produktgrid einbinden
- 5. Guest-uid und Merge beim Login
- 6. Die Vergleichstabelle responsive gestalten
- 7. Unterschiede zwischen Produkten optisch hervorheben
- 8. Performance und Full-Page-Cache-Kompatibilität
- 9. Typische Fehler beim Compare-Feature in Hyvä
- 10. Zusammenfassung
- 11. FAQ
1. Warum Hyvä keine fertige Compare-Lösung mitbringt
Hyvä Themes reduziert das Standard-Frontend bewusst auf das, was die meisten Shops tatsächlich brauchen. Minicart, Wishlist und Checkout gehören dazu, eine Compare-Seite gehört bei den meisten Projekten nicht zum Kern des Sortiments-Erlebnisses und fehlt deshalb im Standard-Theme. Das ist keine technische Lücke, sondern eine bewusste Reduktion, die verhindert, dass Shops ungenutzte Komplexität mit ausliefern.
Für Sortimente mit vielen vergleichbaren Varianten, etwa Elektronik, Werkzeuge oder technische Komponenten, ist ein Produktvergleich trotzdem oft ein echtes Conversion-Feature. Statt eine komplett eigene Datenhaltung zu bauen, lohnt sich der Blick auf die Compare-Product-API, die Magento seit Jahren mitbringt und die seit einigen Versionen auch vollständig über GraphQL ansprechbar ist. Damit lässt sich die Funktionalität im Hyvä-typischen Stil nachrüsten, ohne die Legacy-Blöcke aus dem Luma-Theme zu reaktivieren.
2. Die native Compare-Product-API statt Eigenbau
Magento verwaltet Vergleichslisten serverseitig über eine eigene uid, ganz analog zur maskierten Cart-Id einer Gastbestellung. Die Mutation createCompareList erzeugt eine neue Liste und liefert die uid zurück, addProductsToCompareList und removeProductsFromCompareList pflegen den Inhalt, und die Query compareList liefert alle Produkte samt Attributen für die Vergleichstabelle. Diese uid wird clientseitig gespeichert und bei jeder weiteren Anfrage mitgeschickt, genau wie die Cart-Id im Checkout.
Der Vorteil gegenüber einer Eigenlösung mit eigenem Custom-Attribut oder eigener Datenbanktabelle ist offensichtlich: Die Compare-Product-API kennt bereits Konfigurierbarkeit, Preisregeln, Store-Views und Berechtigungen, sie wird mit jedem Magento-Core-Update mitgepflegt, und sie funktioniert identisch für Gäste und eingeloggte Kunden. Eigenbau lohnt sich hier fast nie, das Rad wurde bereits erfunden und liegt nur eine GraphQL-Query entfernt.
mutation CreateCompareList {
createCompareList(input: { uid: null }) {
uid
item_count
items {
uid
product {
sku
name
}
}
}
}
mutation AddToCompare($listUid: ID!, $productUid: ID!) {
addProductsToCompareList(
input: { uid: $listUid, products: [$productUid] }
) {
uid
item_count
}
}
3. Alpine-Store für die Vergleichsliste über mehrere Seiten hinweg
Damit die Vergleichsliste beim Wechsel von Kategorieseite zu Kategorieseite und nach einem harten Seiten-Reload erhalten bleibt, reicht ein x-data auf Komponentenebene nicht aus. Die uid muss global verfügbar sein, deshalb bietet sich ein Alpine.store an, der beim Start der Seite aus localStorage gelesen und bei jeder Änderung dorthin zurückgeschrieben wird. Hyvä bringt Alpine ohnehin global ein, ein zusätzliches Plugin wird dafür nicht gebraucht.
Wichtig ist, den Store einmalig zu initialisieren, bevor irgendeine Komponente ihn liest, sonst kommt es zu einem kurzen Flackern des Compare-Zählers beim ersten Render. In der Praxis registriert man den Store deshalb in einem eigenen Inline-Script direkt vor dem schließenden body-Tag, ähnlich wie es Hyvä für den bestehenden Wishlist-Store bereits vormacht.
document.addEventListener('alpine:init', () => {
Alpine.store('compare', {
uid: localStorage.getItem('compare_list_uid') || null,
items: JSON.parse(localStorage.getItem('compare_items') || '[]'),
count: 0,
init() {
this.count = this.items.length;
},
async addItem(productUid, listMutation) {
const result = await listMutation(this.uid, productUid);
this.uid = result.uid;
this.count = result.item_count;
localStorage.setItem('compare_list_uid', this.uid);
},
isInList(productUid) {
return this.items.some((item) => item.productUid === productUid);
},
});
});
4. Compare-Widget im Produktgrid einbinden
Das Compare-Widget wird als kleiner Button in der bestehenden Produktkarte platziert, üblicherweise im gleichen Override von Magento_Catalog::product/list.phtml, in dem auch der Wishlist-Button sitzt. Statt eines neuen Blocks reicht ein reines Alpine-Fragment, das direkt auf den globalen Store zugreift und den Produkt-uid als Datenattribut aus dem Server-Rendering übernimmt.
Entscheidend ist die visuelle Rückmeldung: Ein Produkt, das bereits in der Vergleichsliste liegt, muss sich optisch vom Rest der Karte abheben, sonst klicken Kunden mehrfach auf denselben Button und wundern sich über Fehler beim erneuten Hinzufügen. Ein einfacher x-bind:class-Ausdruck auf Basis von isInList reicht dafür bereits aus.
<div class="product-item" x-data="{ productUid: '{{ $productUid }}' }">
<button
type="button"
class="flex items-center gap-1 text-sm"
x-bind:class="$store.compare.isInList(productUid)
? 'text-orange-600 font-semibold'
: 'text-gray-500 hover:text-gray-700'"
x-on:click="$store.compare.isInList(productUid)
? removeFromCompare(productUid)
: $store.compare.addItem(productUid, addToCompareList)"
>
<svg class="w-4 h-4" aria-hidden="true"><!-- Icon --></svg>
<span x-text="$store.compare.isInList(productUid) ? 'Im Vergleich' : 'Vergleichen'"></span>
</button>
</div>
5. Guest-uid und Merge beim Login
Gäste erhalten ihre Compare-uid beim ersten Hinzufügen eines Produkts und behalten sie über localStorage, bis der Browser-Speicher geleert wird. Meldet sich ein Kunde während einer laufenden Session an, muss die Gast-Liste mit einer eventuell bereits vorhandenen Kundenliste zusammengeführt werden, sonst gehen die zuvor markierten Produkte beim Login verloren. Magento bietet dafür die Mutation assignCompareListToCustomer an, die im Login-Flow nach erfolgreicher Authentifizierung aufgerufen wird.
Praktisch heißt das: Der Login-Handler im Hyvä-Checkout beziehungsweise im Customer-Modul muss um einen zusätzlichen GraphQL-Call ergänzt werden, der die im Store gespeicherte Gast-uid übergibt. Nach erfolgreichem Merge wird die localStorage-uid gelöscht, weil die Vergleichsliste ab diesem Zeitpunkt an die Kundensitzung gebunden ist und nicht mehr clientseitig verwaltet werden muss.
6. Die Vergleichstabelle responsive gestalten
Eine Vergleichstabelle mit vier oder fünf Produkten und einem Dutzend Attributen sprengt auf Mobilgeräten fast jedes Layout. Statt die Tabelle zu verkleinern, bis nichts mehr lesbar ist, funktioniert horizontales Scrollen mit einer fixierten ersten Spalte deutlich zuverlässiger: Die Attributnamen bleiben links stehen, während die Produktspalten seitlich durchgescrollt werden können.
Mit Tailwind lässt sich das ohne zusätzliches JavaScript umsetzen, position sticky auf der ersten Spalte kombiniert mit overflow-x-auto auf dem umschließenden Container reicht bereits aus. Auf sehr kleinen Displays kann zusätzlich ein Alpine-gesteuertes Akkordeon je Produkt sinnvoller sein als eine Tabelle, gerade wenn mehr als drei Produkte verglichen werden.
<div class="overflow-x-auto">
<table class="min-w-full border-collapse text-sm">
<thead>
<tr>
<th class="sticky left-0 bg-white z-10 p-3 text-left w-40">Attribut</th>
<template x-for="product in $store.compare.items" :key="product.uid">
<th class="p-3 min-w-[180px] text-left" x-text="product.name"></th>
</template>
</tr>
</thead>
<tbody>
<template x-for="attr in attributes" :key="attr.code">
<tr class="border-t">
<td class="sticky left-0 bg-white z-10 p-3 font-medium" x-text="attr.label"></td>
<template x-for="product in $store.compare.items" :key="product.uid">
<td class="p-3" x-text="product.attributes[attr.code]"></td>
</template>
</tr>
</template>
</tbody>
</table>
</div>
7. Unterschiede zwischen Produkten optisch hervorheben
Der eigentliche Mehrwert einer Compare-Seite entsteht erst, wenn Unterschiede zwischen den Produkten sofort ins Auge fallen, statt dass Kunden jede Zeile einzeln durchlesen müssen. Dafür wird pro Attributzeile geprüft, ob alle Werte identisch sind, und bei Abweichung eine dezente Hervorhebung gesetzt, etwa ein leicht eingefärbter Hintergrund für die Zellen, die vom Mehrheitswert abweichen.
Diese Logik lässt sich rein clientseitig in Alpine berechnen, sobald die Produktdaten aus der GraphQL-Antwort im Store liegen, ganz ohne serverseitige Zusatzverarbeitung. Wichtig ist dabei, numerische Attribute wie Gewicht oder Akkulaufzeit nicht nur auf reine Textgleichheit zu prüfen, sondern normalisiert zu vergleichen, sonst werden 2.0 kg und 2 kg fälschlich als unterschiedlich markiert.
8. Performance und Full-Page-Cache-Kompatibilität
Da die gesamte Compare-Logik über GraphQL-Mutationen läuft, die naturgemäß nicht über den Full Page Cache ausgeliefert werden, bleibt die eigentliche Produktseite von der Vergleichsfunktion unberührt und weiterhin vollständig cachefähig. Der Compare-Zähler im Header wird rein clientseitig aus dem Alpine-Store gerendert, ähnlich wie die Warenkorb-Anzahl in der Minicart, wodurch keine ESI-Blöcke oder zusätzliche Cache-Ausnahmen nötig sind.
Bei der GraphQL-Query für die Vergleichstabelle selbst lohnt es sich, nur die tatsächlich angezeigten Attribute abzufragen, statt pauschal alle Produktfelder zu laden. Eine zu breite Query erhöht die Antwortzeit unnötig und überträgt Daten, die auf der Vergleichsseite ohnehin nicht dargestellt werden, besonders bei Produkten mit vielen konfigurierbaren Attributen macht sich das deutlich bemerkbar.
9. Typische Fehler beim Compare-Feature in Hyvä
Der häufigste Fehler ist, die Compare-uid nur im Alpine-Store und nicht zusätzlich in localStorage zu halten. Nach einem harten Reload ist der Store leer, die Serverliste existiert aber weiterhin, sodass Kunden scheinbar eine leere Vergleichsliste sehen, obwohl der Inhalt serverseitig noch vorhanden ist. Eine konsequente Synchronisation zwischen Store und localStorage in beide Richtungen vermeidet dieses Problem zuverlässig.
Ein zweiter, oft übersehener Punkt ist das fehlende Merge beim Login: Wird assignCompareListToCustomer vergessen, verliert der Kunde beim Anmelden alle zuvor als Gast markierten Produkte, ohne dass eine Fehlermeldung erscheint, was aus Kundensicht wie ein stiller Datenverlust wirkt. Ebenso wichtig ist die CSP-Registrierung jedes Inline-Scripts über hyvaCsp registerInlineScript, sonst blockiert die Content Security Policy den Store und die Compare-Buttons reagieren gar nicht.
| Baustein | Zuständigkeit | Technologie | Persistenz |
|---|---|---|---|
| Compare-uid | Eindeutige Kennung der Vergleichsliste | GraphQL createCompareList | localStorage (Gast), Kundenkonto (eingeloggt) |
| Compare-Store | Globaler Zustand über alle Seiten hinweg | Alpine.store | In-Memory + localStorage-Sync |
| Compare-Widget | Hinzufügen/Entfernen im Produktgrid | phtml + Alpine x-data | keine, liest aus Store |
| Vergleichstabelle | Darstellung und Attribut-Diffing | GraphQL compareList Query | keine, rein clientseitig gerendert |
| Login-Merge | Zusammenführen von Gast- und Kundenliste | assignCompareListToCustomer | serverseitig ab Login persistent |
Mironsoft
Hyvä-Theme-Entwicklung und Luma-Migration
Noch auf Luma unterwegs oder ein Hyvä-Theme, das nicht rund läuft?
Wir entwickeln Hyvä-Themes für Magento von Grund auf oder migrieren bestehende Luma-Shops sauber, mit Tailwind CSS, Alpine.js und ohne unnötiges JavaScript-Gepäck.
Luma-zu-Hyvä-Migration
Bestehenden Shop strukturiert und ohne Funktionsverlust auf Hyvä umstellen.
Custom-Theme-Entwicklung
Individuelles Hyvä-Theme nach Design-Vorgaben von Grund auf umsetzen.
Performance-Optimierung
Core Web Vitals und Ladezeiten im Hyvä-Frontend gezielt verbessern.
10. Zusammenfassung
Produktvergleich in Hyvä: Das Wichtigste auf einen Blick
Keine Eigenlösung
Magentos Compare-Product-API über GraphQL nutzen statt eigener Datenhaltung zu bauen.
Alpine-Store
Globaler Store mit localStorage-Sync hält die Vergleichsliste über Seitenwechsel hinweg.
Responsive Tabelle
Sticky erste Spalte plus horizontales Scrollen statt kaum lesbarer Mini-Tabelle.
Login-Merge
assignCompareListToCustomer verhindert Datenverlust beim Wechsel von Gast zu Kunde.