Zum Inhalt springen
Core Web Vitals Spezialisten
Core Web Vitals & Messung

Web Worker: Main-Thread entlasten und INP verbessern

Rechenintensives JavaScript in einen Web Worker auslagern hält den Main-Thread frei, senkt die Total Blocking Time und bringt INP unter die 200-ms-Schwelle.

12 Min. Lesezeit Web WorkerMain-ThreadINPTotal Blocking TimeJavaScript

Eine Website kann blitzschnell laden und sich trotzdem träge anfühlen. Der Grund liegt fast immer an derselben Stelle: dem Main-Thread. Auf diesem einen Hauptstrang erledigt der Browser fast alles, was JavaScript ihm aufträgt -- und solange er mit einer Berechnung beschäftigt ist, kann er auf nichts anderes reagieren. Tippt, klickt oder scrollt in diesem Moment ein Besucher, muss die Eingabe warten. Genau diese Wartezeit misst die Interaction to Next Paint, kurz INP, die seit dem 12. März 2024 (web.dev) First Input Delay als Core Web Vital abgelöst hat. Der wirksamste Hebel gegen einen überlasteten Hauptstrang ist, rechenintensive Arbeit gar nicht erst dort auszuführen, sondern in einen Web Worker auszulagern -- einen zweiten Thread, der im Hintergrund rechnet, während der Main-Thread frei bleibt und Eingaben sofort beantwortet. Dieser Beitrag zeigt, welche Aufgaben sich lohnen, wie die Auslagerung technisch funktioniert und wie sich der Gewinn an INP und Total Blocking Time nachweisen lässt. In unserer Core-Web-Vitals-Optimierung steht die Frage nach dem Main-Thread stets weit oben, wenn eine Seite auf Eingaben zäh reagiert.

Das Wichtigste in Kürze

  • Der Browser führt JavaScript auf einem einzigen Main-Thread aus. Ab 50 ms (web.dev) gilt eine Aufgabe als lange Task, und für diese Spanne ist der Hauptstrang blockiert -- klickt ein Besucher genau dann, muss seine Eingabe warten.
  • Ein Web Worker ist ein zweiter Thread: Er nimmt Daten per postMessage entgegen, rechnet im Hintergrund und meldet nur das Ergebnis zurück. Zugriff auf DOM und window hat er nicht, daher gilt Berechnung in den Worker, Darstellung auf den Main-Thread.
  • Geeignet sind Filter über große Listen, Preis- und Rabattberechnungen, das Parsen großer JSON-Daten und das Bündeln von Analyse-Ereignissen. Aufgaben unter 50 ms (web.dev) lohnen nicht, weil Start und Kopieren der Daten den Nutzen übersteigen.
  • Als gut gilt ein INP unter 200 ms (web.dev), wobei mindestens 75 Prozent (web.dev) der Interaktionen darunter bleiben müssen. 77 Prozent (Web Almanac 2025) der mobilen Seiten erreichen diesen Wert bereits.
  • Im Labor dient die Total Blocking Time als Stellvertreter: Sie summiert für jede lange Task die Zeit oberhalb von 50 ms und macht rund 30 Prozent (DebugBear) des Lighthouse-Performance-Scores aus. Erst messen, dann gezielt eine Aufgabe auslagern.

Warum der Main-Thread zum Nadelöhr wird

Der Browser verarbeitet JavaScript standardmäßig single-threaded: Es gibt genau einen Main-Thread, der Skripte ausführt, das Layout berechnet, die Seite zeichnet und auf Klicks, Tastatureingaben und Scrollen reagiert. All das teilt sich dieselbe Warteschlange. Läuft eine Funktion, kann in dieser Zeit nichts anderes geschehen -- der Browser arbeitet einen Auftrag nach dem anderen ab. Solange die einzelnen Aufgaben kurz sind, merkt niemand etwas davon. Problematisch wird es, sobald eine einzelne Aufgabe zu lange am Stück läuft. Ab einer Dauer von 50 ms (web.dev) gilt sie als lange Task, und für diese Spanne ist der Hauptstrang blockiert. Klickt ein Besucher genau dann, verzögert sich die Antwort um exakt die verbleibende Laufzeit der Task.

In modernen Frontends häufen sich solche langen Tasks schnell an. Ein Produktfilter, der über tausende Artikel iteriert, eine Preis- und Rabattkalkulation im Warenkorb, das Parsen einer großen JSON-Antwort, das Aufbereiten von Analyse-Daten oder ein umfangreiches Skript, das beim Laden alles auf einmal initialisiert -- jede dieser Operationen kann den Main-Thread für hunderte Millisekunden festhalten. Der Nutzer erlebt das als hakelige Oberfläche: Der Klick auf einen Filter zeigt erst nach einer spürbaren Pause Wirkung, das Menü öffnet verzögert, das Eingabefeld nimmt Zeichen stockend an. Wie man die schädlichsten dieser langen Aufgaben systematisch aufspürt, behandeln wir im Beitrag zu den Long Animation Frames als INP-Bremsen; hier geht es um das Gegenmittel, die Arbeit vom Hauptstrang zu nehmen.

Was auf dem Main-Thread zusammenläuft

Der Main-Thread ist der einzige Ort, an dem der Browser die sichtbare Oberfläche verändern darf: Er führt das Seiten-JavaScript aus, berechnet das Layout, zeichnet Pixel und verarbeitet jede Nutzerinteraktion. Weil all diese Aufgaben nacheinander abgearbeitet werden, entsteht ein Engpass, sobald eine davon zu lange dauert. Ein Web Worker schafft daneben einen zweiten Thread -- allerdings ohne Zugriff auf die Oberfläche. Er eignet sich daher für reine Rechenarbeit, die kein DOM anfassen muss.

INP und die Total Blocking Time als Labor-Stellvertreter

INP misst im Feld, wie schnell eine Seite auf Interaktionen reagiert. Der Wert erfasst die Verzögerung zwischen einer Eingabe und dem nächsten sichtbaren Bildaufbau und meldet über eine Sitzung hinweg die langsamste bedeutende Interaktion. Als gut gilt ein INP unter 200 ms (web.dev); zwischen 200 und 500 ms (web.dev) besteht Verbesserungsbedarf, über 500 ms (web.dev) ist der Wert schlecht. Für ein gutes Zeugnis müssen mindestens 75 Prozent (web.dev) der Interaktionen unter der 200-ms-Marke bleiben. Nach den Auswertungen des Web Almanac erreichen aktuell 77 Prozent (Web Almanac 2025) der mobilen Seiten ein gutes INP -- besser als beim LCP, wo nur 62 Prozent (Web Almanac 2025) bestehen, aber die verbleibenden 23 Prozent (Web Almanac 2025) zeigen, dass Reaktionsschnelligkeit weiterhin ein verbreitetes Problem ist.

INP ist ein Feldwert und lässt sich im Labor nicht direkt messen, weil es echte Interaktionen braucht. Als Stellvertreter dient im Labor die Total Blocking Time, kurz TBT. Sie summiert für jede lange Task die Zeit oberhalb der 50-ms-Schwelle auf und beschreibt damit, wie stark der Main-Thread während des Ladens blockiert war. TBT gilt als der beste Labor-Stellvertreter für INP und macht in der Lighthouse-Bewertung rund 30 Prozent (DebugBear) des Performance-Scores aus. Als gut gelten unter 200 ms (web.dev), zwischen 200 und 600 ms (web.dev) besteht Verbesserungsbedarf. Weil beide Werte dieselbe Ursache haben -- einen zu lange belegten Hauptstrang -- senkt jede ausgelagerte Rechenaufgabe in der Regel beide zugleich. Wie sich der Diagnosebericht dazu lesen und priorisieren lässt, zeigt unser Beitrag zum Lesen des Lighthouse-Reports.

Was ein Web Worker ist und was er kann

Ein Web Worker ist ein zusätzlicher JavaScript-Thread, der parallel zum Main-Thread läuft. Er wird aus einer eigenen Skriptdatei gestartet und kommuniziert mit der Seite ausschließlich über Nachrichten: Der Main-Thread schickt Daten per postMessage in den Worker, der Worker rechnet und schickt das Ergebnis per postMessage zurück. Während der Worker arbeitet, bleibt der Hauptstrang völlig frei -- er kann Klicks entgegennehmen, das Menü öffnen und die Seite neu zeichnen, ohne auf die Berechnung zu warten. Erst wenn das Ergebnis eintrifft, aktualisiert der Main-Thread die Oberfläche. Aus Sicht des Nutzers reagiert die Seite die ganze Zeit über flüssig, auch wenn im Hintergrund eine aufwendige Rechnung läuft.

Der Preis dafür ist eine strikte Trennung. Ein Worker hat keinen Zugriff auf das DOM, auf das window-Objekt oder auf viele browserseitige Schnittstellen. Er kann also keine Elemente verändern, keine Klassen setzen und nichts direkt auf den Bildschirm bringen. Das ist kein Mangel, sondern der Kern des Konzepts: Nur der Main-Thread darf die Oberfläche anfassen, weil sonst zwei Threads gleichzeitig am selben Bild zeichnen würden. Für die Praxis bedeutet das eine klare Arbeitsteilung -- der Worker liefert Daten, der Main-Thread setzt sie in sichtbare Änderungen um. Anders als das reine Aufteilen langer Aufgaben, das wir im Beitrag zu scheduler.yield und langen Tasks behandeln, verschwindet die Arbeit hier ganz vom Hauptstrang, statt nur in kleinere Häppchen zerlegt zu werden.

Welche Aufgaben in den Worker gehören

Ein Web Worker lohnt sich überall dort, wo länger gerechnet wird, ohne dass die Berechnung selbst die Oberfläche berühren muss. Die Faustregel: Alles, was nur Daten hinein- und Daten herausgibt, ist ein Kandidat. Alles, was das DOM verändert, bleibt auf dem Main-Thread. Je länger eine Aufgabe läuft und je häufiger sie durch Nutzerinteraktion ausgelöst wird, desto größer ist der Gewinn. Ein Filter, der bei jedem Klick über einen großen Datenbestand läuft, ist das Paradebeispiel -- er blockiert genau in dem Moment, in dem der Nutzer aktiv ist. Auch Drittanbieter-Skripte lassen sich teilweise in einen Worker verlagern; wie man deren Last grundsätzlich reduziert, behandelt der Beitrag zum Entschlacken von Third-Party-Skripten.

Suchen und Filtern

Das Durchsuchen und Filtern großer Listen -- etwa tausende Produkte nach Größe, Farbe und Preis -- ist reine Rechenarbeit. Im Worker läuft sie, ohne dass der Filterklick spürbar hakt.

Preise und Rabatte berechnen

Staffelpreise, Rabattregeln und Steuersätze im Warenkorb neu zu berechnen, kostet bei komplexen Sortimenten Zeit. Der Worker liefert die fertigen Beträge zurück, die Oberfläche bleibt bedienbar.

Daten aufbereiten und tracken

Das Parsen großer JSON-Antworten und das Sammeln von Analyse-Ereignissen bindet den Hauptstrang. Im Hintergrund aufbereitet und gebündelt gesendet, konkurriert es nicht mehr mit der Interaktion.

Der Worker sieht das DOM nicht

Ein Web Worker kann keine Elemente lesen oder verändern, keine Klassen umschalten und nicht auf window oder document zugreifen. Wer versucht, im Worker die Oberfläche anzufassen, erhält einen Fehler. Die Regel lautet daher: Berechnung in den Worker, Darstellung auf den Main-Thread. Das Ergebnis wird per Nachricht zurückgereicht, und erst der Hauptstrang übersetzt es in sichtbare Änderungen. Diese Trennung sauber zu ziehen ist der wichtigste Schritt einer gelungenen Auslagerung.

So läuft die Auslagerung im Code ab

Technisch ist ein Worker schnell eingerichtet. Auf dem Main-Thread wird er aus einer Skriptdatei erzeugt, bekommt seine Eingangsdaten per postMessage und meldet sein Ergebnis über ein onmessage-Ereignis zurück. Der folgende, bewusst schlanke Ausschnitt zeigt das Muster am Beispiel eines Produktfilters: Der Klick schickt die Rohdaten in den Worker, statt selbst zu filtern.

main.js
// Rechenintensive Filterung in einen Web Worker auslagern
const worker = new Worker('/js/filter.worker.js', { type: 'module' });

button.addEventListener('click', () => {
  // Daten in den Hintergrund-Thread schicken, Main-Thread bleibt frei
  worker.postMessage({ produkte, filter });
});

worker.onmessage = (e) => {
  render(e.data.treffer); // fertiges Ergebnis anzeigen
};

Die eigentliche Arbeit erledigt die Worker-Datei. Sie nimmt die Daten entgegen, filtert und schickt allein das Ergebnis zurück. Weil dieser Code in einem eigenen Thread läuft, blockiert selbst eine aufwendige Filterung die Oberfläche nicht:

filter.worker.js
// filter.worker.js - läuft im eigenen Thread, blockiert die Oberfläche nicht
self.onmessage = (e) => {
  const { produkte, filter } = e.data;
  const treffer = produkte.filter(passtZu(filter)); // die schwere Arbeit
  self.postMessage({ treffer });
};

In größeren Anwendungen wird die Nachrichtenkommunikation schnell unübersichtlich. Hier hilft die quelloffene Bibliothek Comlink, die den Austausch zwischen Main-Thread und Worker so kapselt, dass sich Worker-Funktionen fast wie normale Funktionsaufrufe nutzen lassen. Ein Detail bleibt zu beachten: Beim Verschicken werden die Daten kopiert (structured clone), was bei sehr großen Objekten selbst Zeit kostet. Für solche Fälle lassen sich Daten per Transferable oder über einen SharedArrayBuffer übergeben, statt sie zu kopieren. Diese Feinheiten der Frontend-Optimierung entscheiden darüber, ob sich die Auslagerung auch bei großen Datenmengen lohnt.

Grenzen und Fallstricke

Ein Worker ist kein Allheilmittel. Das Starten kostet einen kleinen Grundaufwand, und jede Nachricht muss serialisiert und kopiert werden. Für sehr kurze Berechnungen unter der 50-ms-Grenze übersteigt dieser Overhead den Nutzen -- solche Aufgaben bleiben besser auf dem Main-Thread oder werden mit anderen Mitteln entzerrt. Auch DOM-Aktualisierungen gehören zwingend auf den Hauptstrang. Der Worker glänzt dort, wo eine klar abgegrenzte, längere Rechenaufgabe reine Daten verarbeitet. Die folgende Übersicht ordnet typische Frontend-Aufgaben ein:

Typische AufgabeWirkung auf den Main-ThreadIn den Worker auslagern?
Produktfilter über tausende ArtikelLange Task, blockiert den KlickJa -- reine Rechenarbeit ohne DOM
Preis- und Rabattberechnung im WarenkorbVerzögerung bei jeder ÄnderungJa -- liefert nur Beträge zurück
Großes JSON oder CSV parsenBlockiert bis alles verarbeitet istJa -- Ergebnis wandert fertig zurück
Analyse- und Tracking-Daten bündelnKonkurriert mit der InteraktionJa -- im Hintergrund sammeln und senden
DOM aktualisieren, Klassen setzenMuss die Oberfläche zeichnenNein -- nur der Main-Thread malt die Seite
Kurze Berechnung unter 50 msKaum spürbarer EinflussNein -- der Overhead lohnt nicht

Der Kern in einem Satz

Nicht jede Berechnung muss schneller werden -- sie muss nur weg vom Main-Thread, wenn der Nutzer gerade auf eine Antwort wartet. Ein Web Worker verschiebt die Arbeit dorthin, wo sie niemanden blockiert.

Messen, priorisieren und nachweisen

Auslagern ohne Messung ist Ratewerk. Der erste Schritt ist deshalb, die langen Tasks überhaupt zu finden. Das Performance-Panel der Chrome-Entwicklerwerkzeuge markiert lange Tasks im Zeitstrahl mit einer roten Ecke und zeigt, welche Funktion den Hauptstrang wie lange belegt. Der Lighthouse-Bericht weist die Total Blocking Time aus und listet unter den Diagnosen die größten Verursacher. Für das echte Nutzererlebnis zählt am Ende der Feldwert INP, den die quelloffene web-vitals-Bibliothek in ihrer Attribution-Variante samt der auslösenden Interaktion liefert. Wichtig ist, im Labor mit realistischer Drosselung zu messen -- ein schnelles Bürogerät verbirgt genau die Blockaden, die auf einem durchschnittlichen Smartphone am stärksten wehtun. Wie sich das Ergebnis zusätzlich für schwache Geräte und Netze anpassen lässt, zeigt der Beitrag zum adaptiven Laden auf langsamen Netzen.

  1. Lange Tasks aufspüren -- im Performance-Panel oder über die Lighthouse-Diagnose zur Total Blocking Time
  2. Die teuerste Aufgabe prüfen: Verarbeitet sie reine Daten, ohne das DOM zu berühren?
  3. Nur diese Berechnung in einen Web Worker auslagern und das Ergebnis per Nachricht zurückreichen
  4. Die Nachrichtengröße im Blick behalten -- bei großen Objekten Transferable oder SharedArrayBuffer nutzen
  5. TBT im Labor erneut messen und mit dem Zustand davor vergleichen
  6. Den INP in den Felddaten echter Nutzer nachverfolgen, bis er stabil unter 200 ms liegt

Wir jagen nicht jede Millisekunde, sondern die eine Aufgabe, die den Hauptstrang blockiert, während der Nutzer klickt. Sie in einen Worker zu verschieben bringt oft mehr als ein Dutzend kleiner Mikrooptimierungen -- und der Effekt ist im INP der echten Besucher messbar.

Projekterfahrung aus 50+ Performance-Projekten

Genau so gehen wir in einem strukturierten Performance-Audit vor: die langen Tasks im Feld und im Labor benennen, die auslagerbare Rechenarbeit vom Hauptstrang nehmen und den Gewinn an Total Blocking Time und INP nachweisen -- statt pauschal Skripte zu verkleinern. Häufig verschiebt sich der Engpass nach dem ersten Eingriff, weil dann eine andere Interaktion dominiert; die Analyse wird deshalb wiederholt, bis die Seite über alle wichtigen Interaktionen hinweg flüssig reagiert. Welche Leistungen dazugehören, zeigt unsere Übersicht der Performance-Leistungen; wie sich ein fester Grenzwert für Skriptlast dauerhaft absichern lässt, behandelt der Beitrag zu den JavaScript-Performance-Budgets. Wer den flüssigen Eindruck bis in die Navigation ziehen will, findet im Beitrag zu den nahtlosen Seitenwechseln mit View Transitions den nächsten Schritt.

Dieser Artikel basiert auf Daten aus: web.dev (Interaction to Next Paint, Optimize long tasks und Total Blocking Time, Google Chrome), DebugBear (Total Blocking Time und Lighthouse-Gewichtung) und Web Almanac 2025 (HTTP Archive, Performance-Kapitel). Alle genannten Statistiken wurden zum Zeitpunkt der Veröffentlichung geprüft.

Verwandte Artikel