von generativer KI erstellt.
In fast jedem gewachsenen Shopify-Theme liegt Code, den niemand mehr anfasst. Er stammt aus einer Agentur davor, aus einem Freelancer-Auftrag, aus einer App, die längst deinstalliert ist. Er tut, was er soll, also lässt man ihn liegen. Genau dort sammelt sich das an, was später teuer wird.
Vor kurzem waren wir in einem Theme unterwegs, um etwas völlig anderes zu bauen. Beim Durchsehen fiel eine Section auf, die bei jedem Check Warnungen produzierte. Sichtbar kaputt war nichts. Der Slider lief, die Bilder wurden geladen, die Kunden merkten nichts. Wir haben ihn trotzdem neu geschrieben, in derselben Sitzung, ohne Ticket und ohne Position auf der Rechnung.
Dieser Beitrag zeigt beide Fassungen vollständig und benennt jeden einzelnen Befund. Nicht als Abrechnung mit dem, der das ursprünglich geschrieben hat, sondern weil die Fehlermuster in dieser einen Datei fast alle sind, die uns in Shopify-Themes regelmäßig begegnen.
Was die Section macht
Es ist ein Story-Slider, wie man ihn aus Instagram kennt: eine Reihe runder Bilder mit Beschriftung, horizontal scrollbar, nur auf schmalen Bildschirmen sichtbar, jedes Bild ein Link auf eine Kategorie oder ein Produkt. Ein einzelner Eintrag kann farbig umrandet hervorgehoben werden. Funktional ist das überschaubar, und genau deshalb ist es ein gutes Beispiel: Alles, was hier schiefgeht, geht nicht wegen der Komplexität schief.
Wie die alte Fassung aussah
Die alte Fassung stammt nicht von uns. Wir zeigen sie deshalb nur in kurzen Ausschnitten und haben alle Klassennamen durch neutrale Platzhalter ersetzt. Es geht um die Muster, nicht um die Datei und schon gar nicht um ihren Urheber.
Das Markup ist schnell erzählt: ein äußerer Container, ein scrollbarer Streifen darin und je Block ein Link mit Bild und Beschriftung.
Darunter folgte ein rohes style-Tag mit dem kompletten Stylesheet der Section, rund achtzig Zeilen lang. Wer das liest und nichts Schlimmes sieht, liegt nicht falsch. Es ist keine Katastrophe. Es ist der normale Zustand von Code, der einmal geschrieben und nie wieder gelesen wurde.
Befund eins: der Link, der immer da ist
Das Anker-Element wird bedingungslos ausgegeben. Hat ein Block kein Linkziel, entsteht ein leeres href. Für den Browser ist das kein fehlender Link, sondern ein Link auf die aktuelle Seite. Ein Klick lädt die Seite neu, der Scrollstand geht verloren, und wer mit der Tastatur navigiert, landet auf einem Element, das sich als Ziel anbietet und keines hat. Screenreader kündigen es als Link an. Crawler folgen ihm.
Das ist der Fehlertyp, den wir am unangenehmsten finden, weil er nie als Fehler auftritt. Nichts protokolliert ihn, niemand beschwert sich, und er verschwindet auch nicht von selbst. In der neuen Fassung entscheidet eine Bedingung, ob ein Anker oder ein neutrales div ausgegeben wird.
Ein leeres href ist kein deaktivierter Link, sondern ein Link auf die eigene Seite. Wo Redakteure Blöcke selbst anlegen dürfen, ist dieser Fall irgendwann da.
Befund zwei: der Theme-Editor sieht die Blöcke nicht
Damit der Shopify-Editor einen Block markieren, in der Vorschau hervorheben und beim Bearbeiten anspringen kann, muss das Markup ein bestimmtes Attribut tragen. Es heißt block.shopify_attributes und gehört auf das äußerste Element eines Blocks. In der alten Fassung fehlte es vollständig.
Die Folge merkt kein Entwickler, aber jeder, der den Shop pflegt: Man klickt links in der Blockliste auf einen Eintrag und die Vorschau springt nicht dorthin. Man ändert ein Bild und die Vorschau lädt komplett neu, statt nur den Block zu aktualisieren. Für Themes im Shopify Theme Store ist das Attribut nicht optional, sondern Abnahmekriterium. Nachgerüstet ist es eine Zeile.
Befund drei: ein Bild für alle Bildschirme
Die Bildausgabe lief über img_url, einen Filter, den Shopify vor Jahren durch image_url ersetzt hat. Er funktioniert weiterhin, wird aber im Theme-Check als veraltet gemeldet, und genau solche Meldungen waren es, die uns überhaupt auf die Datei aufmerksam gemacht haben.
Wichtiger als die Umbenennung ist, was daran hing. Der Filter lieferte genau eine Variante in 200 Pixel Breite, fest verdrahtet im Markup. Auf einem alten Android-Gerät mit 320 Pixel Displaybreite ist das zu viel, auf einem modernen Telefon mit dreifacher Pixeldichte deutlich zu wenig. Es gab kein srcset und kein sizes, also auch keine Möglichkeit für den Browser, etwas Passendes zu wählen. Die neue Fassung überlässt Shopify die Varianten.
Am selben Bild fehlte noch etwas, das leicht zu übersehen ist: width und height. Ohne diese beiden Angaben kennt der Browser das Seitenverhältnis nicht, bevor das Bild geladen ist, reserviert keinen Platz und schiebt beim Nachladen den Inhalt darunter weg. Das ist Layout Shift, und in einem Streifen aus mehreren Bildern ist er gut sichtbar. image_tag setzt beides von selbst, der Fehler war mit der Umstellung also nebenbei erledigt.
Nebenbei erledigt image_tag die Attribute, die man von Hand gern vergisst, und der Alt-Text zieht jetzt nacheinander die Beschriftung, den im Shopify-Backend hinterlegten Alt-Text des Bildes und erst zuletzt einen neutralen Platzhalter. Vorher stand dort die Beschriftung oder eben nichts.
Befund vier: 38 mal !important, und warum sie dort stehen mussten
Im alten Stylesheet steht 38 mal !important. Das ist die Sorte Zahl, bei der man reflexhaft den Kopf schüttelt, und der Reflex führt in die Irre. Die Ausrufezeichen sind nicht die Krankheit, sie sind das Symptom.
Alle Regeln hängen an globalen Klassennamen. Eine Regel für jeden Link innerhalb der Slider-Klasse trifft im ganzen Dokument, und zwar mit einer Spezifität, die jede halbwegs normale Theme-Regel unterbietet. Sobald das Theme selbst irgendwo Links stylt, gewinnt das Theme. Der einzige Ausweg ohne Umbau ist Gewalt, und Gewalt heißt in CSS !important.
Der zweite Effekt derselben Ursache ist ernster. Zwei Instanzen der Section auf einer Seite, etwa ein Slider oben mit Kategorien und einer weiter unten mit Aktionen, schreiben beide dasselbe Stylesheet mit denselben Selektoren und unterschiedlichen Werten. Die zweite überschreibt die erste. Beide sehen dann aus wie die zweite. Das ist kein theoretischer Fall, sondern der Normalfall, sobald jemand eine Section ein zweites Mal einsetzt.
Die neue Fassung gibt der Section eine eigene ID aus section.id, stellt jeden Selektor darauf ab und legt die konfigurierbaren Werte als CSS-Variablen auf das Wurzelelement. Danach ist kein einziges !important mehr nötig, und zwei Instanzen stören einander nicht mehr.
Der Wechsel vom rohen style-Tag im Body zum style-Tag von Liquid gehört zum selben Handgriff. Shopify sammelt den Inhalt dieses Tags ein und behandelt ihn als Theme-CSS, statt ihn als beliebiges Markup mitten im Dokument stehen zu lassen.
Befund fünf: das Schema
Der auffälligste Punkt steckt im Schriftfeld. Als Vorgabewert stand dort sans-serif. Ein font_picker erwartet aber keinen CSS-Gattungsnamen, sondern einen Handle aus der Shopify-Schriftbibliothek, etwa assistant_n4. Was dort stand, gibt es in dieser Bibliothek nicht, und ein Vorgabewert, den es nicht gibt, ist genau die Art Eintrag, die beim Prüfen des Themes Meldungen erzeugt.
Der zweite Punkt betrifft die Übersetzungen. Beschriftungen und Namen lagen als eingebettete Objekte mit den Schlüsseln en und de direkt im Schema. Das ist keine Stilfrage: Shopifys Theme Check meldet jedes dieser Objekte als Fehler. Bei 21 Objekten im Schema kommen so 22 Fehlermeldungen zusammen, und sie verschwinden alle, sobald man die Objekte durch Strings ersetzt. Vorgesehen ist ein Übersetzungsschlüssel im Schema und der eigentliche Text in den Sprachdateien des Themes. Da die Section in diesem Shop ohnehin nur auf Deutsch gepflegt wird, haben wir die Objekte durch schlichte deutsche Strings ersetzt statt durch Schlüssel, die auf leere Übersetzungsdateien zeigen.
Der dritte Punkt ist eine Kleinigkeit mit Folgen. Die maximale Anzeigebreite war ein Freitextfeld mit dem Vorgabewert 2400. Dieser Wert landet ungeprüft in einer Media Query. Wer dort 2400px oder large oder ein Komma einträgt, erzeugt eine ungültige Media Query, und eine ungültige Media Query wird stillschweigend verworfen. Die Section verschwindet dann auf allen Geräten, ohne dass irgendwo eine Fehlermeldung steht. Als Range-Feld mit fester Einheit ist der Fall ausgeschlossen.
| Befund | Warum es zählt | Was wir gemacht haben |
|---|---|---|
| img_url als Bildfilter | Seit Jahren abgelöst, wird im Theme-Check gemeldet | Auf image_url und image_tag umgestellt |
| Eine feste Bildbreite von 200 Pixeln | Kein srcset, also unscharf auf guten und zu schwer auf schwachen Geräten | Vier Breiten plus sizes-Angabe |
| block.shopify_attributes fehlt | Theme-Editor kann Blöcke nicht auswählen und nicht live aktualisieren | Attribut auf das Blockelement gesetzt |
| Anker ohne Prüfung auf ein Ziel | Leeres href verhält sich wie ein Link auf die aktuelle Seite | Anker nur bei vorhandenem Ziel, sonst ein div |
| Globale Klassenselektoren | Zwei Instanzen auf einer Seite überschreiben sich gegenseitig | Alles auf die Section-ID gescoped |
| 38 mal !important | Macht spätere Anpassungen im Theme praktisch unmöglich | Ersatzlos entfallen, weil die Ursache weg ist |
| Rohes style-Tag im Body | Wird nicht als Theme-CSS behandelt | In den style-Tag von Liquid verschoben |
| font_picker mit Vorgabewert sans-serif | Kein gültiger Handle aus der Schriftbibliothek | Auf assistant_n4 gesetzt |
| width und height am img-Tag fehlen | Browser reserviert keinen Platz, Layout Shift beim Nachladen | image_tag setzt beides selbst |
| Übersetzungen als eingebettete Objekte | Harter Fehler im Theme Check, 22 Meldungen bei 21 Objekten | Durch deutsche Strings ersetzt |
| Pixelangabe als Freitextfeld | Eine ungültige Eingabe kippt die Media Query lautlos | Range-Feld mit Einheit px |
| Linkziel als reines Textfeld | Keine Auswahlhilfe, keine Prüfung | Auf den url-Feldtyp umgestellt |
| Keine Listensemantik | Für Screenreader eine Ansammlung von Links ohne Struktur | role list und role listitem ergänzt |
Was der Umbau gekostet hat
Der Umbau lief ohne Funktionsverlust. Das stimmt für die Funktionen und nicht für die Konfiguration, und der Unterschied gehört benannt.
Alle elf Einstellungen auf Section-Ebene haben neue Bezeichner bekommen, durchgehend mit einem eigenen Präfix versehen. Aus background-color wurde ndl_slider_background, aus abgerundete_ecken wurde ndl_slider_radius, und so weiter durch die ganze Liste. Shopify speichert die vom Händler gesetzten Werte unter genau diesen Bezeichnern. Ändert man den Bezeichner, findet die Section ihren alten Wert nicht mehr und fällt auf den Vorgabewert zurück. Hintergrundfarbe, Schriftgröße, gewählte Schriftart: einmal neu setzen.
Auf Blockebene sieht es besser aus. Die Bezeichner image, heading, url und highlight sind unverändert geblieben, ebenso der Blocktyp und der Dateiname der Section. Die angelegten Einträge mit ihren Bildern, Beschriftungen und Zielen bleiben also erhalten. Eine Unsicherheit bleibt beim Linkziel, weil dort der Feldtyp von text auf url gewechselt ist. Der Bezeichner ist derselbe, der Typ nicht. Das prüfen wir vor dem produktiven Deployment auf dem Staging-Theme nach.
Bezeichner umzubenennen ist die einzige Änderung in diesem Umbau, die der Händler merkt. Wer das vermeiden will, lässt die alten Bezeichner stehen und räumt nur den Rest auf. Wir haben uns bewusst anders entschieden, weil ein eigener Namensraum die nächste Kollision verhindert.
Wie das Stylesheet danach aussieht
Die vollständige neue Fassung drucken wir hier nicht ab. Sie ist ungefähr so lang wie die alte, und das Interessante daran sind ohnehin nur zwei Zeilen Bauprinzip. Erstens: Jeder Selektor beginnt mit der ID der Section. Zweitens: Jeder Wert, den der Händler einstellen kann, steht als CSS-Variable am Element und nicht im Selektor.
Der Unterschied zur alten Fassung steckt nicht in der Menge, sondern in der Richtung. Vorher hat jede Regel versucht, sich gegen das Theme durchzusetzen. Jetzt kann sie das gar nicht mehr, weil sie nur innerhalb ihrer eigenen Section gilt. Die Ausrufezeichen sind nicht entfernt worden, sie sind überflüssig geworden.
Der Umbau ist damit noch nicht ganz fertig. Der Satz an CSS-Variablen wird gerade sortiert, damit die Werte in einer nachvollziehbaren Reihenfolge stehen und nicht in der, in der sie beim Schreiben angefallen sind. Das ändert am Verhalten nichts und an der Lesbarkeit einiges.
Weshalb wir das ohne Auftrag machen
Die ehrliche Antwort hat zwei Teile, und der erste ist eigennützig. Wir arbeiten in diesen Themes weiter. Jede Datei, die Warnungen produziert, kostet bei der nächsten Aufgabe Aufmerksamkeit, weil man erst prüfen muss, ob die Meldung mit der eigenen Änderung zu tun hat. Ein sauberer Ausgangszustand ist billiger als ein Ticket über eine halbe Stunde.
Der zweite Teil ist eine Haltung zur Zusammenarbeit. Für einen Shopbetreiber ist eine Section, die läuft, in Ordnung. Er kann nicht wissen, dass die Bilder auf schmalen Geräten zu klein ausgeliefert werden, dass der Theme-Editor deshalb hakt oder dass ein zweiter Einsatz derselben Section die Farben des ersten überschreiben wird. Das zu sehen ist unser Teil der Arbeit. Es dann liegen zu lassen, bis jemand danach fragt, wäre eine merkwürdige Auffassung von Betreuung.
Code, der funktioniert, ist nicht dasselbe wie Code, der in Ordnung ist. Der Unterschied fällt erst dem auf, der ihn als Nächstes anfassen muss.
Damit daraus keine Willkür wird, gibt es eine klare Grenze. Kleinigkeiten erledigen wir still mit: ein veralteter Filter, ein fehlendes Attribut, eine Regel, die man im Vorbeigehen richtigstellt. Alles, was darüber hinausgeht, wird vorher besprochen. Niemand will nach einem Update feststellen, dass in seinem Theme Dinge anders sind, über die er nicht gesprochen hat, und wir wollen keine Änderungen verantworten, deren Nebenwirkungen niemand kennt.
Der Fall in diesem Beitrag liegt bewusst auf der stillen Seite, und dafür gab es zwei Gründe. Der Code lag uns aus einem anderen Projekt ohnehin vor, wir mussten ihn also nicht erst beschaffen oder verstehen. Und das für den Auftrag vereinbarte Zeitbudget war noch nicht ausgeschöpft. Beides zusammen hat den Umbau zu einer Sache gemacht, die niemanden etwas kostet. Wäre eines von beiden anders gewesen, hätten wir vorher gefragt.
Wir machen das also nicht flächendeckend und nicht als eigenes Projekt. Ein Theme vollständig zu revidieren ist eine Aufgabe mit Aufwand, Abstimmung und Rechnung. Was still mitgeht, sind die Dateien, die uns gerade unter den Händen liegen. Das ist Teil unserer laufenden Betreuung und steht nirgends als Position auf der Rechnung.
Was sich daraus als Prüfliste ableiten lässt
Wer selbst durch ein gewachsenes Theme geht, findet die meisten dieser Muster mit einer Volltextsuche. Nach img_url suchen. Nach !important suchen und die Treffer nicht zählen, sondern fragen, warum sie dort stehen. In jeder Section mit Blöcken prüfen, ob shopify_attributes ausgegeben wird. Jeden Anker suchen, dessen Ziel direkt aus einer Einstellung kommt, und schauen, ob der leere Fall abgefangen ist. Und im Schema die Feldtypen mit dem vergleichen, was der Wert später tatsächlich tun muss.
Das ersetzt keinen richtigen Theme-Check, aber es findet in einer halben Stunde die Dinge, die sonst jahrelang stehen bleiben. Wenn dabei mehr zusammenkommt, als sich nebenbei erledigen lässt, ist das der Punkt, an dem daraus ein eigenes Entwicklungsprojekt wird. Bis dahin gilt die einfachere Regel: Was man aufmacht, macht man aufgeräumt wieder zu.
Tobias Graeger
Inhaber & Shopify-Entwickler
Tobias leitet alle Projekte persönlich. Mit über 50 abgeschlossenen Shopify-Projekten kennt er die Plattform vom Liquid-Template bis zur API-Integration. Sein Fokus: technisch saubere Lösungen, die mit dem Business mitwachsen.
LinkedInHat dir der Beitrag gefallen?
Ein Klick reicht und wir wissen, welche Themen wir weiter vertiefen sollen.
Feedback konnte gerade nicht gespeichert werden.
Das könnte euch auch interessieren
Post-Purchase-Upsell bei Shopify: Wo der Hebel greift
Zwischen dem Klick auf „Kauf abschließen" und der Dankesseite liegt die einzige Stelle im Shop, an der ein Zusatzangebot den Kauf nicht mehr gefährden kann. Warum dieser Platz trotzdem nur für einen Teil der Bestellungen offensteht und wie man Funnels dafür sinnvoll baut.
Weshalb der Hardware-Schlüssel bei OpenAI Pflicht wird
Ab dem 1. September 2026 verlangt OpenAI für alle individuellen Daybreak-Konten einen Hardware-Sicherheitsschlüssel. Was sich dadurch am Login ändert, warum der YubiKey nur ein Weg von mehreren ist und worauf Teilnehmer sich vorbereiten sollten.