Middleware

Was ist Serverfunktions-Middleware?

Middleware ermöglicht es Ihnen, das Verhalten von Serverfunktionen, die mit createServerFn erstellt wurden, mit Dingen wie gemeinsam genutzter Validierung, Kontext und vielem mehr anzupassen. Middleware kann sogar von anderer Middleware abhängen, um eine Kette von Operationen zu erstellen, die hierarchisch und geordnet ausgeführt werden.

Welche Arten von Dingen kann ich mit Middleware in meinen Serverfunktionen tun?

  • Authentifizierung: Überprüfen Sie die Identität eines Benutzers, bevor Sie eine Serverfunktion ausführen.
  • Autorisierung: Prüfen Sie, ob ein Benutzer die erforderlichen Berechtigungen hat, um eine Serverfunktion auszuführen.
  • Protokollierung: Protokollieren Sie Anfragen, Antworten und Fehler.
  • Beobachtbarkeit: Sammeln Sie Metriken, Spuren und Protokolle.
  • Kontext bereitstellen: Hängen Sie Daten an das Anfrageobjekt an, um sie in anderen Middleware- oder Serverfunktionen zu verwenden.
  • Fehlerbehandlung: Behandeln Sie Fehler konsistent.
  • Und vieles mehr! Die Möglichkeiten liegen bei Ihnen!

Middleware für Serverfunktionen definieren

Middleware wird mit der Funktion createMiddleware definiert. Diese Funktion gibt ein Middleware-Objekt zurück, das verwendet werden kann, um die Middleware weiter mit Methoden wie middleware, validator, server und client anzupassen.

tsx
import { createMiddleware } from '@tanstack/solid-start'

const loggingMiddleware = createMiddleware({ type: 'function' }).server(
  async ({ next, data }) => {
    console.log('Request received:', data)
    const result = await next()
    console.log('Response processed:', result)
    return result
  },
)
import { createMiddleware } from '@tanstack/solid-start'

const loggingMiddleware = createMiddleware({ type: 'function' }).server(
  async ({ next, data }) => {
    console.log('Request received:', data)
    const result = await next()
    console.log('Response processed:', result)
    return result
  },
)

Middleware in Ihren Serverfunktionen verwenden

Sobald Sie Ihre Middleware definiert haben, können Sie sie in Kombination mit der Funktion createServerFn verwenden, um das Verhalten Ihrer Serverfunktionen anzupassen.

tsx
import { createServerFn } from '@tanstack/solid-start'
import { loggingMiddleware } from './middleware'

const fn = createServerFn()
  .middleware([loggingMiddleware])
  .handler(async () => {
    // ...
  })
import { createServerFn } from '@tanstack/solid-start'
import { loggingMiddleware } from './middleware'

const fn = createServerFn()
  .middleware([loggingMiddleware])
  .handler(async () => {
    // ...
  })

Middleware-Methoden

Mehrere Methoden stehen zur Verfügung, um die Middleware anzupassen. Wenn Sie TypeScript verwenden (hoffentlich), wird die Reihenfolge dieser Methoden durch das Typsystem erzwungen, um maximale Inferenz und Typsicherheit zu gewährleisten.

  • middleware: Fügt der Kette eine Middleware hinzu.
  • validator: Modifiziert das Datenobjekt, bevor es an diese Middleware und alle verschachtelten Middleware weitergegeben wird.
  • server: Definiert serverseitige Logik, die die Middleware vor jeder verschachtelten Middleware und letztendlich einer Serverfunktion ausführt, und stellt das Ergebnis der nächsten Middleware zur Verfügung.
  • client: Definiert clientseitige Logik, die die Middleware vor jeder verschachtelten Middleware und letztendlich der clientseitigen RPC-Funktion (oder der serverseitigen Funktion) ausführt, und stellt das Ergebnis der nächsten Middleware zur Verfügung.

Die Methode middleware

Die Methode middleware wird verwendet, um Abhängigkeits-Middleware an die Kette anzuhängen, die vor der aktuellen Middleware ausgeführt wird. Rufen Sie einfach die Methode middleware mit einem Array von Middleware-Objekten auf.

tsx
import { createMiddleware } from '@tanstack/solid-start'

const loggingMiddleware = createMiddleware({ type: 'function' }).middleware([
  authMiddleware,
  loggingMiddleware,
])
import { createMiddleware } from '@tanstack/solid-start'

const loggingMiddleware = createMiddleware({ type: 'function' }).middleware([
  authMiddleware,
  loggingMiddleware,
])

Typsichere Kontext- und Payload-Validierung werden ebenfalls von übergeordneten Middleware geerbt!

Die Methode validator

Die Methode validator wird verwendet, um das Datenobjekt zu modifizieren, bevor es an diese Middleware, verschachtelte Middleware und letztendlich die Serverfunktion weitergegeben wird. Diese Methode sollte eine Funktion empfangen, die das Datenobjekt entgegennimmt und ein validiertes (und optional modifiziertes) Datenobjekt zurückgibt. Es ist üblich, eine Validierungsbibliothek wie zod dafür zu verwenden. Hier ist ein Beispiel

tsx
import { createMiddleware } from '@tanstack/solid-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'

const mySchema = z.object({
  workspaceId: z.string(),
})

const workspaceMiddleware = createMiddleware({ type: 'function' })
  .validator(zodValidator(mySchema))
  .server(({ next, data }) => {
    console.log('Workspace ID:', data.workspaceId)
    return next()
  })
import { createMiddleware } from '@tanstack/solid-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'

const mySchema = z.object({
  workspaceId: z.string(),
})

const workspaceMiddleware = createMiddleware({ type: 'function' })
  .validator(zodValidator(mySchema))
  .server(({ next, data }) => {
    console.log('Workspace ID:', data.workspaceId)
    return next()
  })

Die Methode server

Die Methode server wird verwendet, um serverseitige Logik zu definieren, die die Middleware sowohl vor als auch nach jeder verschachtelten Middleware und letztendlich einer Serverfunktion ausführt. Diese Methode empfängt ein Objekt mit den folgenden Eigenschaften

  • next: Eine Funktion, die beim Aufruf die nächste Middleware in der Kette ausführt.
  • data: Das Datenobjekt, das an die Serverfunktion übergeben wurde.
  • context: Ein Objekt, das Daten von übergeordneten Middleware speichert. Es kann mit zusätzlichen Daten erweitert werden, die an untergeordnete Middleware weitergegeben werden.

Das erforderliche Ergebnis von next zurückgeben

Die Funktion next wird verwendet, um die nächste Middleware in der Kette auszuführen. Sie müssen das Ergebnis der Ihnen übergebenen Funktion next awaiten und zurückgeben (oder direkt zurückgeben), damit die Kette weiter ausgeführt werden kann.

tsx
import { createMiddleware } from '@tanstack/solid-start'

const loggingMiddleware = createMiddleware({ type: 'function' }).server(
  async ({ next }) => {
    console.log('Request received')
    const result = await next()
    console.log('Response processed')
    return result
  },
)
import { createMiddleware } from '@tanstack/solid-start'

const loggingMiddleware = createMiddleware({ type: 'function' }).server(
  async ({ next }) => {
    console.log('Request received')
    const result = await next()
    console.log('Response processed')
    return result
  },
)

Kontext über next an die nächste Middleware weitergeben

Die Funktion next kann optional mit einem Objekt aufgerufen werden, das eine context-Eigenschaft mit einem Objektwert hat. Welche Eigenschaften Sie an diesen context-Wert übergeben, wird mit dem übergeordneten context zusammengeführt und an die nächste Middleware weitergegeben.

tsx
import { createMiddleware } from '@tanstack/solid-start'

const awesomeMiddleware = createMiddleware({ type: 'function' }).server(
  ({ next }) => {
    return next({
      context: {
        isAwesome: Math.random() > 0.5,
      },
    })
  },
)

const loggingMiddleware = createMiddleware({ type: 'function' })
  .middleware([awesomeMiddleware])
  .server(async ({ next, context }) => {
    console.log('Is awesome?', context.isAwesome)
    return next()
  })
import { createMiddleware } from '@tanstack/solid-start'

const awesomeMiddleware = createMiddleware({ type: 'function' }).server(
  ({ next }) => {
    return next({
      context: {
        isAwesome: Math.random() > 0.5,
      },
    })
  },
)

const loggingMiddleware = createMiddleware({ type: 'function' })
  .middleware([awesomeMiddleware])
  .server(async ({ next, context }) => {
    console.log('Is awesome?', context.isAwesome)
    return next()
  })

Clientseitige Logik

Obwohl Serverfunktionen hauptsächlich serverseitig gebundene Operationen sind, gibt es immer noch viele clientseitige Logiken, die die ausgehende RPC-Anfrage vom Client umgeben. Das bedeutet, dass wir auch clientseitige Logiken in Middleware definieren können, die auf der Clientseite um jede verschachtelte Middleware und letztendlich die RPC-Funktion und ihre Antwort an den Client ausgeführt wird.

Clientseitige Payload-Validierung

Standardmäßig wird die Middleware-Validierung nur auf dem Server durchgeführt, um die Größe des Client-Bundles klein zu halten. Sie können jedoch auch Daten auf der Clientseite validieren, indem Sie die Option validateClient: true an die Funktion createMiddleware übergeben. Dies bewirkt, dass die Daten auf der Clientseite validiert werden, bevor sie an den Server gesendet werden, was potenziell einen Hin- und Rückweg spart.

Warum kann ich kein anderes Validierungsschema für den Client übergeben?

Das clientseitige Validierungsschema wird vom serverseitigen Schema abgeleitet. Dies liegt daran, dass das clientseitige Validierungsschema verwendet wird, um die Daten zu validieren, bevor sie an den Server gesendet werden. Wenn das clientseitige Schema vom serverseitigen Schema abweicht, würde der Server Daten erhalten, die er nicht erwartet hat, was zu unerwartetem Verhalten führen könnte.

tsx
import { createMiddleware } from '@tanstack/solid-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'

const workspaceMiddleware = createMiddleware({ validateClient: true })
  .validator(zodValidator(mySchema))
  .server(({ next, data }) => {
    console.log('Workspace ID:', data.workspaceId)
    return next()
  })
import { createMiddleware } from '@tanstack/solid-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'

const workspaceMiddleware = createMiddleware({ validateClient: true })
  .validator(zodValidator(mySchema))
  .server(({ next, data }) => {
    console.log('Workspace ID:', data.workspaceId)
    return next()
  })

Die Methode client

Client-Middleware-Logik wird mit der Methode client auf einem Middleware-Objekt definiert. Diese Methode wird verwendet, um clientseitige Logik zu definieren, die die Middleware sowohl vor als auch nach jeder verschachtelten Middleware und letztendlich der clientseitigen RPC-Funktion (oder der serverseitigen Funktion, wenn Sie SSR durchführen oder diese Funktion von einer anderen Serverfunktion aus aufrufen) ausführt.

Clientseitige Middleware-Logik teilt sich einen Großteil derselben API wie Logik, die mit der Methode server erstellt wurde, wird aber auf der Clientseite ausgeführt. Dazu gehören

  • Die Anforderung, dass die Funktion next aufgerufen werden muss, um die Kette fortzusetzen.
  • Die Möglichkeit, Kontext über die Funktion next an die nächste Client-Middleware weiterzugeben.
  • Die Möglichkeit, das Datenobjekt zu modifizieren, bevor es an die nächste Client-Middleware weitergegeben wird.

Ähnlich wie die Funktion server empfängt sie auch ein Objekt mit den folgenden Eigenschaften

  • next: Eine Funktion, die beim Aufruf die nächste Client-Middleware in der Kette ausführt.
  • data: Das Datenobjekt, das an die Clientfunktion übergeben wurde.
  • context: Ein Objekt, das Daten von übergeordneten Middleware speichert. Es kann mit zusätzlichen Daten erweitert werden, die an untergeordnete Middleware weitergegeben werden.
tsx
const loggingMiddleware = createMiddleware({ type: 'function' }).client(
  async ({ next }) => {
    console.log('Request sent')
    const result = await next()
    console.log('Response received')
    return result
  },
)
const loggingMiddleware = createMiddleware({ type: 'function' }).client(
  async ({ next }) => {
    console.log('Request sent')
    const result = await next()
    console.log('Response received')
    return result
  },
)

Client-Kontext an den Server senden

Client-Kontext wird standardmäßig NICHT an den Server gesendet, da dies unbeabsichtigt große Payloads an den Server senden könnte. Wenn Sie Client-Kontext an den Server senden müssen, müssen Sie die Funktion next mit einer Eigenschaft sendContext und einem Objekt aufrufen, um Daten an den Server zu übertragen. Alle Eigenschaften, die an sendContext übergeben werden, werden zusammen mit den Daten serialisiert und an den Server gesendet und sind im normalen Kontextobjekt jeder verschachtelten Server-Middleware verfügbar.

tsx
const requestLogger = createMiddleware({ type: 'function' })
  .client(async ({ next, context }) => {
    return next({
      sendContext: {
        // Send the workspace ID to the server
        workspaceId: context.workspaceId,
      },
    })
  })
  .server(async ({ next, data, context }) => {
    // Woah! We have the workspace ID from the client!
    console.log('Workspace ID:', context.workspaceId)
    return next()
  })
const requestLogger = createMiddleware({ type: 'function' })
  .client(async ({ next, context }) => {
    return next({
      sendContext: {
        // Send the workspace ID to the server
        workspaceId: context.workspaceId,
      },
    })
  })
  .server(async ({ next, data, context }) => {
    // Woah! We have the workspace ID from the client!
    console.log('Workspace ID:', context.workspaceId)
    return next()
  })

Sicherheit von Client-gesendetem Kontext

Sie haben vielleicht bemerkt, dass im obigen Beispiel, während clientseitig gesendeter Kontext typsicher ist, keine Laufzeitvalidierung erforderlich ist. Wenn Sie dynamische, benutzergenerierte Daten über den Kontext senden, könnte dies ein Sicherheitsproblem darstellen. Wenn Sie dynamische Daten vom Client über den Kontext an den Server senden, sollten Sie sie in der serverseitigen Middleware validieren, bevor Sie sie verwenden. Hier ist ein Beispiel

tsx
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'

const requestLogger = createMiddleware({ type: 'function' })
  .client(async ({ next, context }) => {
    return next({
      sendContext: {
        workspaceId: context.workspaceId,
      },
    })
  })
  .server(async ({ next, data, context }) => {
    // Validate the workspace ID before using it
    const workspaceId = zodValidator(z.number()).parse(context.workspaceId)
    console.log('Workspace ID:', workspaceId)
    return next()
  })
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'

const requestLogger = createMiddleware({ type: 'function' })
  .client(async ({ next, context }) => {
    return next({
      sendContext: {
        workspaceId: context.workspaceId,
      },
    })
  })
  .server(async ({ next, data, context }) => {
    // Validate the workspace ID before using it
    const workspaceId = zodValidator(z.number()).parse(context.workspaceId)
    console.log('Workspace ID:', workspaceId)
    return next()
  })

Server-Kontext an den Client senden

Ähnlich wie beim Senden von Client-Kontext an den Server können Sie auch Server-Kontext an den Client senden, indem Sie die Funktion next mit einer Eigenschaft sendContext und einem Objekt aufrufen, um Daten an den Client zu übertragen. Alle Eigenschaften, die an sendContext übergeben werden, werden zusammen mit der Antwort serialisiert und an den Client gesendet und sind im normalen Kontextobjekt jeder verschachtelten Client-Middleware verfügbar. Das zurückgegebene Objekt des Aufrufs von next in client enthält den vom Server an den Client gesendeten Kontext und ist typsicher. Middleware kann den vom Server an den Client gesendeten Kontext aus früheren Middleware, die von der Funktion middleware verknüpft wurden, ableiten.

Warnung

Der Rückgabetyp von next in client kann nur von Middleware abgeleitet werden, die in der aktuellen Middleware-Kette bekannt ist. Daher ist der genaueste Rückgabetyp von next in Middleware am Ende der Middleware-Kette.

tsx
const serverTimer = createMiddleware({ type: 'function' }).server(
  async ({ next }) => {
    return next({
      sendContext: {
        // Send the current time to the client
        timeFromServer: new Date(),
      },
    })
  },
)

const requestLogger = createMiddleware({ type: 'function' })
  .middleware([serverTimer])
  .client(async ({ next }) => {
    const result = await next()
    // Woah! We have the time from the server!
    console.log('Time from the server:', result.context.timeFromServer)

    return result
  })
const serverTimer = createMiddleware({ type: 'function' }).server(
  async ({ next }) => {
    return next({
      sendContext: {
        // Send the current time to the client
        timeFromServer: new Date(),
      },
    })
  },
)

const requestLogger = createMiddleware({ type: 'function' })
  .middleware([serverTimer])
  .client(async ({ next }) => {
    const result = await next()
    // Woah! We have the time from the server!
    console.log('Time from the server:', result.context.timeFromServer)

    return result
  })

Serverantwort lesen/modifizieren

Middleware, die die Methode server verwendet, wird im selben Kontext wie Serverfunktionen ausgeführt, sodass Sie die genauen Serverfunktions-Kontext-Dienstprogramme befolgen können, um Anforderungsheader, Statuscodes usw. zu lesen und zu modifizieren.

Clientanforderung modifizieren

Middleware, die die Methode client verwendet, wird in einem völlig anderen clientseitigen Kontext als Serverfunktionen ausgeführt, sodass Sie nicht dieselben Dienstprogramme zum Lesen und Modifizieren der Anfrage verwenden können. Sie können die Anfrage jedoch weiterhin modifizieren, indem Sie zusätzliche Eigenschaften zurückgeben, wenn Sie die Funktion next aufrufen. Derzeit unterstützte Eigenschaften sind

  • headers: Ein Objekt, das Header enthält, die der Anforderung hinzugefügt werden sollen.

Hier ist ein Beispiel für das Hinzufügen eines Authorization-Headers zu jeder Anforderung, die diese Middleware verwendet

tsx
import { getToken } from 'my-auth-library'

const authMiddleware = createMiddleware({ type: 'function' }).client(
  async ({ next }) => {
    return next({
      headers: {
        Authorization: `Bearer ${getToken()}`,
      },
    })
  },
)
import { getToken } from 'my-auth-library'

const authMiddleware = createMiddleware({ type: 'function' }).client(
  async ({ next }) => {
    return next({
      headers: {
        Authorization: `Bearer ${getToken()}`,
      },
    })
  },
)

Middleware verwenden

Middleware kann auf zwei Arten verwendet werden

  • Globale Middleware: Middleware, die für jede Anfrage ausgeführt werden soll.
  • Serverfunktions-Middleware: Middleware, die für eine bestimmte Serverfunktion ausgeführt werden soll.

Globale Middleware

Globale Middleware wird automatisch für jede Serverfunktion in Ihrer Anwendung ausgeführt. Dies ist nützlich für Funktionen wie Authentifizierung, Protokollierung und Überwachung, die für alle Anfragen gelten sollen.

Um globale Middleware zu verwenden, erstellen Sie eine Datei global-middleware.ts in Ihrem Projekt (typischerweise unter app/global-middleware.ts). Diese Datei wird in Client- und Serverumgebungen ausgeführt und hier registrieren Sie globale Middleware.

Hier erfahren Sie, wie Sie globale Middleware registrieren

tsx
// app/global-middleware.ts
import { registerGlobalMiddleware } from '@tanstack/solid-start'
import { authMiddleware } from './middleware'

registerGlobalMiddleware({
  middleware: [authMiddleware],
})
// app/global-middleware.ts
import { registerGlobalMiddleware } from '@tanstack/solid-start'
import { authMiddleware } from './middleware'

registerGlobalMiddleware({
  middleware: [authMiddleware],
})

Typsicherheit für globale Middleware

Typen von globaler Middleware sind inhärent von Serverfunktionen selbst getrennt. Das bedeutet, dass, wenn eine globale Middleware zusätzlichen Kontext für Serverfunktionen oder andere serverfunktionsspezifische Middleware bereitstellt, die Typen nicht automatisch an die Serverfunktion oder andere serverfunktionsspezifische Middleware weitergegeben werden.

tsx
// app/global-middleware.ts
registerGlobalMiddleware({
  middleware: [authMiddleware],
})
// app/global-middleware.ts
registerGlobalMiddleware({
  middleware: [authMiddleware],
})
tsx
// authMiddleware.ts
const authMiddleware = createMiddleware({ type: 'function' }).server(
  ({ next, context }) => {
    console.log(context.user) // <-- This will not be typed!
    // ...
  },
)
// authMiddleware.ts
const authMiddleware = createMiddleware({ type: 'function' }).server(
  ({ next, context }) => {
    console.log(context.user) // <-- This will not be typed!
    // ...
  },
)

Um dies zu lösen, fügen Sie die globale Middleware, auf die Sie verweisen möchten, dem Middleware-Array der Serverfunktion hinzu. Die globale Middleware wird zu einem einzigen Eintrag dedupliziert (die globale Instanz), und Ihre Serverfunktion erhält die korrekten Typen.

Hier ist ein Beispiel, wie das funktioniert

tsx
import { authMiddleware } from './authMiddleware'

const fn = createServerFn()
  .middleware([authMiddleware])
  .handler(async ({ context }) => {
    console.log(context.user)
    // ...
  })
import { authMiddleware } from './authMiddleware'

const fn = createServerFn()
  .middleware([authMiddleware])
  .handler(async ({ context }) => {
    console.log(context.user)
    // ...
  })

Ausführungsreihenfolge von Middleware

Middleware wird priorisiert ausgeführt, beginnend mit globaler Middleware, gefolgt von Serverfunktions-Middleware. Das folgende Beispiel wird in dieser Reihenfolge protokollieren

  • globalMiddleware1
  • globalMiddleware2
  • a
  • b
  • c
  • d
tsx
const globalMiddleware1 = createMiddleware({ type: 'function' }).server(
  async ({ next }) => {
    console.log('globalMiddleware1')
    return next()
  },
)

const globalMiddleware2 = createMiddleware({ type: 'function' }).server(
  async ({ next }) => {
    console.log('globalMiddleware2')
    return next()
  },
)

registerGlobalMiddleware({
  middleware: [globalMiddleware1, globalMiddleware2],
})

const a = createMiddleware({ type: 'function' }).server(async ({ next }) => {
  console.log('a')
  return next()
})

const b = createMiddleware({ type: 'function' })
  .middleware([a])
  .server(async ({ next }) => {
    console.log('b')
    return next()
  })

const c = createMiddleware({ type: 'function' })
  .middleware()
  .server(async ({ next }) => {
    console.log('c')
    return next()
  })

const d = createMiddleware({ type: 'function' })
  .middleware([b, c])
  .server(async () => {
    console.log('d')
  })

const fn = createServerFn()
  .middleware([d])
  .server(async () => {
    console.log('fn')
  })
const globalMiddleware1 = createMiddleware({ type: 'function' }).server(
  async ({ next }) => {
    console.log('globalMiddleware1')
    return next()
  },
)

const globalMiddleware2 = createMiddleware({ type: 'function' }).server(
  async ({ next }) => {
    console.log('globalMiddleware2')
    return next()
  },
)

registerGlobalMiddleware({
  middleware: [globalMiddleware1, globalMiddleware2],
})

const a = createMiddleware({ type: 'function' }).server(async ({ next }) => {
  console.log('a')
  return next()
})

const b = createMiddleware({ type: 'function' })
  .middleware([a])
  .server(async ({ next }) => {
    console.log('b')
    return next()
  })

const c = createMiddleware({ type: 'function' })
  .middleware()
  .server(async ({ next }) => {
    console.log('c')
    return next()
  })

const d = createMiddleware({ type: 'function' })
  .middleware([b, c])
  .server(async () => {
    console.log('d')
  })

const fn = createServerFn()
  .middleware([d])
  .server(async () => {
    console.log('fn')
  })

Environment Tree Shaking

Die Funktionalität von Middleware wird je nach Umgebung für jedes produzierte Bundle aus dem Bundle entfernt (tree-shaken).

  • Auf dem Server wird nichts entfernt, sodass der gesamte in der Middleware verwendete Code im Serverbundle enthalten ist.
  • Auf dem Client wird aller serverseitiger Code aus dem Clientbundle entfernt. Das bedeutet, dass jeder Code, der in der Methode server verwendet wird, immer aus dem Clientbundle entfernt wird. Wenn validateClient auf true gesetzt ist, wird der clientseitige Validierungscode in das Clientbundle aufgenommen, andernfalls wird auch der Code für die data-Validierung entfernt.
Unsere Partner
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
Prisma
Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.

Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.