← Alle Leitfäden

Menu und LCP: wie Navigation dein Largest Contentful Paint blockiert

Render-blockierende Menu-Skripte: JavaScript verschieben ohne Navigation zu beschädigen

Async vs. Defer vs. type=module – Strategien zum Laden von Menu-JavaScript ohne LCP-Blockierung, mit Shopify-spezifischen Beispielen.

Du hast ein solides Menu. Kunden mögen es. Es funktioniert. Aber jedes Mal, wenn du deinen Shop mit PageSpeed Insights testest, markiert Google eine bestimmte Datei: dein Menu-Skript. Die Bezeichnung lautet „render-blocking JavaScript”, die Datei ist 120 KB groß und trägt 600 Millisekunden zu deinem mobilen LCP bei. Du weißt, dass du es beheben musst, aber das letzte Mal, als jemand vorschlug, defer zu verwenden, brach das Menu. Die Hälfte der Dropdowns funktionierte nicht mehr, der mobile Toggle reagierte nicht, und du hast es innerhalb einer Stunde rückgängig gemacht.

Das ist ein häufiges Problem. Render-blockierende Skripte sind einer der impactreichsten Performance-Engpässe in Shopify-Shops, und das Menu ist ein häufiger Übeltäter. Aber JavaScript zu verschieben bedeutet nicht einfach, ein Attribut zu einem Script-Tag hinzuzufügen. Wenn du es falsch machst, funktioniert das Menu nicht. Wenn du es richtig machst, kannst du etwa eine halbe Sekunde von deinem LCP abziehen, ohne eine einzige Zeile Menu-Logik anzufassen.

Dieser Artikel erklärt, was Render-Blocking wirklich bedeutet, warum async und defer sich unterschiedlich verhalten, und wie du dein Menu-Skript ohne Funktionsverluste laden kannst.

Kurze Zusammenfassung
  • Render-blockierende Skripte stoppen das gesamte Rendern der Seite, bis sie heruntergeladen, geparst und ausgeführt sind.
  • defer lädt Skripte parallel herunter, verzögert aber die Ausführung bis nach dem HTML-Parsing.
  • async lädt herunter und führt sofort aus, was Menüs beschädigen kann, die von DOM-Reihenfolge abhängen.
  • type="module" verzögert standardmäßig und unterstützt moderne JavaScript-Funktionen.
  • Die meisten Menu-Probleme nach dem Verschieben entstehen durch Initialisierungs-Timing, nicht durch das defer-Attribut selbst.

Was Render-Blocking wirklich bedeutet

Wenn ein Browser auf einen <script>-Tag im HTML ohne Lade-Attribute trifft, stoppt er alles. Er pausiert HTML-Parsing, lädt das Skript herunter, parst es, führt es aus, und erst dann baut er die Seite weiter. Das nennt sich synchrones oder render-blockierendes Verhalten.

Die Logik hinter diesem Verhalten ist alt, aber sinnvoll: JavaScript kann das DOM mit document.write() ändern, also geht der Browser davon aus, dass jedes Skript die Struktur der Seite ändern könnte und daher vor dem Fortfahren laufen muss.

Hier liegt das Problem. Dein Menu-Skript verwendet nicht document.write(). Es muss nicht laufen, bevor der Rest der Seite geparst wird. Aber der Browser weiß das nicht, also blockiert er trotzdem.

Laut Chromes eigener Dokumentation zur Optimierung des Largest Contentful Paint ist das Eliminieren von render-blockierendem JavaScript eine der wirkungsvollsten Verbesserungen, die du vornehmen kannst. Wenn ein Skript im Document Head 400 Millisekunden blockiert, müssen alle Elemente unter ihm im DOM – dein Hero-Image, deine Überschrift, dein Produktgrid – 400 Millisekunden länger warten, um zu rendern.

Die drei Lade-Strategien: Standard, Defer und Async

Es gibt drei Wege, wie ein Browser ein Skript laden kann.

Standard (Synchron)

<script src="menu.js"></script>

Der Browser stoppt das HTML-Parsing, lädt menu.js herunter, parst es, führt es aus, dann setzt er fort. Das blockiert das Rendern für die gesamte Dauer.

Defer

<script src="menu.js" defer></script>

Der Browser lädt menu.js parallel zum HTML-Parsing herunter, führt es aber nicht aus, bis das gesamte HTML-Dokument geparst wurde. Mehrere verschobene Skripte führen in der Reihenfolge aus, in der sie im HTML erscheinen.

Async

<script src="menu.js" async></script>

Der Browser lädt menu.js parallel zum HTML-Parsing herunter und führt es aus, sobald der Download abgeschlossen ist, auch wenn das HTML-Parsing noch nicht fertig ist. Die Ausführungsreihenfolge ist unvorhersehbar, wenn mehrere Skripte async verwenden.

Hier ist eine Tabelle, die das Verhalten zusammenfasst:

Attribut Download Ausführung Blockiert HTML-Parsing Ausführungsreihenfolge
Keine (Standard) Blockiert Parsing Sofort Ja Sequenziell
defer Parallel Nach HTML-Parse Nein Sequenziell
async Parallel Sobald fertig Ja (während Ausführung) Unvorhersehbar

Für die meisten Navigationsmenüs ist defer die richtige Wahl. Es hält Skripte in Reihenfolge, was wichtig ist, wenn ein Skript von einem anderen abhängt (zum Beispiel ein Menu-Skript, das auf einer Utility-Bibliothek beruht). Und es garantiert, dass das DOM bereit ist, bevor das Skript läuft, was entscheidend für jeden Code ist, der Elemente abfragt.

Warum Defer einige Menüs beschädigt (und wie man es behebt)

Du fügst defer zu deinem Menu-Script-Tag hinzu, ladest die Seite neu, und das Menu funktioniert nicht. Die Dropdowns öffnen sich nicht. Der mobile Toggle reagiert nicht. Du entfernst defer, und alles funktioniert wieder. Was ist passiert?

Das Problem ist fast immer Initialisierungs-Timing. Hier sind die drei häufigsten Ursachen.

Problem 1: Skript läuft, bevor Theme-JavaScript bereit ist

Viele Shopify-Themes haben ein globales JavaScript-Objekt oder eine Initialisierungsfunktion, auf die andere Skripte angewiesen sind. Wenn dein Menu-Skript läuft, bevor dieses Objekt erstellt wird, schlägt es fehl.

Beispiel:

// theme.js (geladen zuerst)
window.Theme = { utils: { ... } };

// menu.js (geladen zweite, erwartet Theme)
Theme.utils.initDropdown();

Wenn menu.js verschoben wird, aber theme.js nicht, könnte menu.js vor dem Ende von theme.js laufen, und Theme ist undefined.

Behebung: Verschiebe beide Skripte, oder stelle sicher, dass menu.js auf Abhängigkeiten prüft, bevor es initialisiert wird.

if (typeof Theme !== 'undefined') {
  Theme.utils.initDropdown();
}

Problem 2: Inline-Skripte laufen vor verschobenen Skripten

Inline-Skripte (JavaScript direkt im HTML geschrieben, nicht von einer externen Datei geladen) führen in Dokumentreihenfolge aus, während sie auftreten. Verschobene Skripte führen aus, nachdem das gesamte HTML geparst wurde, was nach allen Inline-Skripten ist.

Wenn du ein Inline-Skript hast, das das Menu initialisiert, und die Menu-Bibliothek ist verschoben, schlägt die Initialisierung fehl.

Behebung: Verschiebe die Initialisierungslogik in das verschobene Skript, oder wickle Inline-Initialisierung in einen DOMContentLoaded-Listener ein.

document.addEventListener('DOMContentLoaded', function() {
  if (typeof MenuApp !== 'undefined') {
    MenuApp.init();
  }
});

Problem 3: Race Conditions mit mehreren verschobenen Skripten

Wenn dein Menu von einer Utility-Bibliothek abhängt, und beide sind verschoben, sollten sie in der Reihenfolge ausführen, in der sie im HTML erscheinen. Aber wenn die Utility-Bibliothek asynchron geladen wird (mit async statt defer), wird die Ausführungsreihenfolge unvorhersehbar.

Behebung: Verwende defer für alle abhängigen Skripte, nicht async. Wenn du async verwenden musst, füge explizite Abhängigkeitsprüfungen hinzu oder verwende dynamische Importe.

Type=”module”: Die moderne Alternative

Modernes JavaScript unterstützt ES6-Module, die eingebautes verzögertes Laden haben.

<script type="module" src="menu.js"></script>

Skripte mit type="module" verzögern automatisch. Sie laden parallel herunter und führen nach dem HTML-Parsing aus, genau wie defer. Sie unterstützen auch import und export, was die Abhängigkeitsverwaltung explizit macht.

Wenn dein Menu-Skript als Modul geschrieben ist, kannst du Abhängigkeiten direkt importieren:

import { initDropdown } from './dropdown.js';
initDropdown();

Das eliminiert die Timing-Probleme, die Breakage mit traditionellem defer verursachen, weil der Browser genau weiß, welche Skripte von welchen abhängen.

Der Nachteil: ältere Browser (Internet Explorer, sehr alte Versionen von Safari) unterstützen keine Module. Aber laut Can I Use unterstützen über 95% des globalen Browser-Verkehrs ab 2025 ES6-Module. Für die meisten Shopify-Shops ist das sicher.

Shopify-spezifische Überlegungen

Shopify-Themes laden Skripte je nach Theme-Architektur auf unterschiedliche Weise. Hier ist, wie das Verschieben in den häufigsten Mustern funktioniert.

Theme.liquid Script-Tags

Wenn dein Menu-Skript in theme.liquid geladen wird wie hier:

{{ 'menu.js' | asset_url | script_tag }}

Der script_tag-Filter fügt standardmäßig nicht defer hinzu. Du musst ihn durch einen manuellen <script>-Tag ersetzen:

<script src="{{ 'menu.js' | asset_url }}" defer></script>

App Embed Blocks

Wenn dein Menu als App Embed installiert ist (üblich für Menu-Apps von Drittanbietern), kontrolliert die App, wie ihr Skript geladen wird. Du kannst nicht selbst defer hinzufügen. Du musst den App-Entwickler kontaktieren oder zu einer Menu-App wechseln, die Skripte bereits asynchron lädt.

Tools wie Navi+ laden ihr JavaScript standardmäßig mit defer, was ein Grund ist, warum sie in LCP-Tests besser abschneiden als ältere Menu-Apps.

Section Rendering und verschobene Skripte

Shopify 2.0 Themes laden Sections dynamisch. Wenn eine Section ein verschobenes Skript enthält, und die Section wird nach dem initialen Laden zur Seite hinzugefügt (zum Beispiel via AJAX für Quick View oder Infinite Scroll), könnte das Skript nicht wie erwartet ausgeführt werden.

Behebung: Verwende DOMContentLoaded nur für initialen Seitenladevorgang. Für dynamisch hinzugefügte Sections triggere die Initialisierung manuell mit einem Custom Event oder Mutation Observer.

Wie du testest, ob Defer funktioniert

Bevor du verzögertes Script-Loading in deinem Live-Shop einsetzt, teste es gründlich. Hier ist eine Checkliste.

  1. Desktop Chrome: Öffne deinen Shop, öffne DevTools, gehe zur Network-Registerkarte, lade neu, und prüfe, dass das Menu-Skript parallel mit anderen Ressourcen herunterlädt und das Dokument nicht blockiert.
  2. Mobile-Simulation: In DevTools, wechsle zur mobilen Emulation (iPhone oder Android), drossele das Netzwerk auf „Fast 3G”, und lade neu. Das Menu sollte sich immer noch korrekt initialisieren.
  3. Cross-Browser: Teste in Safari, Firefox und Edge. Modul-Skripte verhalten sich in Safari leicht unterschiedlich.
  4. Echtes Gerät: Öffne den Shop auf einem echten Telefon über eine echte mobile Verbindung. Emulation ist nützlich, aber reale Latenz enthüllt Timing-Bugs, die Desktop-Tests übersehen.
  5. Funktionalitäts-Check: Öffne jeden Dropdown, teste den mobilen Menu-Toggle, klicke jeden Link an, und prüfe, dass nichts beschädigt ist.

Bevor du pushtFühre einen PageSpeed Insights-Test auf derselben Seite vor und nach dem Aktivieren von Defer aus. Du solltest einen messbaren Drop in LCP und eine Reduktion in render-blockierenden Ressourcen sehen. Wenn LCP sich nicht verbessert, könnte das Skript nicht der Engpass gewesen sein, oder es könnten andere blockierende Ressourcen zu beheben sein.

Wenn Async Sinn macht (und wenn nicht)

async ist für Skripte geeignet, die völlig eigenständig sind und nicht von DOM-Reihenfolge oder anderen Skripten abhängen. Beispiele sind Analytics-Tracker, Chat-Widgets und einige Ad-Skripte.

Für Navigationsmenüs ist async riskant. Das Menu braucht das DOM bereit zu sein, es hängt oft von Theme-Utilities ab, und es muss normalerweise laufen, bevor der Kunde mit der Seite interagiert. Wenn das Skript ausgeführt wird, während HTML noch geparst wird, könnte es versuchen, Elemente abzufragen, die noch nicht existieren.

Wenn du async für ein Menu-Skript erwägst, muss das Skript:

  • nicht von anderen Skripten abhängen
  • keine DOM-Elemente abfragen (oder alle Abfragen in einer DOMContentLoaded-Prüfung wrappen)
  • klein genug sein, dass seine Ausführungszeit das Parsing nicht blockiert

Die meisten Menu-Skripte erfüllen diese Kriterien nicht. Bleib bei defer oder type="module", es sei denn, du hast einen spezifischen Grund für async.

Wie gutes Menu-Script-Loading aussieht

Ein ordnungsgemäß optimiertes Menu-Skript in einem Shopify-Shop sieht so aus:

<script src="{{ 'menu.js' | asset_url }}" defer></script>

Oder, wenn du Module verwendest:

<script type="module" src="{{ 'menu.js' | asset_url }}"></script>

Das Skript lädt parallel mit dem Rest der Seite herunter, führt aus, nachdem das DOM bereit ist, und blockiert nicht das Hero-Image oder ein anderes LCP-Element vom Rendern.

In der Chrome DevTools Performance-Timeline solltest du das Hero-Image rendern sehen, bevor das Menu-Skript ausgeführt wird. Wenn das Menu-Skript zuerst läuft, blockiert es immer noch.

Die meisten Shops können einfach durch den Wechsel von synchronem zu verzögertem Script-Loading 300 bis 600 Millisekunden vom mobilen LCP abziehen. Das Menu funktioniert immer noch. Der Kunde bemerkt keinen Unterschied. Aber Google tut es, und wichtiger noch, der Browser. Die Seite wird schneller sichtbar, was sowieso der ganze Sinn ist.

This article is part of the larger guide on Menu and LCP: how navigation blocks your largest contentful paint.

Teilen Facebook X

Beginnen Sie mit Navi+ AI Menu Builder

Wählen Sie Ihre Plattform — kostenlos zu installieren, in Minuten live.