von Kyle Mathews und Sam Willis am 30. Juli 2025. 
Dein React-Dashboard sollte nicht ins Stocken geraten, nur weil sich ein TODO von ☐ zu ☑ ändert. Doch jedes optimistische Update löst immer noch eine Kaskade von Re-Renders, Filtern, useMemos und Spinner-Aufblitzen aus.
Wenn du jemals gemurmelt hast: „Warum ist das im Jahr 2025 immer noch so schwer?“ — wir auch.
TanStack DB ist unsere Antwort: eine clientseitige Datenbankebene, die auf differenziellem Dataflow basiert und direkt in deine bestehenden useQuery-Aufrufe integriert wird.
Sie berechnet nur neu, was sich geändert hat — 0,7 ms zum Aktualisieren einer Zeile in einer sortierten 100.000er-Sammlung auf einem M1 Pro (CodeSandbox)
Ein früher Alpha-Nutzer, der eine Linear-ähnliche Anwendung baute, tauschte einen Stapel MobX-Code gegen TanStack DB aus und sagte uns erleichtert: „Alles ist jetzt absolut sofortig, wenn man durch die App klickt, selbst mit 1000 geladenen Aufgaben.“
Heute stehen die meisten Teams vor einer hässlichen Weggabelung
Option A. Ansichtsspezifische APIs (schnelles Rendern, langsames Netzwerk, endloses Endpoint-Sprawl) oder
Option B. Alles laden und filtern (einfaches Backend, träger Client).
Differenzieller Dataflow ermöglicht Option C – normalisierte Sammlungen einmal laden, TanStack DB lässt inkrementelle Joins mit Millisekunden-Präzision im Browser streamen. Keine Neufassungen, keine Spinner, kein Ruckeln.
Live-Abfragen, mühelose optimistische Schreibvorgänge und eine radikal einfachere Architektur – alles inkrementell adoptierbar.
Probieren Sie den TanStack DB Starter aus
TanStack DB verwaltet einen normalisierten Sammlungsdatenspeicher im Speicher und verwendet dann differenziellen Dataflow, um Abfrageergebnisse inkrementell zu aktualisieren. Stellen Sie es sich wie Streaming-SQL im Materialize-Stil vor – nur eingebettet im Browser und direkt mit dem Cache von React Query verbunden.
Anders ausgedrückt: TanStack Query kümmert sich weiterhin um „Wie hole ich die Daten?“; TanStack DB kümmert sich um „Wie halte ich alles kohärent und blitzschnell, sobald es da ist?“
Und da es nur eine weitere Ebene über queryClient ist, können Sie es eine Sammlung nach der anderen übernehmen.
Stellen Sie sich vor, wir haben bereits ein Backend mit einer REST-API, die den Endpunkt /api/todos bereitstellt, um eine Liste von To-dos abzurufen und sie zu mutieren.
import {
useQuery,
useMutation,
useQueryClient, // ❌ Not needed with DB
} from '@tanstack/react-query'
const Todos = () => {
const queryClient = useQueryClient() // ❌
// Fetch todos
const { data: allTodos = [] } = useQuery({
queryKey: ['todos'],
queryFn: async () =>
api.todos.getAll('/api/todos'),
})
// Filter incomplete todos
// ❌ Runs every render unless memoized
const todos = allTodos.filter(
(todo) => !todo.completed
)
// ❌ Manual optimistic update boilerplate
const addTodoMutation = useMutation({
mutationFn: async (newTodo) =>
api.todos.create(newTodo),
onMutate: async (newTodo) => {
await queryClient.cancelQueries({
queryKey: ['todos'],
})
const previousTodos =
queryClient.getQueryData(['todos'])
queryClient.setQueryData(
['todos'],
(old) => [...(old || []), newTodo]
)
return { previousTodos }
},
onError: (err, newTodo, context) => {
queryClient.setQueryData(
['todos'],
context.previousTodos
)
},
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ['todos'],
})
},
})
return (
<div>
<List items={todos} />
<Button
onClick={() =>
addTodoMutation.mutate({
id: uuid(),
text: '🔥 Make app faster',
completed: false,
})
}
/>
</div>
)
}
import {
useQuery,
useMutation,
useQueryClient, // ❌ Not needed with DB
} from '@tanstack/react-query'
const Todos = () => {
const queryClient = useQueryClient() // ❌
// Fetch todos
const { data: allTodos = [] } = useQuery({
queryKey: ['todos'],
queryFn: async () =>
api.todos.getAll('/api/todos'),
})
// Filter incomplete todos
// ❌ Runs every render unless memoized
const todos = allTodos.filter(
(todo) => !todo.completed
)
// ❌ Manual optimistic update boilerplate
const addTodoMutation = useMutation({
mutationFn: async (newTodo) =>
api.todos.create(newTodo),
onMutate: async (newTodo) => {
await queryClient.cancelQueries({
queryKey: ['todos'],
})
const previousTodos =
queryClient.getQueryData(['todos'])
queryClient.setQueryData(
['todos'],
(old) => [...(old || []), newTodo]
)
return { previousTodos }
},
onError: (err, newTodo, context) => {
queryClient.setQueryData(
['todos'],
context.previousTodos
)
},
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ['todos'],
})
},
})
return (
<div>
<List items={todos} />
<Button
onClick={() =>
addTodoMutation.mutate({
id: uuid(),
text: '🔥 Make app faster',
completed: false,
})
}
/>
</div>
)
}
// ✅ Define a Query Collection
import { createCollection } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'
const todoCollection = createCollection(
queryCollectionOptions({
queryKey: ['todos'],
queryFn: async () =>
api.todos.getAll('/api/todos'),
getKey: (item) => item.id, // ✅ New
schema: todoSchema, // ✅ New
onInsert: async ({ transaction }) => {
// ✅ New
await Promise.all(
transaction.mutations.map((mutation) =>
api.todos.create(mutation.modified)
)
)
},
})
)
// ✅ Use live queries in components
import { useLiveQuery } from '@tanstack/react-db'
import { eq } from '@tanstack/db'
const Todos = () => {
// ✅ Live query with automatic updates
const { data: todos } = useLiveQuery((query) =>
query
.from({ todos: todoCollection })
// ✅ Type-safe query builder
// ✅ Incremental computation
.where(({ todos }) =>
eq(todos.completed, false)
)
)
return (
<div>
<List items={todos} />
<Button
onClick={() =>
// ✅ Simple mutation - no boilerplate!
// ✅ Automatic optimistic updates
// ✅ Automatic rollback on error
todoCollection.insert({
id: uuid(),
text: '🔥 Make app faster',
completed: false,
})
}
/>
</div>
)
}
// ✅ Define a Query Collection
import { createCollection } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'
const todoCollection = createCollection(
queryCollectionOptions({
queryKey: ['todos'],
queryFn: async () =>
api.todos.getAll('/api/todos'),
getKey: (item) => item.id, // ✅ New
schema: todoSchema, // ✅ New
onInsert: async ({ transaction }) => {
// ✅ New
await Promise.all(
transaction.mutations.map((mutation) =>
api.todos.create(mutation.modified)
)
)
},
})
)
// ✅ Use live queries in components
import { useLiveQuery } from '@tanstack/react-db'
import { eq } from '@tanstack/db'
const Todos = () => {
// ✅ Live query with automatic updates
const { data: todos } = useLiveQuery((query) =>
query
.from({ todos: todoCollection })
// ✅ Type-safe query builder
// ✅ Incremental computation
.where(({ todos }) =>
eq(todos.completed, false)
)
)
return (
<div>
<List items={todos} />
<Button
onClick={() =>
// ✅ Simple mutation - no boilerplate!
// ✅ Automatic optimistic updates
// ✅ Automatic rollback on error
todoCollection.insert({
id: uuid(),
text: '🔥 Make app faster',
completed: false,
})
}
/>
</div>
)
}
TanStack Query ist mit 12 Millionen (und wachsenden) Downloads pro Woche unglaublich beliebt. Warum also etwas Neues wie TanStack DB entwickeln?
Query löst die schwierigsten Probleme der Serverzustandsverwaltung – intelligentes Caching, Hintergrundsynchronisierung, Anforderungsdeduplizierung, optimistische Updates und nahtlose Fehlerbehandlung.
Es hat sich zum De-facto-Standard entwickelt, da es den Boilerplate-Code und die Komplexität der Verwaltung von asynchronen Datenabrufen eliminiert und gleichzeitig eine hervorragende Entwicklererfahrung mit Funktionen wie automatischem Hintergrund-Refetching, Stale-While-Revalidate-Mustern und leistungsstarken DevTools bietet.
Aber Query behandelt Daten als isolierte Cache-Einträge. Jedes Abfrageergebnis ist unabhängig – es gibt kein Konzept von Beziehungen, Live-Abfragen über mehrere Datenquellen hinweg oder reaktive Updates, wenn ein Datenstück ein anderes beeinflusst. Sie können nicht einfach „zeigen Sie mir alle To-dos, bei denen der Projektstatus aktiv ist“ fragen und beobachten, wie sich die Liste automatisch aktualisiert, wenn sich ein Projektstatus ändert.
TanStack DB schließt diese Lücke. Während Query beim Abrufen und Caching von Serverzuständen hervorragend ist, bietet DB die fehlende reaktive, relationale Ebene darüber. Sie erhalten das Beste aus beiden Welten: die robuste Serverzustandsverwaltung von Query plus die eingebettete Client-Datenbank von TanStack DB, die Ihren gesamten Daten-Graphen verbinden, filtern und reaktiv aktualisieren kann.
Aber es verbessert nicht nur Ihr aktuelles Setup – es ermöglicht eine neue, radikal vereinfachte Architektur.
Lassen Sie uns die drei Optionen noch einmal betrachten
Option A – Ansichtsspezifische APIs: Erstellen Sie ansichtsspezifische API-Endpunkte, die genau das zurückgeben, was jede Komponente benötigt. Sauber, schnell, keine clientseitige Verarbeitung. Aber jetzt ertrinken Sie in brüchigen API-Routen, kämpfen mit Netzwerkwasserfällen, wenn Komponenten verwandte Daten benötigen, und schaffen eine enge Kopplung zwischen Ihren Frontend-Ansichten und Backend-Schemas.
Option B – Alles laden und filtern: Laden Sie breitere Datensätze und filtern/verarbeiten Sie sie clientseitig. Weniger API-Aufrufe, flexibleres Frontend. Aber Sie stoßen an die Leistungsgrenze – todos.filter(), users.find(), posts.map(), useMemo() überall, mit kaskadierenden Re-Renders, die Ihre UX zerstören.
Die meisten Teams wählen Option A, um Leistungsprobleme zu vermeiden. Sie tauschen clientseitige Komplexität gegen API-Proliferation und Netzwerkh Abhängigkeit.
TanStack DB ermöglicht Option C – Normalisierte Sammlungen + Inkrementelle Joins: Laden Sie normalisierte Sammlungen über weniger API-Aufrufe, und führen Sie dann blitzschnelle inkrementelle Joins im Client durch. Sie erhalten die Netzwerkeffizienz breiter Datenladungen mit einer Abfrageleistung unter einer Millisekunde, die Option A überflüssig macht.
Stattdessen
// View-specific API call every time you navigate
const { data: projectTodos } = useQuery({
queryKey: ['project-todos', projectId],
queryFn: () => fetchProjectTodosWithUsers(projectId)
})
// View-specific API call every time you navigate
const { data: projectTodos } = useQuery({
queryKey: ['project-todos', projectId],
queryFn: () => fetchProjectTodosWithUsers(projectId)
})
Sie können dies tun
// Load normalized collections upfront (3 broader calls)
const todoCollection = createQueryCollection({
queryKey: ['todos'],
queryFn: fetchAllTodos,
})
const userCollection = createQueryCollection({
queryKey: ['users'],
queryFn: fetchAllUsers,
})
const projectCollection = createQueryCollection({
queryKey: ['projects'],
queryFn: fetchAllProjects,
})
// Navigation is instant — no new API calls needed
const { data: activeProjectTodos } = useLiveQuery(
(q) =>
q
.from({ t: todoCollection })
.innerJoin(
{ u: userCollection },
({ t, u }) => eq(t.userId, u.id)
)
.innerJoin(
{ p: projectCollection },
({ u, p }) => eq(u.projectId, p.id)
)
.where(({ t }) => eq(t.active, true))
.where(({ p }) =>
eq(p.id, currentProject.id)
)
)
// Load normalized collections upfront (3 broader calls)
const todoCollection = createQueryCollection({
queryKey: ['todos'],
queryFn: fetchAllTodos,
})
const userCollection = createQueryCollection({
queryKey: ['users'],
queryFn: fetchAllUsers,
})
const projectCollection = createQueryCollection({
queryKey: ['projects'],
queryFn: fetchAllProjects,
})
// Navigation is instant — no new API calls needed
const { data: activeProjectTodos } = useLiveQuery(
(q) =>
q
.from({ t: todoCollection })
.innerJoin(
{ u: userCollection },
({ t, u }) => eq(t.userId, u.id)
)
.innerJoin(
{ p: projectCollection },
({ u, p }) => eq(u.projectId, p.id)
)
.where(({ t }) => eq(t.active, true))
.where(({ p }) =>
eq(p.id, currentProject.id)
)
)
Jetzt erfordert das Klicken zwischen Projekten, Benutzern oder Ansichten keine API-Aufrufe mehr. Alle Daten sind bereits geladen. Neue Funktionen wie „Benutzerlast über alle Projekte anzeigen“ funktionieren sofort, ohne Ihr Backend zu berühren.
Ihre API wird einfacher. Ihre Netzwerkanrufe sinken dramatisch. Ihr Frontend wird schneller, während Ihr Datensatz wächst.
Ihre App wäre dramatisch schneller, wenn Sie einfach 20 MB normalisierter Daten vorab laden würden, anstatt Hunderte von kleinen API-Aufrufen zu machen.
Unternehmen wie Linear, Figma und Slack laden massive Datensätze in den Client und erzielen durch massive Investitionen in benutzerdefinierte Indizierung, differentielle Updates und optimiertes Rendering eine unglaubliche Leistung. Diese Lösungen sind für die meisten Teams zu komplex und zu teuer, um sie zu bauen.
TanStack DB bringt diese Funktionalität für alle durch Differential Dataflow – eine Technik, die nur die Teile von Abfragen neu berechnet, die sich tatsächlich geändert haben. Anstatt zwischen „vielen schnellen API-Aufrufen mit Netzwerkwasserfällen“ oder „wenigen API-Aufrufen mit langsamer Client-Verarbeitung“ zu wählen, erhalten Sie das Beste aus beiden Optionen: weniger Netzwerk-Round-Trips UND clientseitige Abfragen unter einer Millisekunde, selbst bei großen Datensätzen.
Dies geht nicht nur um Sync-Engines wie Electric (obwohl sie dieses Muster unglaublich leistungsfähig machen). Es geht darum, eine grundlegend andere Datenladestrategie zu ermöglichen, die mit jedem Backend funktioniert – REST, GraphQL oder Echtzeit-Sync.
Während TanStack DB hervorragend mit REST und GraphQL funktioniert, glänzt es wirklich in Kombination mit Sync-Engines. Hier sind die Gründe, warum Sync-Engines eine so leistungsstarke Ergänzung sind
Einfaches Echtzeit-Erlebnis – Wenn Sie Echtzeit-Updates benötigen, wissen Sie, wie schmerzhaft es sein kann, WebSockets einzurichten, Wiederverbindungen zu verwalten und Event-Handler zu verdrahten. Viele neue Sync-Engines sind nativ für Ihren eigentlichen Datenspeicher (z. B. Postgres) konzipiert, sodass Sie einfach direkt in die Datenbank schreiben und wissen, dass das Update in Echtzeit an alle Abonnenten gestreamt wird. Kein manuelles WebSocket-Plumbing mehr.
Nebenwirkungen werden automatisch verarbeitet – Wenn Sie eine Backend-Mutation durchführen, gibt es oft kaskadierende Updates über mehrere Tabellen hinweg. Den Status eines To-dos ändern? Das kann den Fertigstellungsgrad des Projekts ändern, Teammetriken aktualisieren oder Workflow-Automatisierungen auslösen. Mit TanStack Query allein benötigen Sie eine manuelle Buchführung, um all diese potenziellen Nebenwirkungen zu verfolgen und die richtigen Daten neu zu laden. Sync-Engines eliminieren diese Komplexität – jede Backend-Änderung, die während einer Mutation auftritt, wird automatisch an alle Clients weitergeleitet – ohne zusätzliche Arbeit.
Laden Sie weitaus mehr Daten effizienter – Es ist weitaus kostengünstiger, Daten im Client zu aktualisieren, wenn Sync-Engines verwendet werden. Anstatt nach jeder Änderung ganze Sammlungen neu zu laden, senden Sync-Engines nur die tatsächlich geänderten Elemente. Dies macht es praktisch, weitaus mehr Daten im Voraus zu laden und ermöglicht das Muster „alles einmal laden“, das Apps wie Linear so schnell macht.
TanStack DB wurde von Grund auf für die Unterstützung von Sync-Engines entwickelt. Wenn Sie eine Sammlung definieren, erhalten Sie eine API zum Schreiben von synchronisierten Transaktionen vom Backend in Ihre lokalen Sammlungen. Probieren Sie Implementierungen für Sammlungen für Electric, Trailblaze und (bald) Firebase!
DB bietet Ihnen eine gemeinsame Schnittstelle für Ihre Komponenten zum Abfragen von Daten, was bedeutet, dass Sie nach Bedarf problemlos zwischen verschiedenen Datenladestrategien wechseln können, ohne den Client-Code zu ändern. Beginnen Sie mit REST, wechseln Sie später bei Bedarf zu einer Sync-Engine – Ihre Komponenten müssen den Unterschied nicht kennen.
Wir entwickeln TanStack DB, um die clientseitigen Daten-Engpässe zu beseitigen, auf die jedes Team irgendwann stößt. Hier sind unsere Ziele
Wir freuen uns darauf, Teams eine grundlegend bessere Möglichkeit zur Verwaltung von clientseitigen Daten zu bieten – und dabei die Freiheit zu bewahren, das Backend zu wählen, das am besten passt.
TanStack DB 0.1 (erste Beta) ist jetzt verfügbar. Wir suchen insbesondere Teams, die
Wenn Ihr Team mehr Zeit mit der Optimierung von React-Re-Renders verbringt als mit der Entwicklung von Features, oder wenn sich Ihre kollaborativen Features im Vergleich zu Linear und Figma träge anfühlen, ist TanStack DB genau für Ihre Situation konzipiert.
Starten Sie noch heute
Keine Ruckler mehr. Kein Ruckeln mehr. Hören Sie auf zu rendern – fangen Sie an zu liefern!