React Hook Form mit Zod oder Yup: Schema-Validierung im Vergleich
AI generated
{ }
React · Forms · TypeScript
React Hook Form: Zod oder Yup fuer die Schema-Validierung?
Typinferenz, API-Design und Migrationspfade im direkten Vergleich

Wer Formulare in React 19 mit React Hook Form baut, kommt an einer Schema-Validierungsbibliothek kaum vorbei. Zod und Yup loesen dasselbe Problem mit unterschiedlichen Prioritaeten: TypeScript-first versus etablierte JavaScript-API. Dieser Artikel vergleicht beide entlang von Typinferenz, Syntax, verschachtelten Strukturen und Migrationsaufwand.

15 Min. Lesezeit React Hook Form Zod Yup TypeScript Formulare

1. Warum Schema-Validierung in Formularen zaehlt

React Hook Form regelt Zustand und Rendering von Formularen, ueberlaesst die inhaltliche Pruefung der Eingaben aber bewusst externen Bibliotheken. Diese Trennung hat sich bewaehrt, weil Validierungsregeln oft komplexer sind als reine Pflichtfeld-Checks: Mindestlaengen, Formate, Abhaengigkeiten zwischen Feldern, asynchrone Pruefungen gegen ein Backend. Ein Schema buendelt all diese Regeln an einer Stelle, lesbar und wiederverwendbar, statt sie ueber verstreute validate-Funktionen im Formular zu verteilen.

Zod und Yup sind die beiden verbreitetsten Kandidaten fuer diese Aufgabe im React-Oekosystem. Beide bringen einen offiziellen Resolver fuer React Hook Form mit, beide beschreiben Formularstruktur deklarativ als Objektschema. Der Unterschied liegt tiefer: Zod wurde von Anfang an fuer TypeScript entworfen und leitet Typen direkt aus dem Schema ab, Yup stammt aus der reinen JavaScript-Welt und hat Typisierung nachtraeglich ergaenzt. Diese Herkunft praegt bis heute API, Fehlerverhalten und Ergonomie beider Bibliotheken.

2. Grundaufbau: zodResolver und yupResolver im Vergleich

Beide Resolver folgen demselben Muster: Sie werden ueber die resolver-Option an useForm uebergeben und uebersetzen das jeweilige Schema in das interne Fehlerformat von React Hook Form. Der Rest des Formulars, also register, handleSubmit und die Fehleranzeige ueber formState.errors, bleibt in beiden Faellen identisch. Das macht den Umstieg von einer Bibliothek zur anderen auf Formularebene erstaunlich unaufwendig, solange nur das Schema selbst ausgetauscht wird.

Der Unterschied zeigt sich erst beim Schema-Import und beim Typ, den man an useForm uebergibt. Bei Zod reicht ein einziger Typ-Export ueber z.infer, bei Yup wird meist ein separater InferType-Aufruf benoetigt. Im folgenden Beispiel steht die Zod-Variante, weil sie den knapperen Code produziert; die Yup-Variante folgt in den naechsten Abschnitten mit den entsprechenden Anpassungen.


import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const loginSchema = z.object({
  email: z.string().email("Ungueltige E-Mail-Adresse"),
  password: z.string().min(8, "Mindestens 8 Zeichen"),
});

type LoginFormValues = z.infer<typeof loginSchema>;

function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<LoginFormValues>({
    resolver: zodResolver(loginSchema),
  });

  const onSubmit = (values: LoginFormValues) => {
    console.log(values);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email")} />
      {errors.email && <span>{errors.email.message}</span>}
      <input type="password" {...register("password")} />
      {errors.password && <span>{errors.password.message}</span>}
      <button type="submit">Anmelden</button>
    </form>
  );
}

3. Zods Typinferenz: z.infer als Single Source of Truth

Der groesste praktische Vorteil von Zod liegt in z.infer. Das Schema ist gleichzeitig die Laufzeitvalidierung und die Quelle fuer den TypeScript-Typ, beides bleibt automatisch synchron. Aendert sich ein Feld im Schema, etwa weil ein optionales Feld verpflichtend wird, schlaegt der TypeScript-Compiler an jeder Stelle Alarm, die noch von der alten Struktur ausgeht. Diese Kopplung verhindert eine ganze Klasse von Fehlern, bei denen Validierungsregeln und Typdefinition auseinanderlaufen.

Zod bildet dabei auch komplexere TypeScript-Konstrukte sauber ab: Unions, Literale, optionale und nullable Felder, sogar rekursive Typen ueber z.lazy. Fuer ein Formular mit bedingten Feldern, etwa einem Adressformular, das je nach Land unterschiedliche Pflichtfelder verlangt, laesst sich das ueber z.discriminatedUnion abbilden, und der abgeleitete Typ spiegelt genau diese Fallunterscheidung wider, inklusive korrekter Typeinengung im aufrufenden Code.

4. Yups InferType: solide, aber mit Einschraenkungen

Yup bietet mit InferType einen vergleichbaren Mechanismus, der in den meisten Alltagsfaellen ordentlich funktioniert. Fuer flache Objektschemata mit string, number und boolean liefert Yup exakt den erwarteten Typ, inklusive optionaler Felder ueber .optional() oder .notRequired(). Fuer einfache Login- oder Kontaktformulare ist der Unterschied zu Zod in der Praxis kaum spuerbar, weil beide Bibliotheken dieselbe Grundstruktur korrekt typisieren.

Bei komplexeren Faellen zeigen sich jedoch Luecken: Bedingte Validierung ueber .when() laesst sich in Yup zwar zur Laufzeit sauber ausdruecken, der abgeleitete TypeScript-Typ bildet diese Bedingtheit aber nicht immer praezise ab, weil Yups Typsystem nachtraeglich ueber die JavaScript-API gelegt wurde statt von Grund auf typgetrieben zu sein. In solchen Faellen greifen Teams haeufig zu manuellen Typ-Overrides, was den eigentlichen Vorteil einer einzigen Quelle der Wahrheit wieder aufweicht.

5. API-Design: Chaining, Fehlermeldungen und Lesbarkeit

Beide Bibliotheken setzen auf method chaining, unterscheiden sich aber in Details, die im Alltag auffallen. Zod uebergibt Fehlermeldungen meist als letztes Argument einer Methode, etwa z.string().min(8, "Mindestens 8 Zeichen"), waehrend Yup eine eigene .required("Pflichtfeld") und .min(8, "Mindestens 8 Zeichen") Kette pflegt, bei der required explizit gesetzt werden muss, sonst gilt ein Feld standardmaessig als optional. Dieser Unterschied im Default-Verhalten ist eine haeufige Fehlerquelle bei der Migration in beide Richtungen.

Zod ist bei required strikter: Ein Feld ist standardmaessig verpflichtend, optional muss explizit ueber .optional() markiert werden. Das entspricht eher der Erwartungshaltung an ein typsicheres Schema, kann aber bei der Migration von Yup-Schemata zu unerwarteten Pflichtfeldern fuehren, wenn bestehende .required()-Aufrufe schlicht weggelassen werden. Ein sorgfaeltiger Feld-fuer-Feld-Abgleich ist bei groesseren Formularen daher unverzichtbar.

6. Verschachtelte Objekte und Arrays validieren

Formulare mit dynamischen Listen, etwa mehreren Kontaktpersonen oder Rechnungspositionen, verlangen verschachtelte Schemata mit Arrays von Objekten. Zod bildet das ueber z.array(z.object({...})) ab und kombiniert sich gut mit React Hook Forms useFieldArray, weil beide auf denselben Pfadnotationen fuer Feldnamen basieren. Fehler auf tief verschachtelten Feldern landen exakt an der erwarteten Stelle in formState.errors, ohne zusaetzliche Anpassung.

Yup verwendet dieselbe Grundidee mit yup.array().of(yup.object({...})), verhaelt sich in der Praxis aber sensibler bei fehlenden Standardwerten fuer verschachtelte Objekte; ohne explizites .default() kann InferType an dieser Stelle undefined statt eines Objekttyps liefern. Wer viel mit dynamischen, verschachtelten Formularen arbeitet, profitiert insgesamt staerker von Zods konsequenterer Typableitung, gerade wenn useFieldArray im Spiel ist.


import { z } from "zod";
import { useFieldArray, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

const invoiceSchema = z.object({
  customer: z.string().min(1, "Kunde erforderlich"),
  items: z
    .array(
      z.object({
        description: z.string().min(1, "Beschreibung erforderlich"),
        amount: z.number().positive("Betrag muss positiv sein"),
      })
    )
    .min(1, "Mindestens eine Position erforderlich"),
});

type InvoiceFormValues = z.infer<typeof invoiceSchema>;

function InvoiceForm() {
  const { control, register, handleSubmit } = useForm<InvoiceFormValues>({
    resolver: zodResolver(invoiceSchema),
    defaultValues: { customer: "", items: [{ description: "", amount: 0 }] },
  });
  const { fields, append, remove } = useFieldArray({ control, name: "items" });

  return (
    <form onSubmit={handleSubmit((values) => console.log(values))}>
      <input {...register("customer")} />
      {fields.map((field, index) => (
        <div key={field.id}>
          <input {...register(`items.${index}.description`)} />
          <input type="number" {...register(`items.${index}.amount`, { valueAsNumber: true })} />
          <button type="button" onClick={() => remove(index)}>Entfernen</button>
        </div>
      ))}
      <button type="button" onClick={() => append({ description: "", amount: 0 })}>Position hinzufuegen</button>
    </form>
  );
}

7. Eigene Validierungsregeln: refine versus test

Sobald Standardregeln nicht ausreichen, etwa bei einem Passwort, das Sonderzeichen erfordert, oder einem Passwort-Bestaetigungsfeld, das mit dem ersten uebereinstimmen muss, brauchen beide Bibliotheken eine Erweiterung. Zod bietet dafuer .refine() auf Feld- oder Objektebene, das eine Praedikatsfunktion und eine Fehlermeldung entgegennimmt und sich beliebig mit .superRefine() fuer mehrere Fehlerpfade gleichzeitig kombinieren laesst.

Yup verwendet stattdessen .test() mit Name, Meldung und Testfunktion, konzeptionell vergleichbar, syntaktisch aber etwas umstaendlicher, weil jeder Test einen eindeutigen Namen benoetigt, um Kollisionen zwischen mehreren Tests auf demselben Feld zu vermeiden. Fuer felduebergreifende Pruefungen wie den Passwortabgleich bietet Yup ausserdem .when(), das bedingte Schemata basierend auf anderen Feldwerten erzeugt, ein Konzept, das es in Zod in dieser Form nicht gibt und dort ueber .refine() auf Objektebene nachgebaut werden muss.

8. Migrationsaufwand von Yup zu Zod

Eine Migration bestehender Yup-Formulare zu Zod laesst sich in der Praxis gut Formular fuer Formular durchfuehren, weil React Hook Form die Trennung zwischen UI und Validierung ohnehin erzwingt. Der pragmatische Weg beginnt bei den am haeufigsten geaenderten Formularen, wo der Typinferenz-Vorteil sofort spuerbar wird, und laesst stabile, selten angefasste Formulare zunaechst unangetastet in Yup, solange beide Resolver parallel im Projekt existieren duerfen.

Der eigentliche Aufwand steckt selten in den einfachen Feldern, sondern in den .when()-Konstrukten und benutzerdefinierten Tests, die felduebergreifende Logik abbilden; diese muessen sorgfaeltig auf .refine() beziehungsweise .superRefine() uebertragen und mit denselben Testfaellen erneut abgesichert werden. Ein guter Indikator fuer den Migrationsaufwand eines Formulars ist daher weniger seine Feldanzahl als die Anzahl der bedingten Validierungsregeln.

9. Bundle-Groesse, Performance und Empfehlung

Fuer die meisten Projekte ist der Laufzeitunterschied zwischen Zod und Yup bei typischen Formulargroessen kaum messbar, beide validieren synchron in Millisekundenbruchteilen. Relevanter ist die Bundle-Groesse: Zod ist in aktuellen Versionen kompakter und tree-shakebar, weil es konsequent auf funktionale Bausteine statt einer monolithischen Klassenhierarchie setzt, was sich besonders in Projekten mit vielen kleinen Formularen summiert.

Fuer neue React-19-Projekte mit TypeScript ist Zod inzwischen die naheliegendere Wahl, weil die durchgaengige Typinferenz Entwicklungsfehler frueher abfaengt und der Oekosystem-Support, etwa fuer tRPC oder OpenAPI-Generierung, breiter geworden ist. Bestehende Yup-Projekte ohne akuten Schmerzpunkt muessen deshalb aber nicht zwingend migrieren, solange Team und Codebasis mit der etablierten API produktiv arbeiten.

Kriterium Zod Yup Praxisrelevanz
TypeScript-Inferenz Nativ ueber z.infer, sehr praezise InferType, Luecken bei .when() Hoch bei komplexen Formularen
Pflichtfeld-Default Standardmaessig required Standardmaessig optional Haeufige Migrationsfalle
Bedingte Validierung refine()/superRefine() auf Objektebene .when() feldbasiert Je nach Formularlogik unterschiedlich elegant
Bundle-Groesse Kompakt, tree-shakebar Etwas groesser, Klassen-basiert Relevant bei vielen kleinen Formularen
Oekosystem tRPC, OpenAPI-Tools wachsend Laenger etabliert, viele Altprojekte Team-Erfahrung zaehlt mit

Mironsoft

React-Architektur, Performance und Magento-Frontend-Integration

React-Frontends, die schnell bleiben statt mit jedem Feature langsamer zu werden?

Wir prüfen bestehende React-Anwendungen auf unnötige Re-Renders, aufgeblähte Bundles und fragile State-Verwaltung und bauen daraus ein Frontend, das performant bleibt und sich sauber an Magento oder andere Backends anbindet.

Performance-Audit

Re-Renders, Bundle-Größe und Ladezeiten systematisch messen und beheben.

State-Architektur

Context, Zustand und Server State sauber trennen statt alles in einen Topf zu werfen.

Magento-Integration

GraphQL- oder REST-Anbindung an Magento robust und typsicher aufbauen.

10. Zusammenfassung

Zod vs. Yup in RHF: Das Wichtigste auf einen Blick

Typinferenz

Zod leitet Typen direkt ueber z.infer ab, Yup benoetigt InferType mit Luecken bei bedingter Logik.

Resolver-Wechsel

zodResolver und yupResolver folgen demselben Muster, der Formularcode bleibt weitgehend unveraendert.

Pflichtfelder

Zod-Felder sind standardmaessig required, Yup-Felder standardmaessig optional, haeufigste Migrationsfalle.

Empfehlung

Neue TypeScript-Projekte profitieren von Zod, stabile Yup-Formulare muessen nicht zwingend migriert werden.

11. FAQ: Zod vs. Yup in RHF: Das Wichtigste auf einen Blick

1Kann ich Zod und Yup im selben Projekt gleichzeitig nutzen?
Ja, beide Resolver lassen sich formularweise unabhaengig einsetzen, solange jedes Formular konsistent bei einer Bibliothek bleibt. Ein schrittweiser Umstieg Formular fuer Formular ist die gaengige Migrationsstrategie.
2Warum sind Yup-Felder standardmaessig optional, Zod-Felder aber required?
Die Bibliotheken haben unterschiedliche Design-Philosophien: Yup stammt aus der JavaScript-Welt mit lockeren Defaults, Zod wurde typsicherheitsorientiert entworfen und macht Pflichtfelder zum Standardfall, um versehentlich fehlende Daten frueh sichtbar zu machen.
3Ist zodResolver langsamer als yupResolver?
Bei ueblichen Formulargroessen ist der Laufzeitunterschied vernachlaessigbar, beide validieren synchron innerhalb weniger Millisekunden. Erst bei sehr grossen, tief verschachtelten Schemata mit vielen benutzerdefinierten Regeln koennen kleine Unterschiede messbar werden.
4Wie bilde ich mit Zod ein Formular ab, dessen Pflichtfelder vom Land abhaengen?
Ueber z.discriminatedUnion laesst sich ein Union-Typ mit einem Unterscheidungsfeld wie country definieren, wobei jeder Zweig eigene Pflichtfelder besitzt. Der abgeleitete TypeScript-Typ engt sich im Code automatisch entsprechend dem gewaehlten Zweig ein.
5Unterstuetzt Yup asynchrone Validierung gegen ein Backend?
Ja, sowohl Yup als auch Zod unterstuetzen asynchrone Pruefungen ueber test-Funktionen beziehungsweise refine mit Promise-Rueckgabe, beide Resolver von React Hook Form warten korrekt auf das Ergebnis, bevor das Formular als gueltig gilt.
6Was passiert bei der Migration mit bestehenden Fehlermeldungstexten?
Fehlermeldungstexte lassen sich in beiden Bibliotheken meist unveraendert uebernehmen, da sie als String-Argument an die jeweilige Regel uebergeben werden. Nur die Syntax der Regel selbst muss angepasst werden, der sichtbare Text fuer Nutzer bleibt gleich.
7Lohnt sich Zod auch fuer kleine, einfache Formulare?
Fuer ein einzelnes Kontaktformular ist der Unterschied gering, beide Bibliotheken erledigen die Aufgabe zuverlaessig. Der Vorteil von Zod zeigt sich vor allem, wenn mehrere Formulare Typen teilen oder das Schema mit Backend-Validierung wiederverwendet wird.
8Kann ich ein Zod-Schema auch ausserhalb von React Hook Form verwenden?
Ja, Zod-Schemata sind unabhaengig von React Hook Form nutzbar, etwa zur Validierung von API-Antworten oder Umgebungsvariablen. Das macht Zod-Schemata oft zur zentralen Typquelle fuer ein ganzes Projekt, nicht nur fuer Formulare.
9Wie gehe ich mit verschachtelten Fehlermeldungen bei useFieldArray um?
React Hook Form legt Fehler bei Arrays unter demselben Pfad wie die Feldnamen ab, etwa errors.items[0].amount. Sowohl zodResolver als auch yupResolver respektieren diese Struktur automatisch, solange das Schema dieselbe Verschachtelung abbildet.
10Gibt es eine dritte Alternative neben Zod und Yup?
Ja, etwa Valibot als besonders leichtgewichtige, modulare Alternative oder ArkType mit einer noch staerker typgetriebenen Syntax. Fuer die meisten React-19-Projekte bleiben Zod und Yup jedoch die ausgereiftesten und am besten dokumentierten Optionen mit offiziellem React-Hook-Form-Resolver.