INP im Detail: Warum Interaktionen in Magento-Shops oft zu langsam sind
SERP
<meta>
SEO · INP Deep Dive · Performance · Magento 2
INP im Detail:
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.

12 Min. Lesezeit Input Delay · Processing Time · Presentation Delay Alpine.js · Chrome DevTools · Main Thread

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.

11. FAQ: INP im Detail

1Was ist der Unterschied zwischen INP und den drei Phasen Input Delay, Processing Time und Presentation Delay?
INP ist der Gesamtwert einer Interaktion in Millisekunden. Die drei Phasen sind die Teilabschnitte, aus denen sich dieser Wert zusammensetzt, und lassen sich einzeln im DevTools Performance-Panel messen.
2Warum reicht "JavaScript reduzieren" als Ratschlag nicht aus, um INP zu verbessern?
INP-Probleme können in allen drei Phasen entstehen. Ohne Phasen-Messung optimiert man häufig am eigentlichen Engpass vorbei.
3Wie genau berechnet Google den INP-Wert einer Seite?
Als annähernd das 98. Perzentil aller über interactionId gruppierten Interaktionen einer Sitzung, nicht als Durchschnitt.
4Welche Rolle spielen Drittanbieter-Skripte konkret beim INP?
Sie registrieren eigene Event-Listener und blockieren durch synchrones Laden den Main Thread, was primär den Input Delay erhöht.
5Warum kann Alpine.js trotz fehlendem Virtual DOM bei großen x-for-Listen ruckeln?
Jedes Element erhält eigene Effekte. Bei hunderten Zeilen werten sich bei einer Zustandsänderung alle Effekte synchron neu aus.
6Wie liest man einen Long Task im Chrome DevTools Performance-Panel richtig?
Long Tasks über 50ms sind mit einem roten Dreieck markiert. Bottom-Up und Call Tree zeigen die Funktion mit der höchsten Self Time.
7Was bewirkt scheduler.yield() und wird es von allen Browsern unterstützt?
Gibt die Kontrolle kurzzeitig an den Main Thread zurück. Primär in Chromium verfügbar, daher braucht es ein setTimeout-Fallback.
8Wie virtualisiere ich eine lange Produktliste in einem Hyvä-Shop?
Nur sichtbare Zeilen werden als echte Alpine-Komponenten gemountet, der Rest bleibt ein Platzhalter mit reservierter Höhe.
9Was ist Layout Thrashing und wie entsteht es durch getBoundingClientRect?
Entsteht durch abwechselndes Lesen und Schreiben von Layout-Eigenschaften in einer Schleife und erzwingt synchrone Reflows.
10Ab welcher Tasklänge spricht man von einem Long Task und warum ist die 50ms-Grenze wichtig?
Ab 50 Millisekunden, weil ab diesem Wert wartende Eingaben spürbar verzögert werden. Die Long Tasks API markiert diese automatisch.