Warum Interaktionen in Magento-Shops oft zu langsam sind
INP setzt sich aus drei messbaren Phasen zusammen, die in den meisten Performance Guides nur oberflächlich behandelt werden. Dieser Deep Dive zerlegt Input Delay, Processing Time und Presentation Delay einzeln, erklärt die Rolle von Drittanbieter Skripten und Alpine.js Reaktivität in Hyvä Shops und zeigt anhand des Chrome DevTools Performance Panels, wie sich lange Tasks konkret aufspüren und beheben lassen.
Inhaltsverzeichnis
- 1. INP im Überblick: Warum sich ein eigener Deep Dive lohnt
- 2. Phase 1: Input Delay verstehen und messen
- 3. Phase 2: Processing Time und ihre Ursachen
- 4. Phase 3: Presentation Delay und die Rendering-Pipeline
- 5. Drittanbieter-Skripte und Main-Thread-Konkurrenz
- 6. Alpine.js-Reaktivität in Hyvä-Shops im Detail
- 7. Long-Task-Profiling mit dem Chrome DevTools Performance-Panel
- 8. Praktische Fixes: Debouncing, Virtualisierung, Yielding
- 9. INP-Antipatterns im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. INP im Überblick: Warum sich ein eigener Deep Dive lohnt
Die Analyse zu Core Web Vitals hat INP als eine von drei Metriken eingeführt: unter 200 Millisekunden ist gut, verursacht durch lange JavaScript-Tasks. Für die tägliche Optimierungsarbeit reicht das nicht, denn "lange Tasks vermeiden" ist ein Ziel, kein Debugging-Schritt. INP-Optimierung scheitert in der Praxis fast immer daran, dass Teams die falsche Phase angehen, weil nie gemessen wurde, wo die Zeit tatsächlich verloren geht. Dieser Deep Dive liefert die fehlende Messmethodik.
INP zerlegt sich in drei messbare Phasen: Input Delay, Processing Time und Presentation Delay. Google berechnet den finalen Wert nicht als Durchschnitt, sondern als annähernd schlechteste Interaktion einer Sitzung, meist das 98. Perzentil aller über interactionId gruppierten Events. Ein einzelner hakeliger Klick auf "In den Warenkorb" kann so den gesamten Seiten-INP dominieren, selbst wenn die meisten anderen Klicks flüssig laufen.
2. Phase 1: Input Delay verstehen und messen
Der Input Delay beginnt, sobald das Betriebssystem einen Klick, Tap oder Tastendruck an den Browser meldet, und endet, wenn der zuständige Event-Handler zu laufen beginnt. Dazwischen liegt reine Wartezeit: Der Main Thread muss frei sein, bevor die Eingabe überhaupt verarbeitet werden kann. Läuft in diesem Moment bereits ein anderer Task, etwa ein Drittanbieter-Skript oder eine Style-Neuberechnung, wartet die Eingabe in der Task-Queue, bis dieser Task fertig ist.
Ein guter Input Delay liegt bei wenigen Millisekunden. Sobald ein einzelner Task länger als 50 Millisekunden läuft, gilt er als Long Task und blockiert jede währenddessen eintreffende Eingabe um den Rest seiner Laufzeit. Typische Verursacher sind synchron ladende Tag-Manager-Container, große JSON.parse-Aufrufe für Produktdaten oder ein initialer Alpine-init()-Durchlauf über eine sehr große Komponente. Der Input Delay ist damit selten ein Problem des Handlers selbst, sondern ein Symptom für Main-Thread-Überlastung durch andere Skripte.
// Measure INP phases (input delay, processing time, presentation delay) via the Event Timing API
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.interactionId) {
const inputDelay = entry.processingStart - entry.startTime;
const processingTime = entry.processingEnd - entry.processingStart;
const presentationDelay = entry.startTime + entry.duration - entry.processingEnd;
console.table({
interactionType: entry.name,
inputDelay: Math.round(inputDelay),
processingTime: Math.round(processingTime),
presentationDelay: Math.round(presentationDelay),
totalDuration: Math.round(entry.duration)
});
// Flag interactions that exceed the 200ms "good" INP threshold
if (entry.duration > 200) {
console.warn(`Slow interaction detected: ${entry.name} took ${Math.round(entry.duration)}ms`);
}
}
}
});
// durationThreshold filters out sub-16ms interactions that are not actionable
observer.observe({ type: 'event', buffered: true, durationThreshold: 16 });
3. Phase 2: Processing Time und ihre Ursachen
Processing Time ist die Spanne vom Start des Event-Handlers bis zu dessen vollständigem Abschluss, inklusive aller synchron verketteten Folgearbeit: verschachtelter Funktionsaufrufe, synchron aufgelöster Promises und aller requestAnimationFrame-Callbacks, die noch vor dem nächsten Paint laufen. In den meisten Magento- und Hyvä-Shops ist diese Phase der größte Anteil an einem schlechten INP-Wert, weil hier die eigentliche Anwendungslogik sitzt: Warenkorb-Updates, Preisberechnungen, Filterlogik und DOM-Manipulation.
Teuer wird Processing Time vor allem durch drei Muster: unnötige Neuberechnungen ganzer Datensätze bei jedem Tastendruck statt nur bei Bedarf, synchrones Lesen und Schreiben von Layout-Eigenschaften im Wechsel und tief verschachtelte reaktive Effekte, die bei einer einzigen Zustandsänderung kaskadenartig neu ausgewertet werden. Das Chrome DevTools Performance-Panel zeigt Processing Time als durchgehenden gelben JavaScript-Block direkt nach dem Event im Flame-Chart.
<!-- Alpine.js component with a debounced watcher to avoid re-filtering on every keystroke -->
<div x-data="{
query: '',
results: [],
init() {
// Only re-run the expensive filter after typing pauses for 250ms
this.$watch('query', Alpine.debounce(function (value) {
this.results = this.filterCatalog(value);
}, 250));
},
filterCatalog(value) {
// Runs against a pre-indexed array, not the full DOM
return window.productIndex.filter(p => p.name.toLowerCase().includes(value.toLowerCase()));
}
}">
<input type="text" x-model="query" placeholder="Search products..." class="border rounded-lg px-3 py-2 w-full">
<ul>
<template x-for="item in results" :key="item.sku">
<li x-text="item.name"></li>
</template>
</ul>
</div>
4. Phase 3: Presentation Delay und die Rendering-Pipeline
Presentation Delay beginnt, sobald der Event-Handler fertig ist, und endet, wenn der Browser den aktualisierten Frame tatsächlich auf dem Bildschirm darstellt. Dazwischen liegen Style-Neuberechnung, Layout, Paint und Compositing, sowie jede weitere Rendering-Arbeit, die durch die Interaktion ausgelöst wurde, etwa ein Bild-Decode oder eine CSS-Transition. Diese Phase wird beim Debugging am häufigsten übersehen, weil Entwickler primär den JavaScript-Handler profilen und die Rendering-Pipeline danach ignorieren.
Große DOM-Bäume, komplexe CSS-Selektoren, teure Eigenschaften wie box-shadow oder filter auf vielen Elementen sowie nicht dekodierte Bilder verlängern die Presentation Delay spürbar. Im DevTools Performance-Panel erscheint sie als lilafarbener "Rendering"- und grüner "Paint"-Abschnitt direkt nach dem gelben Processing-Block. Bei Interaktionen, die viele Elemente gleichzeitig sichtbar oder unsichtbar schalten, etwa ein Mega-Menü oder ein Filter-Overlay, dominiert häufig diese dritte Phase statt der eigentlichen JavaScript-Ausführung.
5. Drittanbieter-Skripte und Main-Thread-Konkurrenz
Jedes Drittanbieter-Skript, Tag-Manager-Container, Live-Chat-Widget, A/B-Testing-Tool oder Tracking-Pixel registriert eigene Event-Listener, oft direkt am document in der Bubble- oder Capture-Phase. Ein einzelner Klick löst dadurch nicht nur den eigenen Alpine-Handler aus, sondern eine Kette fremder Handler, die jeweils eigene Processing Time beanspruchen. Bei drei bis vier gängigen Drittanbieter-Tools pro Seite ist es keine Seltenheit, dass die Summe ihrer Main-Thread-Zeit die eigentliche Shop-Logik deutlich übersteigt.
Besonders kritisch sind Tag-Manager, die beim Laden synchron weitere Container nachladen und dabei mehrere hundert Millisekunden Main-Thread-Zeit blockieren, oft bevor der Nutzer überhaupt interagiert hat, aber mit direkter Auswirkung auf den Input Delay der ersten echten Interaktion. Die wirksamste Gegenmaßnahme ist, Drittanbieter-Skripte nicht beim Seitenaufbau, sondern erst nach der ersten Nutzerinteraktion oder nach einem Idle-Timeout zu laden, kombiniert mit Server-Side-Tagging, wo möglich, um Main-Thread-Arbeit komplett auf den Server zu verlagern.
// Load third-party scripts only after the first genuine user interaction
// instead of blocking the main thread during initial page load
const thirdPartyScripts = [
{ src: '/js/tag-manager.js', name: 'Tag Manager' },
{ src: '/js/chat-widget.js', name: 'Chat Widget' },
{ src: '/js/ab-testing.js', name: 'A/B Testing' }
];
let loaded = false;
function loadThirdPartyScripts() {
if (loaded) return;
loaded = true;
thirdPartyScripts.forEach(({ src, name }) => {
const script = document.createElement('script');
script.src = src;
script.defer = true;
script.dataset.vendor = name;
document.body.appendChild(script);
});
}
// Trigger on first interaction, or after 5s idle as a fallback so bots still see the scripts
['pointerdown', 'keydown', 'touchstart'].forEach((eventType) => {
window.addEventListener(eventType, loadThirdPartyScripts, { once: true, passive: true });
});
setTimeout(loadThirdPartyScripts, 5000);
6. Alpine.js-Reaktivität in Hyvä-Shops im Detail
Alpine.js basiert auf demselben feingranularen Reaktivitätssystem wie Vue 3 (@vue/reactivity) und nutzt JavaScript-Proxies, um nur die Effekte neu auszuführen, die tatsächlich von einer geänderten Eigenschaft abhängen. Anders als bei React oder Vue mit Virtual-DOM-Diffing entfällt der Vergleich ganzer Komponentenbäume, was Alpine strukturell schneller macht als klassische Framework-Ansätze. Dieser Vorteil kippt jedoch bei großen x-for-Schleifen: Jedes gerenderte Element erhält eigene reaktive Effekte, und bei mehreren hundert Facetten- oder Produktzeilen wächst die Anzahl der Effekte proportional mit.
Ändert sich in so einem Fall eine gemeinsam genutzte Zustandsvariable, etwa ein Suchbegriff, werten alle abhängigen Effekte synchron neu aus, direkt innerhalb des auslösenden Event-Handlers, was Processing Time explodieren lässt. Gleiches gilt für teure berechnete Ausdrücke direkt in x-data-Objekten oder in x-text-Bindings, die bei jedem Effekt-Durchlauf neu berechnet statt gecacht werden. $watch-Callbacks ohne Debounce auf häufig ändernde Werte wie x-model-gebundene Sucheingaben verschärfen das Problem zusätzlich.
<!-- Windowed rendering: only mount Alpine effects for rows inside the visible viewport -->
<div
x-data="{
allRows: window.categoryProducts,
rowHeight: 96,
visibleStart: 0,
visibleCount: 12,
get visibleRows() {
return this.allRows.slice(this.visibleStart, this.visibleStart + this.visibleCount);
},
onScroll(event) {
const scrollTop = event.target.scrollTop;
this.visibleStart = Math.max(0, Math.floor(scrollTop / this.rowHeight) - 2);
}
}"
x-on:scroll.passive="onScroll"
class="overflow-y-auto h-[600px] relative"
>
<!-- Spacer keeps scrollbar height correct without rendering every row -->
<div :style="`height: ${allRows.length * rowHeight}px; position: relative;`">
<template x-for="row in visibleRows" :key="row.sku">
<div
:style="`position: absolute; top: ${allRows.indexOf(row) * rowHeight}px; height: ${rowHeight}px;`"
class="w-full border-b border-slate-100 flex items-center px-4"
x-text="row.name"
></div>
</template>
</div>
</div>
7. Long-Task-Profiling mit dem Chrome DevTools Performance-Panel
Im Chrome DevTools Performance-Panel startet die Analyse mit aktivierter Option "Screenshots", einem Klick auf "Record", der Ausführung der zu untersuchenden Interaktion, etwa ein Klick auf "In den Warenkorb", und dem anschließenden Stopp der Aufnahme. Seit Chrome 118 zeigt die Spur "Interactions" die INP-Aufschlüsselung direkt farbcodiert an: Input Delay, Processing Time und Presentation Delay als getrennte Segmente auf einen Blick, ganz ohne manuelle Berechnung.
Im Main-Thread-Flame-Chart darunter sind Long Tasks über 50 Millisekunden mit einem roten Dreieck in der oberen rechten Ecke markiert. Ein Klick auf einen solchen Block öffnet im unteren Bereich "Bottom-Up" und "Call Tree", wo sich die Funktion mit der höchsten "Self Time" identifizieren lässt, also der eigentliche Verursacher statt nur des aufrufenden Wrappers. Eigene performance.mark()- und performance.measure()-Aufrufe im Code erscheinen zusätzlich in der Spur "Timings" und erlauben es, einzelne Funktionsblöcke gezielt zu benennen und wiederzufinden.
8. Praktische Fixes: Debouncing, Virtualisierung, Yielding
Gegen zu häufige Neuberechnungen hilft Debouncing, in Alpine direkt über den Modifier x-on:input.debounce.300ms oder programmatisch via Alpine.debounce() in einem $watch. Lange Listen, etwa Facetten mit hunderten Werten oder große Produktraster, sollten virtualisiert werden: Nur die im Viewport sichtbaren Zeilen werden gemountet, der Rest existiert nur als reservierter Platzhalter mit korrekter Höhe.
Für unvermeidbar lange Berechnungen ist Yielding der wichtigste Hebel: Arbeit in Chunks aufteilen und zwischen den Chunks über scheduler.yield(), ersatzweise setTimeout(fn, 0), die Kontrolle an den Main Thread zurückgeben, damit wartende Eingaben verarbeitet werden können. Schwere Alpine-Komponenten, etwa ein Produktkonfigurator, lassen sich per dynamischem import() erst beim tatsächlichen Öffnen laden statt beim initialen Seitenaufbau. Layout Thrashing verhindert man, indem alle getBoundingClientRect()-Aufrufe einer Schleife zuerst gesammelt und erst danach alle DOM-Schreibvorgänge gebündelt ausgeführt werden.
// Yield to the main thread between chunks of work to keep INP low
async function processFacetValues(items) {
const chunkSize = 40;
let index = 0;
while (index < items.length) {
const end = Math.min(index + chunkSize, items.length);
for (; index < end; index++) {
applyFacetValue(items[index]);
}
// Prefer the native scheduler API where available (Chrome 129+)
if ('scheduler' in window && 'yield' in window.scheduler) {
await window.scheduler.yield();
} else if ('isInputPending' in navigator) {
// Only yield if a pending user input is waiting to be processed
if (navigator.isInputPending({ includeContinuous: true })) {
await new Promise((resolve) => setTimeout(resolve, 0));
}
} else {
// Fallback for older browsers: always yield between chunks
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
}
9. INP-Antipatterns im direkten Vergleich
Jedes der folgenden fünf Muster wirkt auf eine andere INP-Phase und braucht einen jeweils eigenen Fix. Die Tabelle ordnet typische Antipatterns aus Magento- und Hyvä-Shops den betroffenen Phasen zu.
| Muster | Betroffene INP-Phase | Schlechter Ansatz | Fix |
|---|---|---|---|
| Synchroner Tag-Manager im Head | Input Delay | Blockiert den Main Thread schon vor der ersten Interaktion | Async/defer laden, Init auf erste Interaktion verschieben |
| Große x-for-Schleife ohne Virtualisierung | Processing Time | Rendert tausende DOM-Knoten mit eigenen Alpine-Effekten | Fenster-Rendering, nur sichtbare Zeilen mounten |
| Teurer berechneter Ausdruck in x-data | Processing Time | Filtert/sortiert bei jedem Tastendruck den ganzen Katalog | Debounce plus Memoisierung des berechneten Werts |
| Layout Thrashing durch getBoundingClientRect in Schleife | Presentation Delay | Verschachteltes Lesen/Schreiben erzwingt Reflow pro Iteration | Erst alle Reads, dann alle Writes bündeln |
| Ungedrosselter Scroll-/Input-Handler | Input Delay / Processing Time | Feuert dutzende Male pro Sekunde, staut die Task-Queue | Throttle/Debounce mit requestAnimationFrame |
In der Praxis überlagern sich mehrere dieser Muster gleichzeitig: Ein synchroner Tag-Manager verlängert den Input Delay der ersten Interaktion, während eine nicht virtualisierte Produktliste im selben Klick die Processing Time in die Höhe treibt. Wer beide Ursachen anhand der DevTools-Aufschlüsselung einzeln misst, statt pauschal "JavaScript zu optimieren", behebt INP-Probleme dauerhaft statt nur oberflächlich.
Mironsoft
INP-Profiling, Alpine.js-Performance und Main-Thread-Optimierung für Magento-Shops
INP-Probleme systematisch beheben lassen?
Wir profilen die INP-Phasen eures Magento-Shops im Chrome DevTools Performance-Panel, identifizieren Main-Thread-Blocker und optimieren Alpine.js-Komponenten sowie Drittanbieter-Skripte gezielt für messbar bessere Interaktionszeiten.
INP-Profiling-Audit
Phasenaufschlüsselung, Long-Task-Analyse und Priorisierung nach Impact
Alpine.js-Performance-Refactoring
Virtualisierung, Debouncing und Code-Splitting schwerer Komponenten
Third-Party-Skript-Governance
Ladezeitpunkt, Server-Side-Tagging und Main-Thread-Budget je Skript
10. Zusammenfassung
INP zerfällt in drei einzeln messbare Phasen, Input Delay, Processing Time und Presentation Delay, und jede hat eigene Ursachen und eigene Fixes. Input Delay entsteht durch Main-Thread-Überlastung, meist durch Drittanbieter-Skripte. Processing Time wird von ineffizienter Anwendungslogik und unkontrollierter Alpine-Reaktivität bei großen Listen dominiert. Presentation Delay wird durch teure Rendering-Arbeit und Layout Thrashing verlängert, oft unabhängig vom eigentlichen JavaScript-Code.
Das Chrome DevTools Performance-Panel mit der Interactions-Spur macht diese Aufschlüsselung sichtbar, statt sie zu erraten. Debouncing, Virtualisierung, Yielding über scheduler.yield() und Code-Splitting schwerer Alpine-Komponenten sind die vier wirksamsten Hebel, um INP in Magento- und Hyvä-Shops dauerhaft unter 200 Millisekunden zu halten, auch bei komplexen Interaktionen wie Filterung oder Produktkonfiguration.
INP im Detail - Das Wichtigste auf einen Blick
Input Delay
Wartezeit bis der Handler startet. Meist verursacht durch Main-Thread-Blockaden fremder Skripte.
Processing Time
Ausführungszeit des Handlers inklusive Alpine-Effekte. Größter Hebel bei Filterlogik und großen Listen.
Presentation Delay
Zeit bis zum nächsten Frame. Wird durch Layout Thrashing und teure CSS-Eigenschaften verlängert.
Profiling & Fixes
DevTools-Interactions-Spur zur Diagnose, scheduler.yield() und Virtualisierung zur Behebung.