Cookies 🍪

Diese Website verwendet Cookies, die eine Einwilligung erfordern. Mehr in der Datenschutzerklärung.

Zum Inhalt springen
Nordalux
Hey 👋
Wir sind für dich da
Einblicke Shopify Tipps

Weshalb wir fremden Code aufräumen, ohne dass jemand danach fragt

| Tobias Graeger
Dieses Bild wurde mit Hilfe
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.

Ausschnitt aus der Ausgangsfassung, Klassennamen anonymisiert. Vier Befunde stecken allein in diesen Zeilen.
1<div class="xx-story-strip">
2 <div class="xx-story-strip-holder">
3 {% for block in section.blocks %}
4 <a href="{{ block.settings.url }}">
5 <div {% if block.settings.highlight == true %}class="xx-story-highlight"{% endif %}>
6 <img
7 src="{{ block.settings.image | img_url: '200x' }}"
8 alt="{{ block.settings.heading }}"
9 loading="lazy"
10 >
11 </div>
12 {{ block.settings.heading }}
13 </a>
14 {% endfor %}
15 </div>
16</div>

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.

Nachher: Der Anker entsteht nur, wenn es ein Ziel gibt. Die Klasse bleibt in beiden Fällen dieselbe, das Styling muss nichts davon wissen.
1{% if ndl_slider_link != blank %}
2 <a class="ndl-story-slider__link" href="{{ ndl_slider_link }}">
3{% else %}
4 <div class="ndl-story-slider__link">
5{% endif %}
6 ...
7{% if ndl_slider_link != blank %}
8 </a>
9{% else %}
10 </div>
11{% endif %}

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.

Nachher: image_url erzeugt die Quelle, image_tag setzt srcset und sizes. Der Alt-Text hat eine Fallback-Kette, damit er nie leer bleibt.
1{{
2 block.settings.image
3 | image_url: width: 300
4 | image_tag:
5 alt: ndl_slider_heading,
6 loading: 'lazy',
7 widths: '100, 150, 200, 300',
8 sizes: '(max-width: 600px) 20vw, 92px'
9}}

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.

Eine vollständige Regel aus dem alten Stylesheet, nur der Klassenname ist anonymisiert. Zehn Deklarationen, zehn Ausrufezeichen.
1.xx-story-strip a {
2 max-width: {{ section.settings.desktop-size }}px !important;
3 width: 100% !important;
4 display: inline-block !important;
5 margin-right: {{ section.settings.desktop-distance }}px !important;
6 font-size: {{ section.settings.fontsize }}px !important;
7 line-height: 20px !important;
8 color: {{ section.settings.schriftfarbe }} !important;
9 font-weight: 600 !important;
10 font-family: {{ section.settings.schriftart.family }}, {{ schriftart.fallback_families }} !important;
11 text-decoration: none !important;
12}

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.

Nachher, gekürzt: Werte als CSS-Variablen am Element, Selektoren an die Section-ID gebunden. Aus 38 Ausrufezeichen werden null.
1<section
2 id="ndl-story-slider-{{ section.id }}"
3 class="ndl-story-slider"
4 style="
5 --ndl-story-slider-background: {{ section.settings.ndl_slider_background }};
6 --ndl-story-slider-desktop-size: {{ section.settings.ndl_slider_desktop_size }}px;
7 --ndl-story-slider-radius: {{ section.settings.ndl_slider_radius }}px;
8 "
9>
10
11{% style %}
12 #ndl-story-slider-{{ section.id }} .ndl-story-slider__item {
13 max-width: var(--ndl-story-slider-desktop-size);
14 }
15{% endstyle %}

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.

Zwei Regeln aus der neuen Fassung. Kein !important, jeder Selektor an die Section-ID gebunden, jeder einstellbare Wert über eine Variable.
1#ndl-story-slider-{{ section.id }} .ndl-story-slider__item {
2 display: inline-block;
3 width: 100%;
4 max-width: var(--ndl-story-slider-desktop-size);
5 margin-right: var(--ndl-story-slider-desktop-gap);
6 vertical-align: top;
7}
8
9#ndl-story-slider-{{ section.id }} .ndl-story-slider__image img {
10 display: block;
11 width: 100%;
12 height: auto;
13 border-radius: var(--ndl-story-slider-radius);
14}

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.

T

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.

LinkedIn

Hat dir der Beitrag gefallen?

Ein Klick reicht und wir wissen, welche Themen wir weiter vertiefen sollen.

Feedback konnte gerade nicht gespeichert werden.

Beitrag teilen