Middleware

Was ist Server-Funktions-Middleware?

Middleware ermöglicht es Ihnen, das Verhalten von mit createServerFn erstellten Server-Funktionen mit Dingen wie gemeinsamer Validierung, Kontext und vielem mehr anzupassen. Middleware kann sogar von anderer Middleware abhängen, um eine Kette von Operationen zu erstellen, die hierarchisch und in der Reihenfolge ausgeführt werden.

Was kann ich mit Middleware in meinen Server-Funktionen tun?

  • Authentifizierung: Überprüfen Sie die Identität eines Benutzers, bevor Sie eine Server-Funktion ausführen.
  • Autorisierung: Prüfen Sie, ob ein Benutzer die notwendigen Berechtigungen hat, um eine Server-Funktion auszuführen.
  • Protokollierung: Protokollieren Sie Anfragen, Antworten und Fehler.
  • Observability: Erfassen Sie Metriken, Traces und Protokolle.
  • Kontext bereitstellen: Hängen Sie Daten an das Anfrageobjekt an, um sie in anderen Middleware- oder Server-Funktionen zu verwenden.
  • Fehlerbehandlung: Behandeln Sie Fehler auf konsistente Weise.
  • Und vieles mehr! Die Möglichkeiten liegen bei Ihnen!

Definieren von Middleware für Server-Funktionen

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

tsx
import { createMiddleware } from '@tanstack/react-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/react-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
  },
)

Verwenden von Middleware in Ihren Server-Funktionen

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

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

const fn = createServerFn()
  .middleware([loggingMiddleware])
  .handler(async () => {
    // ...
  })
import { createServerFn } from '@tanstack/react-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 (hoffentlich) TypeScript verwenden, wird die Reihenfolge dieser Methoden durch das Typsystem erzwungen, um maximale Inferenz und Typsicherheit zu gewährleisten.

  • middleware: Fügen Sie eine Middleware zur Kette hinzu.
  • validator: Modifizieren Sie das Datenobjekt, bevor es an diese Middleware und alle verschachtelten Middleware übergeben wird.
  • server: Definieren Sie serverseitige Logik, die die Middleware vor jeder verschachtelten Middleware und schließlich einer Server-Funktion ausführt, und stellen Sie das Ergebnis der nächsten Middleware bereit.
  • client: Definieren Sie clientseitige Logik, die die Middleware vor jeder verschachtelten Middleware und letztendlich der clientseitigen RPC-Funktion (oder der serverseitigen Funktion) ausführt und das Ergebnis der nächsten Middleware bereitstellt.

Die Methode middleware

Die Methode middleware wird verwendet, um Middleware-Abhängigkeiten in die Kette einzufügen, 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/react-start'

const loggingMiddleware = createMiddleware({ type: 'function' }).middleware([
  authMiddleware,
  loggingMiddleware,
])
import { createMiddleware } from '@tanstack/react-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 Server-Funktion übergeben wird. Diese Methode sollte eine Funktion erhalten, die das Datenobjekt annimmt 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/react-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/react-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 Server-Funktion ausführt. Diese Methode erhält 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 Server-Funktion übergeben wurde.
  • context: Ein Objekt, das Daten von übergeordneten Middleware speichert. Es kann mit zusätzlichen Daten erweitert werden, die an untergeordnete Middleware übergeben werden.

Zurückgeben des erforderlichen Ergebnisses von next

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 wird.

tsx
import { createMiddleware } from '@tanstack/react-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/react-start'

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

Bereitstellen von Kontext für die nächste Middleware über next

Die Funktion next kann optional mit einem Objekt aufgerufen werden, das eine context-Eigenschaft mit einem Objektwert hat. Alle Eigenschaften, die Sie an diesen context-Wert übergeben, werden in den übergeordneten context gemischt und der nächsten Middleware bereitgestellt.

tsx
import { createMiddleware } from '@tanstack/react-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/react-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 Server-Funktionen hauptsächlich serverseitig gebundene Operationen sind, gibt es immer noch viele clientseitige Logiken rund um die ausgehende RPC-Anfrage vom Client. Das bedeutet, dass wir auch clientseitige Logiken in Middleware definieren können, die auf der Client-Seite 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 gering zu halten. Sie können jedoch auch Daten auf der Client-Seite validieren, indem Sie die Option validateClient: true an die Funktion createMiddleware übergeben. Dies führt dazu, dass die Daten auf der Client-Seite validiert werden, bevor sie an den Server gesendet werden, was möglicherweise eine Hin- und Rückreise spart.

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

Das clientseitige Validierungsschema wird vom serverseitigen Schema abgeleitet. Das 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 unerwartete Daten erhalten, was zu unerwartetem Verhalten führen könnte.

tsx
import { createMiddleware } from '@tanstack/react-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/react-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 Server-Funktion aufrufen) ausführt.

Clientseitige Middleware-Logik teilt sich einen Großteil derselben API wie die mit der Methode server erstellte Logik, wird aber auf der Client-Seite ausgeführt. Dies beinhaltet

  • Die Anforderung, die Funktion next aufzurufen, um die Kette fortzusetzen.
  • Die Möglichkeit, Kontext für die nächste Client-Middleware über die Funktion next bereitzustellen.
  • Die Möglichkeit, das Datenobjekt zu modifizieren, bevor es an die nächste Client-Middleware übergeben wird.

Ähnlich wie die Funktion server erhält 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 Client-Funktion übergeben wurde.
  • context: Ein Objekt, das Daten von übergeordneten Middleware speichert. Es kann mit zusätzlichen Daten erweitert werden, die an untergeordnete Middleware übergeben 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
  },
)

Senden von Client-Kontext an den Server

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öchten, müssen Sie die Funktion next mit einer sendContext-Eigenschaft und einem Objekt aufrufen, um Daten an den Server zu übertragen. Alle Eigenschaften, die an sendContext übergeben werden, werden zusammen mit den Daten gemischt, 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, obwohl clientseitig gesendeter Kontext typsicher ist, keine Laufzeitvalidierung erforderlich ist. Wenn Sie dynamische, benutzergenerierte Daten über den Kontext übergeben, kann dies ein Sicherheitsproblem darstellen. **Wenn Sie dynamische Daten vom Client über den Kontext an den Server senden, sollten Sie diese 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()
  })

Senden von Server-Kontext an den Client

Ä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 sendContext-Eigenschaft und einem Objekt aufrufen, um Daten an den Client zu übertragen. Alle Eigenschaften, die an sendContext übergeben werden, werden zusammen mit der Antwort gemischt, 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 vorherigen Middleware-Ketten ableiten, die über die Funktion middleware verknüpft sind.

Warnung

Der Rückgabetyp von next in client kann nur aus Middleware abgeleitet werden, die in der aktuellen Middleware-Kette bekannt ist. Daher ist der genaueste Rückgabetyp von next in der 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
  })

Lesen/Modifizieren der Server-Antwort

Middleware, die die Methode server verwendet, wird im gleichen Kontext wie Server-Funktionen ausgeführt. Sie können daher exakt dieselben Server-Funktions-Kontext-Dienstprogramme verwenden, um Anfrage-Header, Statuscodes usw. zu lesen und zu modifizieren.

Modifizieren der Client-Anfrage

Middleware, die die Methode client verwendet, wird in einem **völlig anderen clientseitigen Kontext** als Server-Funktionen ausgeführt. Sie können daher nicht dieselben Dienstprogramme verwenden, um die Anfrage zu lesen und zu modifizieren. Sie können die Anfrage jedoch modifizieren, indem Sie zusätzliche Eigenschaften beim Aufruf der Funktion next zurückgeben. Derzeit unterstützte Eigenschaften sind

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

Hier ist ein Beispiel für das Hinzufügen eines Authorization-Headers zu jeder Anfrage, 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()}`,
      },
    })
  },
)

Verwenden von Middleware

Middleware kann auf zwei verschiedene Arten verwendet werden

  • Globale Middleware: Middleware, die für jede Anfrage ausgeführt werden soll.
  • Server-Funktions-Middleware: Middleware, die für eine bestimmte Server-Funktion ausgeführt werden soll.

Globale Middleware

Globale Middleware wird automatisch für jede Server-Funktion 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 ist der Ort, an dem Sie globale Middleware registrieren.

So registrieren Sie globale Middleware

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

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

registerGlobalMiddleware({
  middleware: [authMiddleware],
})

Typsicherheit globaler Middleware

Typen globaler Middleware sind von Natur aus von Server-Funktionen **getrennt**. Das bedeutet, dass, wenn eine globale Middleware zusätzliche Kontexte für Server-Funktionen oder andere serverfunktionsspezifische Middleware bereitstellt, die Typen nicht automatisch an die Server-Funktion 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 Server-Funktion hinzu. **Die globale Middleware wird dedupliziert zu einem einzigen Eintrag (der globale Instanz), und Ihre Server-Funktion 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)
    // ...
  })

Reihenfolge der Middleware-Ausführung

Middleware wird Abhängigkeits-zuerst ausgeführt, beginnend mit globaler Middleware, gefolgt von Server-Funktions-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')
  })

Tree Shaking von Umgebungen

Middleware-Funktionalität wird basierend auf der Umgebung für jedes erzeugte Bundle per Tree Shaking entfernt.

  • Auf dem Server wird nichts per Tree Shaking entfernt, sodass der gesamte in der Middleware verwendete Code im Server-Bundle enthalten ist.
  • Auf dem Client wird jeglicher serverseitiger Code aus dem Client-Bundle entfernt. Das bedeutet, dass jeder Code, der in der Methode server verwendet wird, immer aus dem Client-Bundle entfernt wird. Wenn validateClient auf true gesetzt ist, wird der clientseitige Validierungscode in das Client-Bundle aufgenommen, andernfalls wird auch der data-Validierungscode 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.