Strapi & 11ty als Headless CMS für Shopware 6
Krzysztof KaszanekLesezeit: 11 Minuten
Die Herausforderung
Einer der von uns entwickelten und betreuten Shopware 6-Onlineshops—NFC-Tag-Shop.de—verwendete zwei externe, veraltete CMS-Plattformen (eine davon wurde später für komplexere Seiten hinzugefügt) für seine sogenannten “SEO-Seiten”. Diese Seiten beinhalteten Tutorials, FAQs und wiki-ähnliche Seiten - kurz gesagt, Seiten, die sich auf das Shop-Angebot bezogen und als Wissensbasis für Kunden und Interessenten der Technologie und Produkte dienten.
Beide CMS waren stark veraltet und ließen einige grundlegende Funktionen für Content- Redakteure vermissen, und die Pflege zweier getrennter Systeme wurde immer mühsamer. Das Team von NFC-Tag-Shop.de wollte die “SEO-Seiten”-Bearbeitung von der Shopware- Inhaltsbearbeitung entkoppeln, um sie für die Nutzer flexibler und performanter zu gestalten, mit dem zusätzlichen Vorteil, dass sie unabhängig von Shopware-Upgrades ist.
Deshalb wurde die Entscheidung getroffen, die Seiten auf eine neue CMS-Plattform zu migrieren.
Zu diesem Zeitpunkt prüften wir mehrere Optionen
- Shopware als eingebautes CMS
- Headless CMS Backend und Shopware als CMS-Frontend
- Headless CMS-Backend und separates CMS-Frontend, Verwendung von Shopware als Proxy zum Rendern der in die Basisseitenvorlage eingebetteten CMS-Seiten
- Headless CMS-Backend und separates CMS-Frontend, Nutzung der Shopware-API zum Abrufen der notwendigen Basisseitenelemente wie Header & Footer
All diese Lösungen bringen ihre eigenen Vor- und Nachteile mit sich, auf die wir in diesem Blogbeitrag jedoch nicht näher eingehen werden. Nach mehreren Diskussionen kamen wir zu dem Schluss, dass die letzte Option in diesem speziellen Fall die beste Wahl ist. Bei dieser Entscheidung spielte sicherlich eine große Rolle, dass der CMS-Teil in diesem Szenario weitestgehend unabhängig von Shopware 6 bleibt.
Für uns bei NFC21 dreht sich SEO um die Erstellung von reichhaltigen Inhalten, die dazu dienen, die Nutzung unserer NFC-Produkte zu erklären, zu demonstrieren und zu fördern. Unsere Mission ist es, ein umfassendes Benutzererlebnis mit Informationsmaterial, Tutorials, Gerätedatenbanken und mehr zu bieten. Daher benötigten wir mehr als nur einen SEO-Blog!
Wir kamen zu dem Schluss, dass wir ein System benötigen, das die Beziehungen zwischen den Inhalten erleichtert und gleichzeitig die bestmögliche Leistung und Benutzererfahrung bietet.
Zusammen mit Kiwee haben wir einen Ansatz entwickelt, der sich nahtlos in unsere E- Commerce-Systeme integriert und eine solide Basis für zukünftige Erweiterungen bietet.
Von Anfang an war uns klar, dass ein einfacher, blogähnlicher Ansatz nicht ausreichen würde.
Auswahl der Technologie
Es gibt viele Headless-CMS auf dem Markt. Wir haben einige der gängigen Lösungen in Betracht gezogen und uns für Strapi entschieden. Die wichtigsten Faktoren waren:
- Starke Unterstützung für die Modellierung komplexer relationaler Daten.
- Umfangreiches Komponentensystem.
- Es handelt sich um ein Open-Source-Projekt!
- Große Unterstützung durch die Community.
Da der Inhalt der CMS-Seiten statisch und für alle Benutzer gleich war, entschieden wir uns für einen Jamstack-ähnlichen Ansatz und produzierten die Seiten mithilfe eines statischen Website-Generators vor. Unsere Wahl fiel auf Eleventy aufgrund seiner Einfachheit, Flexibilität (sowohl in Bezug auf die Projektstruktur als auch auf die Verwendung von Template-Engines) und des Verzichts auf obligatorisches Javascript auf der Client-Seite.
CMS-Architektur
Die Architektur des neuen CMS, das wir entwickelt haben, besteht aus verschiedenen Diensten.
Strapi ist ein Headless CMS, das eine API für den Abruf von Inhalten bereitstellt. Jede
Ac nderung am Inhalt sollte die Generierung statischer Seiten über einen Webhook auslö sen.
Shopware Seitenelemente API bietet benutzerdefinierte Endpunkte für Shopware, die Teile von NFC-Tag-Shop.de zurückgeben, die gemeinsam zwischen den SEO-Seiten und dem Shop selbst bestehen.
Eleventy (11ty) ist ein Static Site Generator, der die Inhalte von Strapi und der genannten benutzerdefinierten API abruft und statische Seiten erstellt. Er verarbeitet die Daten und rendert dann HTML-Vorlagen mit ihnen.
Implementierung eines Headless CMS
Der Prozess der Umsetzung kann in drei Hauptphasen gegliedert werden.
Erstellung von Inhaltstypen in Strapi
Der erste Schritt in unserem Prozess war die Definition von Inhaltstypen. In Strapi sind Inhaltstypen Sammlungen von spezifischen Inhalten. Wir begannen mit der Erstellung eines UML-Diagramms, das alle Entitäten des alten CMS repräsentierte. Dies bot uns eine ausgezeichnete Gelegenheit, die Typdefinitionen zu überprüfen und zu optimieren. Anschließend erstellten wir die tatsächlichen Inhaltstypen und Komponenten in Strapi, einschließlich aller Beziehungen zwischen ihnen.
Es ist wichtig zu beachten, dass eine CMS-Seite nicht unbedingt einen Strapi- Inhaltstyp benötigt und dass ein Inhaltstyp nicht zwingend Seiten generieren muss. Ein Inhaltstyp namens Autor könnte beispielsweise Entitäten für jeden Inhaltsproduzenten auf Ihrer Website enthalten. Diese Daten könnten mit dem Inhaltstyp Artikel verknüpft sein
(über eine Beziehung), aber Sie könnten sich dazu entscheiden, nicht für jeden Autor eine eigene Seite zu erstellen.
Datenmigration aus dem alten CMS in Strapi
Nach der Definition der Inhaltstypen starteten wir die Vorbereitung der Datenmigrationsskripte. Mit Hilfe von Datenbank-Dumps aus dem vorherigen CMS konvertierten wir die Daten, um eine Kompatibilität mit den Strapi-Inhaltstypen zu gewährleisten, und generierten die Entitäten mithilfe der Strapi-API.
Da jede Datenmigration einzigartig ist und stark von den jeweiligen Quell- und Zielsystemen sowie projektspezifischen Details abhängt, werden wir hier nicht detailliert darauf eingehen. Ich möchte jedoch einige Aspekte hervorheben, die uns Kopfzerbrechen bereitet haben und die bei der Migration von CMS-Inhalten leicht übersehen werden
können.
Shortcodes Viele CMS nutzen Shortcodes, die wiederholbare Blöcke von HTML-Markup repräsentieren. Bei der Migration zu einer anderen Plattform werden diese Shortcodes nicht mehr funktionieren. Abhängig davon, wie häufig sie in der vorherigen Implementierung genutzt wurden, sollten Sie entweder eine Liste der Anwendungen für die manuelle Ersetzung erstellen oder ein automatisiertes Skript zur Ersetzung von Shortcodes durch entsprechende Komponenten im neuen CMS entwickeln.
Bilder Vergewissern Sie sich, dass Sie alle im vorherigen System verwendeten Bilder kopieren. Sollte sich der Medienpfad nach der Migration ändern, denken Sie daran, alle URLs der Bilder im Inhalt zu aktualisieren.
Benutzerdefinierte Shopware-API zur Bereitstellung essenzieller Seitenelemente
Um das einheitliche Erscheinungsbild mit dem Shopware-Shop zu gewährleisten, war es von Bedeutung, dass die neuen CMS-Seiten in dieselbe Kopf- und Fußzeile eingebettet sind und das gleiche Design wie die Shop-Seiten aufweisen.
Wir haben mehrere benutzerdefinierte API-Endpunkte in Shopware implementiert, die für folgende Aufgaben zuständig waren:
- Bereitstellung der Kopf- und Fußzeilenvorlagen. Die Endpunkte liefern die für die CMS- Seiten angepassten HTML-Vorlagen.
- Allgemeine Meta-Tags, die in den
<head>
der CMS-Seiten eingefügt werden sollten. - CSS- und JS-Bündel, die von Shopware genutzt werden, um das einheitliche Styling und die Interaktivität in der Kopf- und Fußzeile sicherzustellen.
Implementierung von Seitenvorlagen in 11ty
Der abschließende Schritt bestand in der Neuimplementierung von Seitenvorlagen aus den vormaligen CMS-Plattformen in 11ty.
Wir entschieden uns, die Migration als Chance zu sehen, um sie zu überarbeiten und zu optimieren, statt sie bloß 1:1 neu aufzusetzen.
Zu diesen Verbesserungen zählten die Optimierung des CSS, das Maximieren der Anwendung von Standard-Bootstrap-Klassen und -Komponenten, die Aufbesserung des Schema.org-Markups durch die Integration neuer und die Verbesserung bereits bestehender Elemente sowie der Austausch von HTML-Elementen durch solche, die in einem gegebenen Kontext semantisch passender sind.
Ich möchte einige der faszinierendsten Aspekte des Einsatzes von 11ty als statischem Website-Generator für Strapi CMS beleuchten.
Verständnis der 11ty-Datenkaskade
Der Wechsel von Frameworks wie Vue.js oder React, die eine eher spezifische Herangehensweise an die Datenverarbeitung haben, erfordert bei 11ty ein Umdenken.
In puncto Datenverarbeitung zeigt sich 11ty äußerst flexibel. Es bietet die Möglichkeit, Daten auf sieben verschiedenen Ebenen der data cascade hinzuzufügen, beispielsweise Frontmatter in den Vorlagen, berechnete Daten oder globale Daten, um nur einige zu nennen.
Mit erhöhter Flexibilität kommt allerdings auch eine größere Komplexität. Anfangs fand ich es schwierig zu entscheiden, wo und wie ein bestimmtes Datenpaket gespeichert werden sollte. Sollte ich die Sammlung von Seiten, die ich von Strapi abrufe, in eng definierten Vorlagendateien speichern? Aber was, wenn ich einen Teil derselben Sammlung in einer anderen Vorlage darstellen muss, muss ich sie dann erneut abrufen? Wäre es vielleicht besser, sie im globalen Datenobjekt zu speichern?
Letztendlich ist es wichtig zu verstehen, dass 11ty viele Wege bietet, das gleiche Problem zu lösen, es gibt keinen einzig “richtigen” Weg. Wir haben uns dafür entschieden, die meisten Sammlungen in globalen Daten zu speichern, weil wir einen erheblichen “Cross-Use” von verschiedenen Sammlungen zwischen den Seiten hatten und dies für uns am bequemsten schien. Würde es auch funktionieren, wenn wir uns für den eingeschränkteren, templatebezogenen Ansatz der Datenverarbeitung entscheiden würden? Sicherlich, das ist nur eine weitere Möglichkeit, damit umzugehen.
Paginierung: Nicht nur für Seitenumbrüche nützlich
11ty bietet eine Funktion namens Pagination. Der Name dieser Funktion ist ein wenig irreführend und verdeckt ihr wahres Potenzial. Wie in der Dokumentation erläutert: “Die Paginierung ermöglicht es Ihnen, durch einen Datensatz zu iterieren und mehrere Dateien aus einer einzigen Vorlage zu generieren”. Dies erwies sich nicht nur für die Präsentation von Seitensammlungen als unerlässlich, sondern auch für einzelne Seiten! Wie kann das sein? Der gesamte Inhalt ist lokalisiert, was bedeutet, dass selbst eine einzelne Seite technisch gesehen eine Sammlung ist, die deutsche und englische Übersetzungen enthält.
Permalinks: Wie verlinke ich auf andere Seiten?
Wir haben einige Zeit investiert, um eine zuverlässige Methode zur Handhabung von Permalinks und Querverweisen zwischen Seiten in 11ty zu finden.
Angenommen, wir arbeiten an Seiten für Blogposts. Jeder Blogbeitrag hat ein Slug-Feld in Strapi, das als Grundlage für den Permalink dient. In 11ty wurde der Slug lokalisiert, indem ein Sprachpräfix (z. B. /en
), ein Inhaltstyp-Präfix (z. B. /blog
) und möglicherweise eine Kategorienhierarchie hinzugefügt wurden. Der finale Permalink könnte so aussehen: /de/blog/tutorials/wie-man-permalinks-managt-in-11ty
. All diese “Verarbeitung” findet in der Vorlage für die Blogpost-Seite statt.
Nun, angenommen, wir haben eine Autorenseite und möchten alle Blogbeiträge dieses Autors verlinken. Aber wie bekommen wir den Permalink des Blogposts? In der API-Antwort von Strapi sollten wir die Slugs der Blogbeiträge finden, die von einem bestimmten Autor verfasst wurden. Wie Sie oben gesehen haben, kann die endgültige Form eines Permalinks jedoch stark variieren, sodass der Slug allein nutzlos ist. Die Logik zur Generierung von Blogpost-Permalinks in der Vorlage für die Autorenseite zu duplizieren, scheint keine gute Idee zu sein.
Nach einigen Überlegungen und dem Testen verschiedener Ansätze wurde uns klar, dass 11ty Collections genau das waren, was wir suchten. Das Sammlungsobjekt ist in allen Vorlagen verfügbar und ermöglicht den Zugriff auf die Attribute anderer Seiten (einschließlich Permalink).
Durch die Kombination von eindeutigen tags
zur Kennzeichnung von Seitentypen und der Implementierung eines benutzerdefinierten permalink()
-Filters in Nunjucks können wir problemlos Links zwischen verschiedenen Seitenvorlagen erstellen.
Rendering von dynamischen Zonen aus Strapi
Dynamische Zonen bieten eine flexible Methode zur Zusammenstellung von Inhalten aus Strapi-Komponenten.
Es gibt zahlreiche Möglichkeiten, das Rendering von Strapi-Komponenten in 11ty zu implementieren. Sie können Nunjucks-Makros verwenden, Vue oder React einbetten und damit Komponenten erstellen oder das 11ty-Tool WebC nutzen.
Zu Beginn haben wir Nunjucks macros, genutzt, doch dieser Ansatz hat ein ernstes Problem - er unterstützt keinen asynchronen Code. Gleich werden wir sehen, warum dies für einige Komponenten ein Hindernis darstellt. Aus diesem Grund und aufgrund weiterer Vorteile gegenüber Makros, wie der automatic assets bundling, haben wir zu WebC gewechselt.
Die Idee besteht darin, alle Komponenten einer dynamischen Zone in einer Schleife zu rendern und die Parameter an eine Middleware <components>
zu übergeben.
Die Leistungsfähigkeit von WebC
Betrachten wir die Funktionen von WebC anhand des Beispiels der
Der faszinierendste Teil ist, wie WebC uns ermöglicht, den für das Rendern einer bestimmten Komponente erforderlichen Inhalt dynamisch abzurufen. Dies ist der Punkt, an dem Nunjucks Makros versagen, da sie keine asynchronen Aufrufe zulassen.
Diese Komponente fügt auch benutzerdefiniertes CSS im <style>
-Tag hinzu. Dank des WebC-Bündelungsmodus wird dieses CSS nur geladen, wenn Sie eine Seite mit dieser speziellen Komponente besuchen. Sie müssen nur Folgendes hinzufügen
<style @raw="getBundle('css')" webc:keep></style>
in den <head>
und voila! Wir haben kostenloses, komponentengesteuertes CSS.
Gewonnene Erkenntnisse
Die Arbeit an diesem Projekt war äußerst interessant und wir haben viel über die Herausforderungen von Headless CMS im Allgemeinen sowie Strapi und 11ty im Besonderen gelernt. Werfen wir einen Blick auf einige der Erkenntnisse, die wir mitnehmen konnten.
11ty Build-Zeiten
Wenn man mit 11ty arbeitet, kann man leicht in die Falle geraten, die Build-Leistung zu vernachlässigen. Der Code wird lediglich während des Builds ausgeführt und hat daher keine Auswirkungen auf den Endbenutzer. Zudem werden nur einige statische Seiten erstellt, also sollte der Prozess schnell ablaufen, nicht wahr?
Es zeigt sich jedoch, dass dies nicht so offensichtlich ist, besonders wenn man mit recht großen Seitensammlungen arbeitet. In unserem Fall generieren wir bei jedem Build insgesamt ~2500 Seiten, was deutlich mehr Zeit in Anspruch nimmt, als wir ursprünglich erwartet hatten. Infolgedessen mussten unsere Redakteure recht lange warten, um die von ihnen vorgenommenen Änderungen in der Live-Instanz zu sehen, was das Risiko erhöhte, die Build-Pipeline durch zu viele gleichzeitige Builds zu blockieren.
Nach einigen Optimierungen konnten wir akzeptable Erstellungszeiten erreichen, aber es ist immer noch nicht ideal. Wir planen daher, eine umfangreichere Caching-Lösung zu implementieren, die sowohl API-Antworten als auch die von 11ty konvertierten Daten beinhaltet, um die Situation weiter zu verbessern.
Strapi ist weniger ausgereift, als wir angenommen hatten
Nach umfassenden Recherchen entschieden wir uns für Strapi als unsere Headless-CMS-Plattform. Im Vergleich zu anderen Plattformen erschien sie uns am ausgereiftesten, mit der größten Community im Hintergrund und insgesamt die sicherste Wahl für unser erstes Headless-CMS-Projekt.
Ich bin der Ansicht, dass wir, selbst mit dem Wissen, das wir jetzt haben, keine andere Plattform auswählen würden, wenn wir diese Entscheidung erneut treffen müssten. Dennoch gibt es einige Aspekte, die wir von Strapi erwartet hätten, besonders nach unserer Arbeit mit WordPress oder dem eingebauten Shopware Page Builder.
Einschränkungen in der Layout-Verwaltung
Ein recht gängiges Szenario in der CMS-Welt sieht so aus: Sie haben verschiedene Komponenten und möchten diese in einem Raster anordnen, beispielsweise die Bildkomponente in der ersten Spalte und daneben die Textkomponente in der zweiten Spalte.
Image |
Text paragraph |
---|
Wie sich herausstellt, gibt es derzeit (Stand v4.19) keine Möglichkeit, dies in Strapi zu realisieren.
Unser erster Gedanke war: “Wir setzen das einfach selbst um, indem wir Strapi-Komponenten für Zeilen und Spalten erstellen, richtig?”
Naja, nicht ganz. Es ist nicht möglich, dynamische Zonen zu Komponenten hinzuzufügen, was bedeutet, dass Sie für jede Komponente, die in diese Spalte eingefügt werden soll, ein separates Feld hinzufügen müssten. Das ist nicht das, was wir beabsichtigen, und es wäre auch nicht skalierbar, wenn die Anzahl der Komponenten zunimmt.
Momentan ist dies eine der am häufigsten von der Strapi-Community geforderten Funktionen. Nach diesem Thread auf der Strapi-Feedback-Plattform zu urteilen, waren wir nicht die einzigen, die davon ausgingen, dass diese Funktion in einem so ausgereiften CMS vorhanden sein würde. Obwohl diese Funktion bereits 2020 erstmals angefragt wurde und es eine hohe Nachfrage gibt, ist sie noch nicht einmal in der Strapi-Roadmap aufgeführt.
Alle Mediendateien werden in einem einzigen, flachen Verzeichnis gespeichert
In den meisten gebräuchlichen Frameworks werden Mediendateien auf die eine oder andere Weise in Verzeichnissen organisiert. WordPress etwa sortiert Medien nach Jahr und Monat und lädt die Datei in das Verzeichnis /wp-content/uploads/2024/01/
hoch. Strapi hingegen lädt alles in den Ordner /uploads
hoch, in einer flachen Struktur. Selbst wenn Sie die Funktion “Ordner” im Strapi-Dashboard nutzen, landet die Datei auf Dateisystemebene stets im gleichen Verzeichnis /uploads
.
Dies kann zu Leistungsproblemen beim Durchsuchen von Verzeichnissen führen, wenn die Anzahl der Mediendateien in einem einzelnen Ordner zunimmt. Besonders wenn Sie die Funktion “Responsive friendly upload” aktiviert haben, resultiert ein Upload in 5 verschiedenen Dateien.
Verknüpfungen von zugehörigen Entitäten sind nicht lokalisiert
Angenommen, wir haben zwei Inhaltstypen in Strapi, Smartphone und Vendor, und beide können in zwei Sprachen übersetzt werden: Deutsch und Englisch. Die Seiten, die das Smartphone: iPhone 14 darstellen, sollten den gleichen Vendor haben: Apple, unabhängig von der Sprache. In Strapi kann man ein Feld oder eine Beziehung als “gemeinsam für alle Gebietsschemata” festlegen. Das funktioniert so: Wenn Sie Apple als Hersteller der deutschen Übersetzung von iPhone 14 zuweisen, wird der englischen Version automatisch der gleiche Hersteller zugewiesen, sodass die Inhaltsredakteure dies nicht für jede Sprache wiederholen müssen.
Das Problem, das wir anfangs nicht erwartet haben, ist, dass die Beziehungszuweisung nicht automatisch lokalisiert wird. Es ist jedoch kein Fehler von Strapi, denn die Option sagt wörtlich “gemeinsam für alle Sprachumgebungen”.
Infolgedessen konnten wir uns nicht auf die von der Strapi-API zurückgegebene ID der zugehörigen Entität verlassen. Sie könnte sowohl Englisch als auch Deutsch sein, je nachdem, welche Übersetzung zuerst festgelegt wurde.
Wir haben einfach irrtümlich angenommen, dass es so funktioniert. Es wäre eine angenehme Option, auch wenn sie für andere Felder als Entitätsbeziehungen keinen Sinn macht. Eine andere Möglichkeit wäre, den Controller des Inhaltstyps Smartphone zu überschreiben, um sicherzustellen, dass der zugehörige Vendor in der API-Antwort immer lokalisiert ist.
Feldnamen und Beschreibungen sind nicht Teil des Modells
Es ist oft nützlich, einem Feld einen aussagekräftigen Namen und eine Beschreibung hinzuzufügen. Betrachten Sie beispielsweise das Feld “productId”, das in einer unserer Komponenten vorhanden ist. Soll es die Produkt-UUID aus Shopware sein? Oder vielleicht die SKU des Produkts? Um den Redakteuren die Arbeit zu erleichtern, verwenden wir Beschriftungen und Beschreibungen, um den Kontext für solche Felder bereitzustellen.
Wenn Sie einen Inhaltstyp von Component in Strapi erstellen, wird eine entsprechende JSON-Datei erstellt, die eine Liste aller Felder, ihren Typ, ob sie obligatorisch sind, usw. enthält. In Strapi wird diese als Schemadatei bezeichnet.
Strapi hat sich entschieden, die Feldbezeichnung und -beschreibung (und einige andere Eigenschaften) nicht im Modell, sondern in der Datenbank zu speichern. Das bedeutet, dass die Übertragung zwischen Instanzen die Verwendung von config:dump
und config:restore
Befehlen erfordert. Wir sind der Meinung, dass eine so grundlegende Funktion nicht so viel Aufwand erfordern sollte, aber zumindest gibt es eine Möglichkeit, dies zu tun.
Die Gründe für diesen Ansatz sind in ihrem FAQ-Bereich aufgeführt. Wir finden das nicht sehr überzeugend, da andere CMS-Plattformen, die wir nutzen, dieses Problem nicht haben.
Abschließende Worte
Dieser Artikel soll Ihnen einen Überblick darüber geben, wie eine Migration zu einem Headless CMS aussehen kann. Wir sind auch auf spezifische Implementierungsprobleme eingegangen, die wir bei der Arbeit mit Strapi und 11ty erlebt haben. Wir hoffen, dass er Ihren Werkzeugkasten für Headless CMS erweitert und Sie in der Lage sind, einige dieser Konzepte in Ihrem Projekt umzusetzen.
Die Arbeit an diesem Projekt war sehr aufschlussreich, und trotz zahlreicher Herausforderungen auf dem Weg sind wir mit dem Ergebnis zufrieden: Eine “Wissensdatenbank” mit schnellen, statischen Seiten und einem CMS, das unabhängig von Shopware 6 ist. Schauen Sie sich das Ergebnis auf https://www.nfc-tag-shop.de/info/ an!