TypeScript Strict Mode: Warum dein Team ihn aktivieren sollte und wie der Umstieg gelingt

Sandor Farkas
Gründer & Lead Developer
Experte für Softwareentwicklung und Legacy-Code-Optimierung
LinkedInEine Dashboard-Komponente geht am Montagmorgen in Produktion. Am Dienstagnachmittag trifft ein Support-Ticket ein: Die Seite stürzt mit Cannot read properties of undefined (reading 'totalRevenue') ab, sobald ein neu registrierter Nutzer, der das Onboarding nicht abgeschlossen hat, das Dashboard lädt. Der TypeScript-Compiler war zufrieden. Der Typ sagte User.companyMetrics: CompanyMetrics. Die API gibt für Nutzer ohne Unternehmen null zurück, und der Frontend-Code hat das nie geprüft.
Das ist genau die Klasse von Bugs, die der TypeScript Strict Mode verhindern soll, und es ist einer der häufigsten Gründe, warum Teams, die TypeScript bereits nutzen, weiterhin Laufzeitfehler ausliefern. Den Strict Mode einzuschalten ist keine kosmetische Änderung. Es ist eine Verpflichtung zu einem strengeren Vertrag zwischen deinen Typen und deiner Laufzeit, und auf einer bestehenden Codebasis bringt er hunderte latenter Bugs ans Licht, die sich still hinter laxen Defaults versteckt haben. Dieser Beitrag erklärt, was der TypeScript Strict Mode tatsächlich aktiviert, welche Flags den höchsten Return on Investment liefern und eine schrittweise Einführungsstrategie, die für Teams funktioniert, die es sich nicht leisten können, die Feature-Entwicklung zwei Wochen lang einzufrieren, während sie 2.000 Compiler-Fehler beheben. So gelingt der Umstieg, ohne dass die Roadmap stillsteht.
Was der TypeScript Strict Mode tatsächlich aktiviert
"strict": true in der tsconfig.json zu setzen ist ein Meta-Flag. Es aktiviert acht einzelne Optionen auf einmal, und sie einzeln zu verstehen ist wichtig, weil du sie während einer Migration einzeln aktivieren wirst. Stand TypeScript 5.x sind die Flags: noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, alwaysStrict, noImplicitThis und useUnknownInCatchVariables.
Zwei davon sind in Bezug auf Fehlervolumen und realen Effekt weit wichtiger als die anderen. strictNullChecks zwingt dazu, null und undefined als eigenständige Typen zu behandeln, statt als Werte, die jedem Typ zuweisbar sind, was den obigen Dashboard-Bug abgefangen hätte. noImplicitAny zwingt jeden Parameter, jeden Rückgabewert und jede Variable ohne explizite Annotation und ohne ableitbaren Typ zur Annotation, statt stillschweigend auf any zurückzufallen. Zusammen sind diese beiden Flags für die überwältigende Mehrheit der Fehler verantwortlich, die du beim ersten Aktivieren des Strict Mode auf einer Legacy-Codebasis siehst, und sie sind auch für die meisten Laufzeit-Bugs verantwortlich, die der Strict Mode verhindert.
Zwei weitere Flags verdienen eigenständige Aufmerksamkeit. strictPropertyInitialization zwingt Klassen-Eigenschaften dazu, im Konstruktor initialisiert oder als optional deklariert zu werden, und fängt eine subtile Familie von Bugs ab, bei denen auf eine Eigenschaft zugegriffen wird, bevor sie zugewiesen wurde. useUnknownInCatchVariables typisiert abgefangene Fehler als unknown statt any und zwingt den Code dazu, einzugrenzen, bevor er .message oder .stack auf etwas zugreift, das vielleicht gar kein Error ist. Es gibt außerdem ein separates Flag, exactOptionalPropertyTypes, das nicht durch strict: true aktiviert wird, aber eine spezifische Lücke in der Behandlung optionaler Eigenschaften schließt: { name?: string } akzeptiert normalerweise auch { name: undefined } und nicht nur {}, was manchmal nicht das ist, was du willst. Aktiviere es erst, nachdem du den Rest stabilisiert hast.
Warum Strict Null Checks das wichtigste Flag ist, das du je aktivieren wirst
Ohne strictNullChecks behandelt TypeScript null und undefined als Untertypen jedes anderen Typs. Eine als string typisierte Variable kann zur Laufzeit legal null enthalten, und der Compiler warnt dich nicht.
// strictNullChecks AUS
interface User {
email: string;
}
function getEmailDomain(user: User): string {
return user.email.split('@')[1]; // Stuerzt zur Laufzeit ab, wenn email tatsaechlich null ist
}
Mit aktiviertem strictNullChecks zwingt das Typsystem den Code, den Fall zu behandeln, falls User.email null sein kann:
// strictNullChecks AN
interface User {
email: string | null;
}
function getEmailDomain(user: User): string {
if (user.email === null) {
return '';
}
const parts = user.email.split('@');
return parts[1] ?? '';
}
Ein sekundärer Effekt überrascht Teams oft: Array-Indexierung gibt T | undefined zurück, sobald auch noUncheckedIndexedAccess aktiviert ist, und selbst ohne dieses Flag gibt Array.prototype.find T | undefined zurück. Code, der früher annahm, ein .find()-Ergebnis sei immer vorhanden, muss nun den fehlenden Fall explizit behandeln. Das ist kein Bug in TypeScript, sondern TypeScript, das dem Entwickler endlich von einer Annahme erzählt, die still getroffen wurde.
Eine realistische Migrationsstrategie für bestehende Codebasen
Der häufigste Fehler, den Teams beim Aktivieren des Strict Mode machen, ist, das Flag an einem Freitagnachmittag in einem Pull Request umzulegen und am Montag in einem Meer von zweitausend Fehlern zu ertrinken. Eine bessere Strategie ist inkrementell, wobei jeder Schritt eine Codebasis erzeugt, die sauber kompiliert.
Der erste Schritt ist, eine zweite tsconfig zu erstellen (nenne sie tsconfig.strict.json), die die Hauptkonfiguration erweitert und den Strict Mode nur für eine Whitelist einer Teilmenge von Dateien aktiviert. Beginne mit brandneuen Dateien und einem kleinen, gut isolierten Modul, das du zuerst härten willst. Verwende include, um die Dateien unter strikter Prüfung aufzulisten, und führe beide Konfigurationen in CI aus: Die Hauptkonfiguration muss die gesamte Codebasis fehlerfrei kompilieren, und die strikte Konfiguration muss die Whitelist-Teilmenge fehlerfrei kompilieren. So härtest du die Codebasis Datei für Datei, ohne je den Build zu zerstören.
// tsconfig.strict.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true
},
"include": [
"src/domain/**/*.ts",
"src/lib/payments/**/*.ts"
]
}
Der zweite Schritt ist, ein striktes Flag nach dem anderen auf Ebene der gesamten Codebasis zu aktivieren. noImplicitAny ist meist das einfachste: Es fängt fehlende Annotationen ab, fördert aber selten Logik-Bugs zutage. strictNullChecks ist das schwierigste und wertvollste. Es über eine etablierte Next.js-Anwendung zu aktivieren erzeugt typischerweise zwischen 200 und 2.000 Fehler, je nach Codebasis-Größe und bestehender Disziplin. Behebe die Fehler in Stapeln nach Modul statt nach Fehleranzahl, weil sich Fehler um spezifische Domänenkonzepte gruppieren (ein User-Objekt, das null sein kann, ein aus der Umgebung gelesener Konfigurationswert, eine Server-Action, deren Rückgabetyp tatsächlich einen Fehlerfall enthält). Sie gemeinsam zu beheben offenbart oft eine sauberere Form für den Domänentyp.
Der dritte Schritt ist, noUncheckedIndexedAccess und exactOptionalPropertyTypes zu übernehmen, nachdem das Team mit den grundlegenden Strict-Flags vertraut ist. Diese fangen subtilere Bugs ab, erfordern aber mehr Refactoring, um erfüllt zu werden, und sie zu früh zu aktivieren erzeugt tendenziell Widerstand, der die gesamte Migration verlangsamt.
Muster für den Umgang mit den schwierigsten Fehlern
Die meisten Strict-Mode-Fehler fallen in eine Handvoll Kategorien, jede mit einem gut verstandenen Muster.
Bei Fehlern zu möglicherweise null-wertigen Werten bevorzuge Type Guards und frühe Returns gegenüber Non-Null-Assertions. Der !-Operator (user.email!.split('@')[1]) bringt den Compiler zum Schweigen, ohne zur Laufzeit etwas Nützliches zu tun: Der Absturz passiert trotzdem, nur die Warnung verschwindet. Reserviere ! für Fälle, in denen externes Wissen, das der Compiler nicht sehen kann, garantiert, dass der Wert vorhanden ist, etwa Werte, die bereits früher im Request-Zyklus von einer Laufzeit-Schema-Bibliothek wie Zod oder Valibot validiert wurden.
Bei Fehlern zu unknown in Catch-Blöcken führe einen kleinen Helfer zur Fehlereingrenzung ein:
function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === 'string') return error;
return 'Unknown error';
}
try {
await processPayment(orderId);
} catch (error) {
logger.error('Payment failed', { message: getErrorMessage(error), orderId });
}
Bei Fehlern zu nicht initialisierten Klassen-Eigenschaften ist die richtige Lösung fast immer, die Eigenschaft im Konstruktor zu initialisieren, sie mit ? als optional zu markieren oder die Definite-Assignment-Assertion ! für Eigenschaften zu verwenden, die tatsächlich von einem Framework initialisiert werden (Angulars Dependency Injection, TypeORM-Relationen). Vermeide das verlockende Muster, Eigenschaften Default-Werte zu geben, die keinen gültigen Zustand darstellen. Ein leerer String für eine User-ID etwa erzeugt stille Bugs weiter unten, wenn Code annimmt, jeder nicht-leere Wert, den er sieht, sei echt.
Bei großen Refactorings in React- und Next.js-Codebasen eliminieren Discriminated Unions ganze Kategorien von Null-Check-Fehlern auf Typebene. Eine Komponente, die entweder einen Lade-, einen Fehler- oder einen Datenzustand anzeigt, wird viel sauberer, wenn ihre Props erzwingen, dass genau einer dieser drei Zustände existiert:
type DashboardState =
| { status: 'loading' }
| { status: 'error'; message: string }
| { status: 'success'; data: DashboardData };
function Dashboard({ state }: { state: DashboardState }) {
if (state.status === 'loading') return <Spinner />;
if (state.status === 'error') return <ErrorBanner message={state.message} />;
return <Panel data={state.data} />;
}
Der Compiler garantiert nun, dass auf state.data nur im Success-Zweig und auf state.message nur im Error-Zweig zugegriffen wird. Keine Laufzeitprüfungen, kein Optional Chaining, keine defensiven Defaults: Die Typen haben die Arbeit erledigt.
Den Strict Mode anlassen: Regression verhindern
Sobald der Strict Mode aktiviert ist, ist die schwierigere Aufgabe, ihn anzulassen. Zwei Gewohnheiten verhindern Regression. Erstens: Erzwinge die strikte Konfiguration in CI mit einem separaten Build-Schritt, der den Pull Request scheitern lässt, falls neue oder geänderte Dateien strikte Fehler einführen. Das ist wichtig, weil ein Code-Review allein nicht zuverlässig ist, um ein wieder eingeführtes any oder einen fehlenden Null-Check abzufangen. Zweitens: Füge ESLint-Regeln (@typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion) auf Schweregrad warn oder error hinzu, um Notausgänge im Review sichtbar zu halten statt verborgen.
Teams mit einer wachsenden Codebasis profitieren auch davon, die Strict-Abdeckung explizit zu verfolgen. Ein kleines Skript, das die in der strikten Konfiguration enthaltenen Dateien zählt und den Prozentsatz der Codebasis unter strikter Prüfung meldet, lässt sich zu einem Dashboard hinzufügen. Fortschritt wird sichtbar und motivierend statt abstrakt, was für Migrationen, die mehrere Monate dauern, sehr wichtig ist.
Der Ertrag
Der häufigste Widerstand gegen das Aktivieren des TypeScript Strict Mode ist, dass er die Feature-Arbeit kurzfristig verlangsamt. Nach unserer Erfahrung mit Code-Quality-Audits an Next.js- und Node.js-Codebasen ist der Produktivitätseinbruch real, dauert aber zwei bis sechs Wochen, und Teams erholen sich schnell. Dauerhaft bleibt eine ganze Kategorie null-bezogener Bugs, die nicht mehr in Produktion gelangen, eine Codebasis, in der Refactoring sicherer ist, weil die Typen das Laufzeitverhalten ehrlich beschreiben, und neue Entwickler, die sich einarbeiten können, indem sie die Typen lesen, statt die App auszuführen und auf Fehler zu warten.
Für Teams, die eine Legacy-TypeScript-Codebasis erben, die vor dem Standardwerden von Strict Mode oder Strict Null Checks geschrieben wurde, ist diese Migration eine der wirkungsvollsten verfügbaren Investitionen. Der Code wird sicherer, die Typen werden ehrlich, und die Laufzeitfehler, die Nutzer frustrieren und Support-Kapazität verbrauchen, beginnen zu verschwinden. Wenn dein Team diesen Umstieg erwägt oder mit einer Codebasis ringt, in der Typen und Laufzeit auseinandergedriftet sind, helfen wir europäischen Teams, den Strict Mode auf produktiven Next.js- und Node.js-Anwendungen einzuführen, ohne die Feature-Entwicklung einzufrieren. Kontaktiere uns unter hello@wolf-tech.io oder besuche wolf-tech.io für eine kostenlose Beratung.
