Facettierte Navigation im Hyvä-Theme anpassen
AI generated
Hyvä
phtml
Hyvä · Layered Navigation · Alpine.js · Elasticsearch
Facettierte Navigation im Hyvä-Theme anpassen
von Swatch-Filtern bis zur AJAX-Filterung

Wer die facettierte Navigation nur mit den Standard-Templates von Magento_LayeredNavigation betreibt, verschenkt Performance, Barrierefreiheit und Conversion-Potenzial im Sidebar-Filter. Ein eigenes ViewModel pro Filter-Typ, ein Alpine.js-Preis-Slider und ein mobiler Filter-Drawer machen Layered Navigation im Hyvä-Theme schnell, zugänglich und SEO-sauber zugleich.

16 Min. Lesezeit Magento_LayeredNavigation · Swatch-Filter · Preis-Slider · Filter-Drawer Magento 2.4.8-p4 · PHP 8.4 · Tailwind CSS v4

1. Was facettierte Navigation im Hyvä-Kontext bedeutet

Die facettierte Navigation in Magento 2 basiert technisch auf dem Modul Magento_LayeredNavigation, das aus den in einer Kategorie vorhandenen Produkten aggregierte Filterwerte berechnet: Attribute, Preisspannen, Kategorien und Boolean-Flags. Die eigentliche Aggregation läuft nicht in PHP, sondern als Facet-Query gegen Elasticsearch oder OpenSearch, die über terms- und range-Aggregationen die verfügbaren Filterwerte inklusive Trefferzahl pro Kategorie-Request zurückliefern. Magento übersetzt diese Rohdaten in Magento\Catalog\Model\Layer\Filter\FilterInterface-Objekte, die anschließend im Frontend als Filterliste gerendert werden.

Der entscheidende Unterschied zwischen Layered Navigation im Hyvä-Theme und der klassischen Luma-Umsetzung liegt nicht im Backend, sondern in der Rendering- und Interaktionsschicht. Luma nutzt Knockout.js-Bindings und UI-Components, um Filterlisten zu aktualisieren, samt asynchronem Widget-Bootstrapping und einer eigenen JavaScript-Komponente pro Filtertyp. Hyvä ersetzt das durch serverseitig gerenderte phtml-Templates in Kombination mit Alpine.js für rein clientseitige Interaktion wie das Öffnen von Filtergruppen oder das Entfernen aktiver Filter-Chips. Für jede produktionsreife Umsetzung facettierter Navigation bedeutet das: keine Knockout-Bindings, kein jQuery-Widget, sondern deklarative x-data-Attribute direkt im Template.

Wichtig für die Abgrenzung: Diese facettierte Navigation betrifft ausschließlich die Sidebar-Filter der Kategorieseite, nicht die Toolbar mit Sortierung und Ansichtsumschaltung und nicht die Swatch-Darstellung auf der Produktdetailseite. Diese Trennung sollte auch im ViewModel-Design sichtbar bleiben: Ein Filter-ViewModel kennt nur Filterzustand und Facet-Daten, keine Sortierlogik.

2. Architektur: Block-Hierarchie der Layered Navigation

Die Block-Hierarchie der facettierten Navigation beginnt mit Magento\LayeredNavigation\Block\Navigation, der als Container fungiert und über getFilters() die aktiven Magento\Catalog\Model\Layer-Filter durchläuft. Im Hyvä-Theme rendert das zentrale Template Magento_LayeredNavigation/templates/layer/view.phtml diese Filterliste, delegiert aber pro Filter-Typ an ein eigenes Renderer-Template, das über Layout-XML dem jeweiligen Filter-Feld zugeordnet wird. Diese Zuordnung ist der zentrale Hebel, um Layered Navigation im Hyvä-Theme gezielt anzupassen, ohne den Core-Block zu verändern.

Für jeden Filter-Typ, Attribut-Dropdown, Swatch-Filter, Preis-Filter, Kategorie-Filter und Boolean-Filter, existiert ein eigenes Renderer-Template mit zugehörigem ViewModel. Diese Trennung folgt dem Prinzip, ArgumentInterface-ViewModels gegenüber Block-Klassen zu bevorzugen: Der Block bleibt schlank und liefert nur die Filter-Collection, während das ViewModel die Präsentationslogik pro Filter-Typ übernimmt, etwa das Aufbereiten der Swatch-Hex-Werte oder das Berechnen der Preisspannen-Grenzen für den Slider.

Die Layout-XML-Anpassung erfolgt in catalog_layer_view.xml, wo pro Filter-Feld ein eigenes Template samt ViewModel registriert wird. Diese Konfiguration ist der Ansatzpunkt, um einen Standard-Dropdown-Filter durch einen Swatch-Filter im Sidebar oder einen Preis-Filter durch einen Alpine-Slider zu ersetzen, ohne den Kern der facettierten Navigation zu patchen.


<?xml version="1.0"?>
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="catalog.leftnav">
            <arguments>
                <!-- Map a filter request field to a dedicated renderer template and view model -->
                <argument name="filter_renderer_map" xsi:type="array">
                    <item name="color" xsi:type="array">
                        <item name="template" xsi:type="string">Mironsoft_LayeredNav::filter/swatch.phtml</item>
                        <item name="view_model" xsi:type="object">Mironsoft\LayeredNav\ViewModel\SwatchFilterViewModel</item>
                    </item>
                    <item name="price" xsi:type="array">
                        <item name="template" xsi:type="string">Mironsoft_LayeredNav::filter/price-slider.phtml</item>
                        <item name="view_model" xsi:type="object">Mironsoft\LayeredNav\ViewModel\PriceSliderFilterViewModel</item>
                    </item>
                    <item name="in_stock" xsi:type="array">
                        <item name="template" xsi:type="string">Mironsoft_LayeredNav::filter/boolean.phtml</item>
                        <item name="view_model" xsi:type="object">Mironsoft\LayeredNav\ViewModel\BooleanFilterViewModel</item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</layout>

3. Filter-Typen anpassen: Dropdown, Swatch und Boolean

Der Standard-Dropdown-Filter von Magento_LayeredNavigation rendert jede Filteroption als einfachen Link mit Linktext und Trefferzahl. Für Attribute mit wenigen, klar unterscheidbaren Werten wie Hersteller oder Material ist das ausreichend, doch für Farbattribute erwartet die Nutzererfahrung heute einen visuellen Swatch-Filter direkt im Sidebar, nicht nur einen Textlink. Wichtig: Dieser Swatch-Filter im Sidebar ist ein eigenständiges Renderer-Template und unterscheidet sich bewusst vom PDP-Swatch, weil er nur Hex-Werte und Trefferzahlen anzeigt und keine Preis- oder Galerie-Updates auslöst.

Boolean-Filter, etwa "Nur Sale-Artikel" oder "Auf Lager", benötigen eine dritte Template-Variante, die statt einer Liste einen einzelnen Toggle rendert. Auch hier gilt das gleiche Muster: eigenes ViewModel, eigenes Template, keine Vermischung mit der Preis- oder Attribut-Logik der facettierten Navigation. Jedes ViewModel implementiert ArgumentInterface und erhält den passenden Filter als konstruktorinjizierte Abhängigkeit, weil der konkrete Filter erst zur Laufzeit aus der Layer-Collection bekannt ist.

Der folgende ViewModel-Ausschnitt zeigt, wie ein Swatch-Filter-Renderer für die facettierte Navigation die rohen Filter-Items in ein für Alpine.js konsumierbares Array transformiert, inklusive Hex-Wert-Escaping und aktivem Zustand pro Option.


<?php

declare(strict_types=1);

namespace Mironsoft\LayeredNav\ViewModel;

use Magento\Catalog\Model\Layer\Filter\FilterInterface;
use Magento\Catalog\Model\Layer\Filter\Item;
use Magento\Framework\Escaper;
use Magento\Framework\View\Element\Block\ArgumentInterface;

/**
 * Prepares swatch filter items for the Alpine-based sidebar renderer.
 */
final class SwatchFilterViewModel implements ArgumentInterface
{
    /**
     * Injects the escaper used to sanitize swatch values before output.
     *
     * @param Escaper $escaper Core escaper service.
     */
    public function __construct(
        private readonly Escaper $escaper,
    ) {
    }

    /**
     * Transforms raw layer filter items into an Alpine-consumable array.
     *
     * @param FilterInterface $filter Active layer filter instance.
     * @return array<int, array<string, mixed>>
     */
    public function getSwatchItems(FilterInterface $filter): array
    {
        $items = [];

        /** @var Item $item */
        foreach ($filter->getItems() as $item) {
            $items[] = [
                'label' => $this->escaper->escapeHtml((string) $item->getLabel()),
                'value' => $this->escaper->escapeHtmlAttr((string) $item->getValue()),
                'swatchColor' => $this->escaper->escapeHtmlAttr((string) $item->getData('swatch_color')),
                'count' => (int) $item->getCount(),
                'isSelected' => (bool) $item->getData('is_selected'),
                'removeUrl' => $item->getUrl(),
            ];
        }

        return $items;
    }
}

4. Preis-Slider mit Alpine.js

Der Standard-Preisfilter von Magento_LayeredNavigation rendert feste Preisspannen als Linkliste, berechnet über einen Algorithmus wie manual, auto oder improved in der Attribut-Konfiguration. Für viele Shops mit breiter Preisspanne ist eine feste Bucket-Liste jedoch weniger nutzerfreundlich als ein Two-Thumb-Range-Slider, der Minimal- und Maximalwert frei wählen lässt. Diesen Baustein der facettierten Navigation ersetzt man vollständig durch eine eigene Alpine.js-Komponente, die die vom Server gelieferten Grenzwerte aus der Facet-Aggregation als Startzustand übernimmt.

Der Alpine-Slider hält seinen Zustand in einem x-data-Objekt mit minValue, maxValue sowie den Grenzen aus der Preisaggregation. Änderungen am Slider werden nicht bei jedem input-Event an den Server geschickt, sondern über einen Watcher mit einer Debounce-Funktion um typischerweise 400 bis 600 Millisekunden verzögert, damit während des Ziehens keine Kaskade von Requests entsteht. Erst nach Ablauf der Debounce-Zeit wird die URL mit den neuen Preisgrenzen aktualisiert und die Filterung ausgelöst.

Wichtig für die Barrierefreiheit dieses Bausteins der facettierten Navigation: Beide Thumbs müssen über Tastatur bedienbar sein, mit role="slider", aria-valuemin, aria-valuemax und aria-valuenow, damit Screenreader den aktuellen Zustand korrekt ansagen. Die formatierte Preisspanne wird dabei nie als statischer Text eingefroren, sondern immer per x-text aus dem aktuellen Alpine-Zustand berechnet.


// Two-thumb price range slider replacing the default price filter links
document.addEventListener('alpine:init', () => {
  Alpine.data('priceRangeFilter', (minBound, maxBound, currentMin, currentMax) => ({
    minBound,
    maxBound,
    minValue: currentMin,
    maxValue: currentMax,
    debounceTimer: null,

    init() {
      this.$watch('minValue', () => this.scheduleUpdate());
      this.$watch('maxValue', () => this.scheduleUpdate());
    },

    scheduleUpdate() {
      clearTimeout(this.debounceTimer);
      this.debounceTimer = setTimeout(() => this.applyFilter(), 500);
    },

    applyFilter() {
      const url = new URL(window.location.href);
      url.searchParams.set('price', `${this.minValue}-${this.maxValue}`);
      window.dispatchEvent(new CustomEvent('layered-nav:filter-change', { detail: { url: url.toString() } }));
    },

    formattedRange() {
      return `${this.minValue} EUR - ${this.maxValue} EUR`;
    }
  }));
});

5. Mobiler Filter-Drawer

Auf mobilen Endgeräten verdrängt eine vollständig ausgeklappte facettierte Navigation den Produktinhalt komplett nach unten. Das etablierte Muster ist ein Off-Canvas-Filter-Drawer, der über einen Button geöffnet wird und mit x-show sowie x-transition animiert von der Seite einfährt. Der Drawer übernimmt exakt dieselben Renderer-Templates wie die Desktop-Sidebar, es ändert sich nur der umgebende Container und dessen Sichtbarkeitszustand.

Ein sticky "Anwenden"-Button am unteren Rand des Drawers ist bei mobiler facettierter Navigation Pflicht, weil Nutzer sonst nach jeder Änderung einzeln scrollen müssten, um die Filterung zu bestätigen. Dieser Button sammelt alle im Drawer vorgenommenen Änderungen im lokalen Alpine-Zustand und löst erst beim Klick die eigentliche Filterung aus, statt wie am Desktop sofort bei jeder Interaktion zu reagieren.

Für Barrierefreiheit braucht der Drawer einen Fokus-Trap: Solange er geöffnet ist, darf Tab-Navigation den Fokus nicht auf Elemente hinter dem Overlay springen lassen, und Escape muss den Drawer schließen und den Fokus auf den auslösenden Button zurücksetzen. Diese Anforderung gilt für jeden Off-Canvas-Baustein der facettierten Navigation gleichermaßen, nicht nur für den Filter-Drawer.


<div
    x-data="{ drawerOpen: false, activeFilterCount: 3 }"
    x-on:keydown.escape.window="drawerOpen = false"
>
    <button
        type="button"
        class="lg:hidden inline-flex items-center gap-2 rounded-lg border border-gray-300 px-4 py-2 text-sm font-semibold"
        x-on:click="drawerOpen = true"
        aria-haspopup="dialog"
    >
        Filter
    </button>

    <div
        x-show="drawerOpen"
        x-transition:enter="transition ease-out duration-200"
        x-transition:enter-start="opacity-0"
        x-transition:enter-end="opacity-100"
        class="fixed inset-0 bg-black/50 z-40"
        x-on:click="drawerOpen = false"
    ></div>

    <div
        x-show="drawerOpen"
        x-transition:enter="transition ease-out duration-300"
        x-transition:enter-start="translate-x-full"
        x-transition:enter-end="translate-x-0"
        x-trap="drawerOpen"
        role="dialog"
        aria-modal="true"
        aria-label="Facettierte Navigation"
        class="fixed inset-y-0 right-0 z-50 w-full max-w-sm bg-white flex flex-col"
    >
        <div class="flex-1 overflow-y-auto p-4">
            <!-- Same renderer templates as the desktop sidebar -->
        </div>
        <div class="sticky bottom-0 border-t border-gray-200 bg-white p-4">
            <button
                type="button"
                class="w-full rounded-lg bg-orange-600 px-4 py-3 text-sm font-bold text-white"
                x-on:click="drawerOpen = false"
                x-text="`Filter anwenden (${activeFilterCount})`"
            ></button>
        </div>
    </div>
</div>

6. Aktive Filter-Chips

Oberhalb der Produktergebnisse zeigt eine "Aktive Filter"-Leiste jeden gesetzten Filter als entfernbaren Chip. Diese Leiste ist ein eigenständiger Baustein der facettierten Navigation und wird typischerweise aus Magento\Catalog\Model\Layer\Filter\Item-Objekten gespeist, die bereits die URL zum Entfernen des jeweiligen Filters enthalten. Jeder Chip besteht aus Filterlabel und einem Schließen-Icon, das per Alpine-Click-Handler die zugehörige URL aufruft.

Die Interaktion mit den Chips muss synchron mit den URL-Query-Parametern bleiben, da Magento den Filterzustand vollständig über GET-Parameter abbildet, etwa ?color=5&price=50-100. Entfernt ein Nutzer einen Chip, muss der entsprechende Parameter aus der URL entfernt werden, ohne die übrigen Filter zu beeinflussen. Bei AJAX-Filterung übernimmt history.pushState diese Aufgabe, bei klassischem Seiten-Reload reicht ein einfacher Link ohne den entfernten Parameter.

Ein "Alle Filter zurücksetzen"-Link neben den Chips ist ein kleines, aber wirkungsvolles Detail: Er führt zur Basiskategorie-URL ohne Filterparameter und sollte nur sichtbar sein, sobald mindestens ein Filter der facettierten Navigation aktiv ist. Das ViewModel prüft dafür einfach die Anzahl aktiver Layer-Filter.

7. AJAX-Filterung ohne vollständigen Seiten-Reload

Ein vollständiger Seiten-Reload bei jeder Filteränderung ist der größte Performance- und UX-Nachteil einer naiven Umsetzung facettierter Navigation. Der modernere Ansatz lädt nur den Produktgrid-Bereich und die aktualisierte Filterliste per fetch nach und aktualisiert die URL mit history.pushState, ohne dass der Browser die Seite neu lädt. Der Alpine-Controller für den Filterbereich hält dafür einen loading-Zustand, der während des Requests eine Skeleton-Ansicht oder einen Spinner über dem Grid einblendet.

Als Alternative zum internen REST-Endpunkt eignet sich eine GraphQL-products(filter:)-Query, die Filterwerte und Produktergebnisse in einem einzigen Request liefert, besonders relevant für Headless- oder PWA-nahe Frontends, die ohnehin GraphQL als primäre Datenschicht nutzen. Die Query liefert sowohl die aggregations für die facettierte Navigation als auch die items des Produktgrids, sodass Filterliste und Ergebnisse aus derselben Antwort synchron aktualisiert werden.

Wichtig bei AJAX-Filterung: Der Browser-Zurück-Button muss weiterhin funktionieren. Das erreicht man, indem man auf popstate hört und bei Rücknavigation denselben Fetch-Zyklus erneut auslöst, statt sich auf den Browser-Cache der vorherigen Seite zu verlassen.


query GetFilteredProducts(
  $categoryId: String!
  $filters: ProductAttributeFilterInput!
  $pageSize: Int!
  $currentPage: Int!
) {
  products(
    filter: $filters
    pageSize: $pageSize
    currentPage: $currentPage
  ) {
    total_count
    items {
      id
      sku
      name
      url_key
      price_range {
        minimum_price {
          final_price { value currency }
        }
      }
    }
    aggregations {
      attribute_code
      label
      count
      options {
        label
        value
        count
      }
    }
    page_info {
      current_page
      page_size
      total_pages
    }
  }
}

8. SEO bei facettierter Navigation

Facettierte Navigation erzeugt potenziell tausende Filterkombinationen pro Kategorie, von denen die meisten für Suchmaschinen irrelevant sind und als Duplicate Content oder Crawl-Budget-Verschwendung auffallen. Magento bietet dafür im Admin unter den Katalog-Einstellungen die layered_navigation-Konfiguration, mit der sich pro Attribut steuern lässt, ob ein Filterwert überhaupt eine eigene, indexierbare URL erzeugt. Für die meisten Attribut-Kombinationen ist noindex, follow die richtige Einstellung: Suchmaschinen folgen den Links weiterhin, indexieren die Kombination aber nicht.

Der canonical-Tag jeder gefilterten Kategorieseite muss konsequent auf die ungefilterte Basis-URL der Kategorie zeigen, sobald mehr als ein Filter aktiv ist. Das verhindert, dass zehn verschiedene Filterkombinationen als zehn unterschiedliche Seiten in den Suchindex gelangen. Einzelfilter mit hohem Suchvolumen, etwa eine populäre Marken- oder Größenfilterung, können bewusst von dieser Regel ausgenommen und mit eigenem canonical auf sich selbst indexierbar gemacht werden, wenn dafür eine echte Suchnachfrage besteht.

Zusätzlich sollte die facettierte Navigation niemals reine JavaScript-Links ohne href-Attribut erzeugen, weil Crawler sonst keine URL zum Folgen finden. Jeder Filterlink im Sidebar bleibt ein echtes <a href="…">-Element, dessen Klick-Verhalten Alpine per @click.prevent zusätzlich für AJAX-Filterung überschreibt, ohne das href für Crawler zu entfernen.

9. Performance: Debounce, Caching, Facet-Requests

Der Preis-Slider aus Abschnitt vier ist die naheliegendste Quelle für übermäßige Facet-Requests, wenn kein Debounce implementiert ist: Ohne Verzögerung würde jedes Pixel Slider-Bewegung einen eigenen Request an Elasticsearch auslösen. Ein Debounce von 400 bis 600 Millisekunden reduziert das auf einen einzigen Request nach Beendigung der Interaktion und ist bei jeder Alpine-Komponente der facettierten Navigation Pflicht, nicht nur beim Preisfilter.

Facet-Caching-Strategien greifen auf zwei Ebenen: Der Full Page Cache darf gefilterte Kategorieseiten grundsätzlich cachen, solange der Filterzustand vollständig über die URL abgebildet ist und keine sessionabhängigen Daten in die Antwort einfließen. Zusätzlich lohnt sich ein kurzlebiger Query-Result-Cache auf Ebene der Elasticsearch-Aggregation für stark frequentierte Kategorien, damit wiederholte identische Facet-Anfragen nicht erneut die komplette Aggregation berechnen.

Zu viele gleichzeitige Facet-Requests entstehen häufig, wenn mehrere Filtergruppen unabhängig voneinander eigene Fetch-Aufrufe auslösen, statt eine einzige kombinierte Anfrage zu stellen. Die saubere Lösung bündelt alle Änderungen an der facettierten Navigation innerhalb eines kurzen Zeitfensters zu einem einzigen Request, bevor die Filterung tatsächlich ausgeführt wird.

Naive Standard-Umsetzung vs. empfohlenes Hyvä-Muster im Vergleich

Aufgabe Naiver Standard-Ansatz Empfohlenes Hyvä-Muster Vorteil
Preisfilter Feste Preisspannen-Links Alpine Two-Thumb-Range-Slider Freie Preiswahl, weniger Klicks
Seiten-Update bei Filterwechsel Voller Seiten-Reload AJAX-Fetch + history.pushState Schnellere Filterung, erhaltener Scroll-Zustand
Farbfilter im Sidebar Text-Dropdown-Liste Swatch-Filter mit Hex-Vorschau Schnellere visuelle Erkennung
Suchmaschinen-Indexierung Kein canonical bei Filterkombination canonical auf Basis-Kategorie, noindex,follow Kein Duplicate Content, erhaltenes Crawl-Budget
Slider-Interaktion Request bei jedem Pixel Debounce 400 bis 600 ms Deutlich weniger Facet-Requests

Mironsoft

Hyvä-Frontend-Entwicklung für Magento 2

Facettierte Navigation, die konvertiert statt frustriert?

Wir bauen Layered Navigation im Hyvä-Theme mit eigenen Filter-Renderern, Alpine-Preis-Slider, mobilem Filter-Drawer und AJAX-Filterung, inklusive SEO-sauberer canonical- und robots-Steuerung für Filterkombinationen.

Filter-Konzeption

ViewModel-Architektur pro Filter-Typ und Layout-XML-Zuordnung

Alpine-Interaktion

Preis-Slider, Filter-Drawer und AJAX-Filterung ohne jQuery

SEO & Performance

canonical-Strategie, Facet-Caching und Debounce-Tuning

10. Zusammenfassung

Facettierte Navigation im Hyvä-Theme besteht aus demselben Magento_LayeredNavigation-Datenmodell wie in Luma, kombiniert mit einer vollständig ersetzten Rendering- und Interaktionsschicht: eigene ViewModels pro Filter-Typ, serverseitig gerenderte Templates und Alpine.js statt Knockout.js. Swatch-Filter im Sidebar, ein Alpine-Preis-Slider mit Debounce, ein mobiler Filter-Drawer mit Fokus-Trap und aktive Filter-Chips, die synchron zur URL bleiben, bilden zusammen eine facettierte Navigation, die sich für Nutzer schnell und nachvollziehbar anfühlt.

AJAX-Filterung mit history.pushState oder eine GraphQL-products(filter:)-Query ersetzen den vollständigen Seiten-Reload und machen Layered Navigation im Hyvä-Theme spürbar responsiver. SEO-seitig verhindert eine konsequente canonical-Strategie mit gezieltem noindex, follow, dass Filterkombinationen das Crawl-Budget verschwenden. Debounce bei Slider-Interaktionen und Facet-Caching auf Elasticsearch-Ebene runden eine produktionsreife Umsetzung facettierter Navigation ab.

Facettierte Navigation im Hyvä-Theme: Das Wichtigste auf einen Blick

Architektur

Eigenes Renderer-Template und ViewModel pro Filter-Typ, zugeordnet über catalog_layer_view.xml, ohne den Core-Block zu verändern.

Alpine-Interaktion

Swatch-Filter, Preis-Slider mit Debounce und Filter-Drawer mit Fokus-Trap ersetzen Knockout-Bindings vollständig.

AJAX & GraphQL

fetch plus history.pushState oder products(filter:) ersetzen den vollständigen Seiten-Reload bei jeder Filteränderung.

SEO & Performance

canonical auf die Basis-Kategorie, noindex, follow für irrelevante Kombinationen, Debounce und Facet-Caching gegen zu viele Requests.

11. FAQ: Facettierte Navigation im Hyvä-Theme

1Was ist facettierte Navigation in Magento 2?
Das Sidebar-Filtersystem einer Kategorieseite, umgesetzt durch Magento_LayeredNavigation mit Facet-Aggregationen aus Elasticsearch oder OpenSearch.
2Ersetzt Hyvä Magento_LayeredNavigation komplett?
Nein, nur die Rendering- und Interaktionsschicht. Das Facet-Datenmodell im Backend bleibt unverändert, Knockout.js wird durch Alpine.js ersetzt.
3Wie füge ich einen Swatch-Filter im Sidebar hinzu?
Über Layout-XML ein eigenes Template und ViewModel pro Filter-Feld zuordnen, getrennt vom PDP-Swatch, mit Hex-Wert-Escaping.
4Warum ein eigener Preis-Slider?
Feste Preisspannen-Links sind unflexibel. Ein Alpine-Two-Thumb-Slider erlaubt freie Preiswahl und braucht weniger Klicks.
5Wie funktioniert der mobile Filter-Drawer?
Off-Canvas-Overlay mit x-show und x-transition, dieselben Renderer wie die Sidebar, sticky Anwenden-Button und Fokus-Trap.
6Wie bleiben Filter-Chips mit der URL synchron?
Jeder Chip trägt eine Entfernen-URL. history.pushState hält AJAX-Filterung synchron, sonst genügt ein Link ohne den entfernten Parameter.
7Ist AJAX-Filterung mit dem FPC kompatibel?
Ja, solange der Filterzustand vollständig über die URL abgebildet wird und keine sessionabhängigen Daten einfließen.
8Welche GraphQL-Query liefert Facet-Daten?
products(filter:) liefert aggregations für die Facetten und items für die gefilterten Produktergebnisse in einem Request.
9Wie verhindere ich Duplicate Content?
canonical auf die Basis-Kategorie bei mehr als einem Filter, plus gezieltes noindex, follow über die layered_navigation-Konfiguration.
10Warum keine reinen JavaScript-Links?
Ohne href-Attribut sind Links für Crawler nicht folgbar. Ein echtes a-Element mit href bleibt Pflicht, Alpine überschreibt nur das Klick-Verhalten.