A-floor
Ein Schaufenster für Einrichtungsgeschäfte mit Tausenden von Produkten
Rolle
Full-Stack-Entwickler / Data Engineer
Jahr
2023
Stack
HTMLSCSS (BEM)JSWordpress & WoocommerceNode.js

Die Aufgabe
Produktziel: Entwicklung eines professionellen Tools zur Materialauswahl (Bodenbeläge, Türen, Möbel etc.) für Innenarchitekten. Die Preise werden ausgeblendet, da sie von der Bestellmenge und dem Partnerstatus abhängen. Die Website soll von einem klassischen Shop zu einem Generator für gezielte Kundengespräche (Anfragen zu spezifischen Produkten und Marken) transformiert werden.
Technische Herausforderung:
-
Umfassende Anpassung von WooCommerce: Kompletter Austausch der Produktdarstellungsschicht, Deaktivierung der Preis- und Warenkorbfunktionen auf Architekturebene (ohne Ausblenden per CSS).
-
Entwicklung eines universellen Filtersystems, das die verfügbaren Optionen in einer Kategorie automatisch anhand der tatsächlichen Produkte berechnet und so die manuelle Konfiguration für Kunden überflüssig macht.
-
Aufbau einer zuverlässigen Katalogbefüllungs-Pipeline: Automatisierung der Erfassung Tausender Artikel aus Dutzenden verschiedener Quellen mit komplexen Strukturen direkt in das WooCommerce-Zieldatenmodell.
Die Phasen
Entwicklung eines Headless-ähnlichen Katalogs
- Überschreiben der grundlegenden WooCommerce-Templates (
content-product.phpetc.). - Integration von "Preis anfragen"-Lead-Buttons mit unsichtbarer Kontextübergabe (
data-product,data-brand) an Contact Form 7.
Entwicklung einer dynamischen Filter-Engine
- Schreiben der Logik (
get_category_filters()), die globale Attribute durchläuft und nur die Eigenschaften ausgibt, die in der jeweiligen Kategorie tatsächlich existieren. - Implementierung serverseitiger Filterung über
pre_get_postsmit Zustandsserialisierung in der URL.
AJAX-Optimierung & UX
- Entwicklung von 5 nativen Endpunkten über
admin-ajaxfür Live-Suche und das Filtern von Projekten/Marken. - Integration von
history.pushStatezur Beibehaltung der URL bei AJAX-Anfragen (Links bleiben auch nach dem Neuladen gültig).
Data Engineering (Scraping & Import)
- Schreiben von Playwright-Skripten zum Parsen von Hersteller-Websites.
- Direktes Daten-Mapping (inklusive maschineller Übersetzung ausländischer Beschreibungen via DeepL API) in CSV-Dateien, deren Kopfzeilen strikt dem WooCommerce-Import-Schema-Vertrag entsprechen.
Frontend & UI-Build
- Entwicklung einer unabhängigen Layout-Schicht mit Gulp + Webpack (BEM, ES-Module).
- Erstellung eines "nahtlosen" responsiven Designs ohne abgestufte Media-Queries unter Verwendung von
adaptiveValue()-Mixins zur fließenden Interpolation von Typografie und Abständen.
Der Prozess
Lösungen
Ein Schaukatalog ohne Preise und Warenkorb
Der Katalog läuft auf WooCommerce — wegen dessen Datenmodell: Produkte, hierarchische Kategorien, globale Attribute, Mediengalerien und ein Standard-Backend, in das sich die Content-Managerin des Kunden in einer Stunde einarbeitet.
Den „Shop ohne Shop" habe ich aber nicht dadurch gebaut, dass ich den Warenkorb nachträglich abgeschaltet hätte. Preise über Filter wie woocommerce_get_price_html zu unterdrücken ist der Weg, auf dem der Preis früher oder später doch wieder auftaucht: in strukturierten Daten, in einer Transaktionsmail, im Widget eines Drittanbieter-Plugins. Stattdessen habe ich die Rendering-Schicht vollständig ersetzt: content-product.php, single-product.php und taxonomy-product-cat.php sind durch eigene Templates überschrieben, die die Preis- und Warenkorb-Hooks schlicht nicht aufrufen. Der kommerzielle Block ist nicht versteckt — er existiert in der Ausgabe nicht.
An der Stelle des „In den Warenkorb"-Buttons steht ein Lead-Button — „Preis anfragen" — und er trägt seinen Kontext mit sich: data-product und data-brand. Beim Öffnen des Popups werden diese Werte in versteckte Formularfelder geschrieben, sodass der Vertrieb die Anfrage bereits mit Bezug auf ein konkretes Produkt und eine konkrete Marke erhält — ohne Rückfrage, worum es eigentlich ging. Beim Absenden des Formulars wird ein Yandex-Metrica-Ziel ausgelöst; damit ist die Conversion des Katalogs bis auf die einzelne Produktkarte messbar.
Filter, die pro Kategorie berechnet werden
Das ist das ingenieurtechnische Kernstück des Projekts. Jede Warengruppe hat ihren eigenen Satz an Merkmalen: Bodenbeläge haben Holzart, Sortierung, Fase, Nutzungsklasse, Verbindungsart, integrierte Trittschalldämmung; Türen haben etwas völlig anderes. Das als Einstellungsliste pro Kategorie zu pflegen hätte den Kunden bei jeder Sortimentserweiterung zu Handarbeit verurteilt.
Deshalb wird der Filtersatz aus den Daten abgeleitet. get_category_filters() durchläuft sämtliche globalen WooCommerce-Attribute und prüft für jeden Wert, ob in der aktuellen Kategorie mindestens ein Produkt diesen Wert tatsächlich trägt. Übrig bleiben nur die Merkmale — und nur die Ausprägungen —, die in dieser Kategorie wirklich vorkommen; Attribute ohne Schnittmenge werden gar nicht erst ausgegeben.
Der praktische Effekt: unter „Laminat" sieht die Planerin Nutzungsklasse und Verbindungsart, unter „Türen" einen anderen Satz, und das trägt sich selbst. Der Kunde legt im Backend ein neues Attribut an und weist es Produkten zu — der Filter erscheint in den betreffenden Kategorien, ohne eine einzige Zeile Code.
Die ersten drei Gruppen sind standardmäßig aufgeklappt, der Rest liegt in einem Akkordeon, damit eine lange Liste nicht den Viewport auffrisst. Die Sidebar bleibt beim Scrollen stehen; mobil wird sie zu einem ausfahrenden Panel, die Seite dahinter wird gegen Scrollen gesperrt.
Der Filterzustand wird in den Query-String der URL serialisiert und serverseitig über pre_get_posts angewendet. Der Handler ist generisch geschrieben: Jeder GET-Parameter, zu dem eine existierende Attribut-Taxonomie passt, wird zu einer tax_query-Bedingung. Nichts ist auf konkrete Merkmale hart verdrahtet. Ein Nebeneffekt der serverseitigen Filterung: Eine gefilterte Auswahl lebt unter ihrer eigenen URL — man kann sie einem Kunden per Messenger schicken oder als Lesezeichen ablegen.
Die AJAX-Schicht
Fünf Endpunkte, alle über admin-ajax, alle liefern fertige, PHP-seitig gerenderte Markup-Fragmente — so ist die Produktkarte an einer Stelle definiert und sieht überall gleich aus.
| Was | Wo es läuft |
|---|---|
| Live-Suche nach Produkten | Dropdown-Panel im Header, 1 s Debounce |
| Projektfilter nach Tags | Katalog der realisierten Projekte |
| Markenfilter nach Warengruppe | Startseite und Markenkatalog |
| Auswahl beliebter Produkte | Startseite |
Die Suche liefert zu jedem Treffer Vorschaubild, Bezeichnung, Kategorie und Marke — die Planerin erkennt das Produkt direkt in den Ergebnissen, ohne die Seite zu öffnen.
Der Projektfilter synchronisiert seinen Zustand zusätzlich über history.pushState mit der Adressleiste und stellt den gewählten Tag beim Laden wieder her. Ein Link auf „Projekte in Nowosibirsk" bleibt ein Link — teilbar, und er übersteht Reload und Zurück-Button.
Befüllung des Katalogs: Datenerfassung → CSV → Import
Mehrere tausend Positionen lassen sich nicht von Hand anlegen, und ohne dicht gefüllte Attribute ist die ganze Konstruktion der berechneten Filter sinnlos — es gäbe nichts zu filtern. Der Katalog wurde deshalb über eine automatisierte Pipeline befüllt: pro Hersteller ein eigenes Erfassungsskript auf Playwright-Basis.
Die entscheidende Designentscheidung war, nicht „erst sammeln, dann auf Felder verteilen", sondern direkt in das Zieldatenmodell zu schreiben. Der Collector gibt CSV aus, dessen Kopfzeilen bereits das WooCommerce-Importschema darstellen:
post_title; images; attribute:pa_collection; attribute:pa_chamfer;
attribute:pa_coating; attribute:pa_texture_color; tax:product_cat
Es gibt kein Zwischen-Mapping: Die Datei geht ins Import-Plugin, und die Merkmale werden unmittelbar zu genau den Attributen, aus denen die Kategorieseite ihre Filter zusammensetzt. Das Schema des Collectors und das Schema des Katalogs sind ein Vertrag und nicht zwei Dinge, die man von Hand synchron halten muss.
Jedes Skript ist gleich aufgebaut: Paginierung des Katalogs durchlaufen, Links deduplizieren, Produktseiten einzeln parsen, Merkmale in ein Dictionary extrahieren und anschließend auf einen gemeinsamen Feldsatz normalisieren. Produkte ohne Titel werden übersprungen; ein Fehler auf einer einzelnen Seite wird protokolliert und reißt nicht den gesamten Durchlauf mit — bei tausenden Seiten ist das entscheidend. Für Sites mit Anmeldung wird die Session aus gespeichertem Zustand wiederverwendet; fremdsprachige Herstellerbeschreibungen laufen unter Beibehaltung der Formatierung durch DeepL.
Marken als eigenständiger Bereich
Für Innenarchitektinnen ist die Marke ein ebenso wichtiger Einstiegspunkt wie die Kategorie: Die Auswahl beginnt oft mit „ich brauche genau diesen österreichischen Hersteller". Die Marke ist deshalb als globales WooCommerce-Attribut mit eigener Permalink-Regel und eigenem Seitentemplate umgesetzt: /brand/{slug}. Beschreibung, Land, Gründungsjahr, Logo (in zwei Zuständen — normal und für Hover) und Hintergrund liegen in ACF-Feldern am Term selbst.
Der Markenkatalog ist ein A–Z-Register über lateinische und kyrillische Initialen hinweg, mit korrekter Gruppierung mehrbyte-codierter Anfangsbuchstaben, dazu eine Live-Suche nach Namen und ein Filter nach Warengruppe. Drei Wege zur Marke für drei verschiedene Situationen: Ich kenne den Namen, ich kenne den Buchstaben, ich weiß nur, dass ich Türen brauche.
Favoriten als Moodboard
Eine Planerin stellt sich Materialien für ein Projekt zusammen — das ist näher am Moodboard als am Warenkorb. Die Speicherung ist an YITH Wishlist delegiert (serverseitig, übersteht bei angemeldeten Nutzern also den Gerätewechsel), aber die Favoritenseite nutzt nicht den Shortcode des Plugins: Sie liest die Einträge direkt aus und rendert sie mit derselben Karte wie der Katalog — samt „Preis anfragen"-Button.
So bleiben die Favoriten ein Einstieg ins Gespräch statt einer Sackgasse in Gestalt eines Vor-Warenkorbs. Der Zähler im Header aktualisiert sich ohne Reload, ausgelöst durch die Events beim Hinzufügen und Entfernen.
Frontend und Build
Das Frontend entstand als eigenes Projekt auf Gulp + Webpack: 13 Seiten, zusammengesetzt aus rund 34 wiederverwendbaren Partials, SCSS nach BEM.
Das JavaScript besteht aus nativen ES-Modulen, ohne jQuery in der Frontend-Schicht. Sechs eigenständige Swiper-Konfigurationen, darunter die Kopplung der Thumbnails an die Hauptgalerie auf der Produktseite. Die Projektgalerie ist lightGallery über einem Justified-Layout: Interieuraufnahmen unterschiedlicher Seitenverhältnisse fügen sich zu Reihen gleicher Höhe, ohne dass der Bildausschnitt beschnitten wird — bei einem Projekt, in dem das Visuelle im Mittelpunkt steht, ist genau das der Punkt.
Der Build automatisiert die Routine: Bildkonvertierung nach WebP mit <picture>-Wrapper, SVG-Sprites, Schriftkonvertierung nach woff2, Zusammenführung der Media Queries, Autoprefixing und Cache-Busting per Zeitstempel.
Aufbau der Website
- Katalog — verschachtelte Kategorien (Bodenbeläge → Laminat, Vinyl, Parkett, Teppichboden; Möbel → Sofas, Tische, Stühle, Regale, Vitrinen), Filter pro Kategorie, Produktseite mit Galerie, technischen Daten und verwandten Artikeln
- Marken — A–Z-Register, Markenseite mit Beschreibung und zugehörigen Produkten
- Projekte — Katalog realisierter Interieurs, filterbar nach Tags; Projektseite: Aufgabe, Jahr, Ort, Planer, Fotograf, verwendete Materialien, Galerie
- Wissenswertes — Blog, News, Veranstaltungen, Sonderangebote; jeder Bereich mit eigenem Beitragstemplate
- Favoriten, Über uns, Kontakt mit Showroom-Karte
Stack
Backend — WordPress, WooCommerce (Katalogmodell, Attribute, Kategoriehierarchie), ACF (Felder für Projekte, Marken, Produkte), YITH Wishlist, Contact Form 7 mit programmatischer Übergabe des Produktkontexts
Frontend — SCSS / BEM, native ES-Module, Swiper, lightGallery, noUiSlider, Tippy.js, Google Maps JS API
Katalogbefüllung — Node.js, Playwright, csv-writer, DeepL API; Import in WooCommerce über WebToffee Import Export
Build — Gulp 4, Webpack 5, HTML-Assemblierung aus Partials, WebP-Pipeline, SVG-Sprites, Schriftkonvertierung, Cache-Busting
Analytics — Yandex Metrica mit Zielen beim Formularversand, Cookie-Consent-Banner
Die Ergebnisse
Playwright-Skripte für das automatisierte Parsen
tausend Artikel
hartcodierte Filter
benutzerdefinierte AJAX-Endpunkte