Layer 8Ehrlicher Tech-Support
SoftwareSchweregrad: LästigERR_REQUIRE_ESM

Node: ERR_REQUIRE_ESM kommt nur noch aus altem Node

require() eines ES-Moduls bricht mit ERR_REQUIRE_ESM ab. Aktuelles Node lädt solche Module längst synchron; der Fehler ist offiziell veraltet.

Aufwand:
ca. 15 Min.
Voraussetzung:
Ein Terminal mit node, Die Datei package.json des Projekts, Ein Versionsverwalter für Node (nvm, fnm, Volta o. ä.) oder ein Installationspaket von nodejs.org
Gilt für:
Node.js ab v20.19.0, v22.12.0 und v23.0.0 (require() lädt ES-Module ohne Schalter); Dokumentation von Node.js v26.12.0 (nodejs.org/docs/latest), Ältere Node-Versionen bis v20.18 und v22.11, die ERR_REQUIRE_ESM noch werfen

Das Symptom

Ein Skript, ein Build-Werkzeug oder ein Testlauf bricht mit einem Stapel ab, der mit Error [ERR_REQUIRE_ESM] beginnt. Meist steckt dahinter ein Paket, das in einer neuen Hauptversion auf ES-Module umgestellt wurde, während der eigene Code oder ein Werkzeug dazwischen es noch mit require() lädt. Die üblichen Ratschläge lauten: Paket auf die letzte CommonJS-Version zurückpinnen, alles auf import umschreiben, oder package.json mit type: module versehen und dann die halbe Werkzeugkette hinterherziehen.

Das Verwirrende: Auf dem Rechner des Kollegen läuft derselbe Code ohne Fehler. In der CI fällt er um. Oder umgekehrt. Niemand hat den Code angefasst, und doch verhält sich dasselbe require() auf zwei Maschinen verschieden.

Die „Layer 8“-Wahrheit

Der Unterschied liegt nicht im Code, sondern in der Node-Version. Die aktuelle Fehlerreferenz von Node.js führt ERR_REQUIRE_ESM mit der Einstufung Stability: 0 - Deprecated und erklärt: „This error has been deprecated since require() now supports loading synchronous ES modules.“ Die Versionsgeschichte des Eintrags nennt die Grenze: „v23.0.0, v22.12.0, v20.19.0 require() now supports loading synchronous ES modules by default.“ Ein Node 20.18 oder 22.11 wirft den Fehler noch, 20.19 und 22.12 nicht mehr. Wer im September die Regel gelernt hat, dass CommonJS keine ES-Module laden kann, hat eine Regel gelernt, die in aktuellem Node nicht mehr gilt.

Die Modulreferenz beschreibt die neue Fähigkeit ausführlich. Sie war ab v22.0.0 und v20.17.0 hinter einem Schalter vorhanden, fiel dann in drei Zweigen aus dem Experimentalstatus, und seit v25.4.0 heißt es schlicht: „This feature is no longer experimental.“ Dazu gehört eine Bedingung, die die eigentliche Grenze zieht: Das zu ladende Modul muss vollständig synchron sein, darf also kein top-level await enthalten, weder selbst noch in einem seiner Importe. Ist das verletzt, kommt kein ERR_REQUIRE_ESM, sondern ERR_REQUIRE_ASYNC_MODULE, und die Dokumentation sagt klar, was dann zu tun ist: das Modul per import() laden. Für die Fehlersuche gibt es seit v26.5.0 die Eigenschaft requireStack, die die Kette der beteiligten Module enthält, und den Schalter –experimental-print-required-tla, der die Fundstellen der top-level awaits auf stderr schreibt.

Zweite Fußangel: Was require() zurückgibt, ist der Modul-Namensraum, nicht der Standardexport. Ein ES-Modul mit export default landet als Objekt mit den Eigenschaften default und __esModule: true im aufrufenden Code. Bibliotheksautoren können das ändern, indem sie den gewünschten Wert unter dem Exportnamen module.exports exportieren; die Modulreferenz zeigt das am Beispiel einer Klasse Point und weist darauf hin, dass benannte Exporte dann für CommonJS-Nutzer verloren gehen, wenn sie nicht als Eigenschaften am Standardexport hängen. Wer also nach dem Node-Update statt einer Funktion plötzlich ein Objekt mit default in der Hand hält, sieht korrektes Verhalten.

Und für den Fall, dass das neue Verhalten etwas kaputtmacht, ist die Rückfahrkarte dokumentiert: –no-require-module schaltet die Fähigkeit ab, –trace-require-module zeigt, wo sie benutzt wird, und process.features.require_module verrät zur Laufzeit, ob sie vorhanden ist. Die Entscheidung Zurückpinnen, Umschreiben oder Node aktualisieren ist damit keine Glaubensfrage mehr. Erst die Version prüfen, dann den Code anfassen. Meistens ist nur das Erste nötig.

Die echte, verifizierte Lösung

1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:

  1. 1.

    Node-Version feststellen

    Bevor irgendjemand Abhängigkeiten umschreibt, gehört die laufende Version auf den Tisch, und zwar die, die den Fehler wirft, nicht die auf dem Entwicklerrechner. In CI-Umgebungen, Docker-Basisbildern und auf Servern laufen oft ältere Zweige. Die Grenzen laut Modulreferenz: v20.19.0, v22.12.0 und v23.0.0 laden ES-Module per require() ohne Schalter. Liegt die ausgegebene Version darunter, ist der Fehler erklärt.

    node --version
    node -p "process.features.require_module"
    
  2. 2.

    Node aktualisieren oder die Fähigkeit erkennen

    Die einfachste Lösung ist ein aktuelles Node auf einem gepflegten Zweig. Auf v20 reicht ein Sprung auf 20.19 oder neuer, auf v22 auf 22.12 oder neuer. Zwischen v22.0.0 und v22.11 ließ sich die Fähigkeit laut Versionsgeschichte mit --experimental-require-module einschalten; das ist eine Notlösung, keine Dauerlösung, weil solche Schalter bei jedem Aufruf mitgegeben werden müssen und in Diensten gern vergessen werden. Nach dem Wechsel gibt der Aufruf aus dem ersten Schritt true aus.

    nvm install 22
    nvm use 22
    node -p "process.features.require_module"
    
  3. 3.

    Bei ERR_REQUIRE_ASYNC_MODULE auf import() ausweichen

    Bleibt es auf aktuellem Node bei einem Fehler, lautet er ERR_REQUIRE_ASYNC_MODULE: Irgendwo im Modulgraphen steht ein top-level await, und das lässt sich synchron nicht laden. Dann ist dynamisches import() der dokumentierte Weg. Es liefert ein Promise, der aufrufende Code muss also asynchron werden oder das Promise an einer Stelle auflösen, an der das möglich ist. Welche Datei das await enthält, zeigt --experimental-print-required-tla, wenn der Fehler unbehandelt bleibt.

    node --experimental-print-required-tla app.js
    // statt: const lib = require('./lib.mjs');
    const lib = await import('./lib.mjs');
    
  4. 4.

    Rückgabewert richtig auspacken

    Wer von einer Bibliothek die Funktion selbst erwartet, aber ein Objekt mit default bekommt, hat kein Fehlverhalten vor sich, sondern den Modul-Namensraum. Der Standardexport liegt in .default, benannte Exporte liegen unter ihrem Namen. Ob die Bibliothek stattdessen module.exports als Exportnamen verwendet und damit direkt den Wert liefert, entscheidet ihre Quelle, nicht die eigene Konfiguration. Im Zweifel beide Formen abfangen.

    const ns = require('./point.mjs');
    const Point = ns.default ?? ns;
    
  5. 5.

    Im Notfall das alte Verhalten erzwingen

    Bricht nach einem Node-Update etwas, weil ein Paket plötzlich als ES-Modul geladen wird, das vorher über einen Umweg als CommonJS kam, lässt sich die Fähigkeit mit --no-require-module abschalten und mit --trace-require-module nachvollziehen, welche Stellen betroffen sind. Beides ist dokumentiert; beides ist aber ein Pflaster, keine Heilung. Die dauerhafte Lösung ist, den Code auf import() umzustellen oder das betroffene Paket zu aktualisieren.

    node --trace-require-module app.js
    node --no-require-module app.js
    

Häufige Fragen (FAQ)

?Was bedeutet ERR_REQUIRE_ESM genau?

Ein CommonJS-Modul hat versucht, per require() ein ES-Modul zu laden, und die laufende Node-Version konnte das nicht. Die Fehlerreferenz von Node.js beschreibt den Code so: „An attempt was made to require() an ES Module.“ Entscheidend ist der zweite Satz: „This error has been deprecated since require() now supports loading synchronous ES modules.“ Der Fehler steht in der aktuellen Dokumentation mit Stabilität 0, also als veraltet. Wer ihn sieht, läuft fast sicher auf einem älteren Node als der Dokumentation, die er gerade liest.

?Ab welcher Node-Version verschwindet der Fehler?

Die Modulreferenz führt die Geschichte des Abschnitts zum Laden von ES-Modulen per require() auf: Eingeführt in v22.0.0 und v20.17.0 hinter einem Schalter; „v23.0.0, v22.12.0, v20.19.0 This feature is no longer behind the --experimental-require-module CLI flag.“ Die Warnung beim Laden fiel in v23.5.0, v22.13.0 und v20.19.0 weg, und mit v25.4.0 heißt es: „This feature is no longer experimental.“ Ein Node 20.18 wirft den Fehler also noch, ein Node 20.19 oder 22.12 nicht mehr, ohne dass am Code etwas geändert wurde.

?Mein Node ist aktuell und trotzdem knallt es, nur mit ERR_REQUIRE_ASYNC_MODULE. Was ist das?

Das ist die Grenze der neuen Fähigkeit. require() lädt ein ES-Modul nur, wenn laut Modulreferenz die Bedingung erfüllt ist: „The module is fully synchronous“ (contains no top-level await). Enthält das Modul oder irgendein von ihm importiertes Modul ein top-level await, gilt: „ERR_REQUIRE_ASYNC_MODULE will be thrown. In this case, users should load the asynchronous module using import().“ Die Fehlerreferenz nennt für diesen Fall die Eigenschaft requireStack mit der Kette der beteiligten Module und den Schalter --experimental-print-required-tla, der die Stellen des top-level await auf stderr ausgibt.

?Kann ich das neue Verhalten abschalten, wenn etwas kaputtgeht?

Ja. Die Modulreferenz sagt: „If support for loading ES modules using require() results in unexpected breakage, it can be disabled using --no-require-module.“ Mit --trace-require-module lässt sich ausgeben, wo die Fähigkeit benutzt wird, und im Code zeigt process.features.require_module an, ob sie verfügbar ist. Das ist der saubere Weg, ein älteres Verhalten vorübergehend nachzustellen, statt Abhängigkeiten zu pinnen, die mit dem neuen Verhalten eigentlich gar nichts zu tun haben.

?Warum bekomme ich ein Objekt mit default statt der Funktion, die ich erwartet habe?

Weil require() eines ES-Moduls den Modul-Namensraum liefert, nicht den Standardexport. Die Modulreferenz zeigt es am Beispiel: Ein Modul mit export default class Point kommt als Objekt mit den Eigenschaften default und __esModule: true an. Will der Autor des ES-Moduls, dass require() direkt einen Wert zurückgibt, kann er ihn unter dem Exportnamen module.exports exportieren; dann gehen benannte Exporte für CommonJS-Nutzer verloren, wenn sie nicht als Eigenschaften am Standardexport hängen. Das ist Sache des Bibliotheksautors, nicht des Aufrufers.

Belege

Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.

Zuletzt geprüft am . Dokumentation altert – ein Datum daneben ist der einzige ehrliche Umgang damit.

Auch zu finden unter

Verwandte Fehler

SWLästig

npm meldet Schwachstellen (vulnerabilities)

npm warnt nach der Installation vor Sicherheitslücken? Nicht jede ist gefährlich — und ein blindes npm audit fix kann mehr kaputt machen, als es behebt.