TL;DR: Client-seitiges Filtering bricht bei Skalierung. TanStack Tables manual-Modi für Filtering, Sorting und Pagination delegieren alle Datenoperationen an den Server. Dieser Guide deckt serverseitige Textsuche mit D1, Multi-Column-Sort, Filter-State-Serialisierung in URL-Parameter und debounced Search Input ab.
Einleitung
TanStack Table (früher React Table) ist headless — es verwaltet State und Logik, während Sie das Rendering kontrollieren. Für Produktions-SaaS-Anwendungen mit tausenden Datensätzen brauchen Sie serverseitige Datenoperationen. TanStack Tables manualFiltering-, manualSorting- und manualPagination-Optionen sind genau dafür gebaut.
Server Function mit Filtering
Definieren Sie zuerst eine Server Function, die Suchquery, Sortierparameter und Pagination akzeptiert. Zod-Validierung stellt Typsicherheit ab der API-Grenze sicher:
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
import { db } from '../db'
const searchSchema = z.object({
query: z.string().default(''),
sortBy: z.string().default('createdAt'),
sortDir: z.enum(['asc', 'desc']).default('desc'),
page: z.coerce.number().default(1),
pageSize: z.coerce.number().default(25),
})
export const listCustomersFn = createServerFn({ method: 'GET' })
.validator(searchSchema)
.handler(async ({ data }) => {
const { query, sortBy, sortDir, page, pageSize } = data
const offset = (page - 1) * pageSize
let dbQuery = db.selectFrom('customers').selectAll()
if (query) {
dbQuery = dbQuery.where((eb) =>
eb.or([
eb('name', 'like', `%${query}%`),
eb('email', 'like', `%${query}%`),
])
)
}
const [{ count }] = await dbQuery.select(db.fn.countAll().as('count')).execute()
const rows = await dbQuery
.orderBy(sortBy, sortDir)
.limit(pageSize)
.offset(offset)
.execute()
return { rows, total: count, page, pageSize }
})
Die Server Function handhabt Suche, Sortierung und Pagination in einer einzigen Query. Parameterisierte LIKE-Queries verhindern SQL-Injection und ermöglichen zugleich flexible Textsuche über mehrere Spalten.
TanStack Table Integration
Mit der fertigen Server Function verdrahten Sie TanStack Table über die manualPagination- und manualSorting-Modi. Das weist die Tabelle an, Datenoperationen an den Server zu delegieren:
import { useReactTable, getCoreRowModel, getFilteredRowModel } from '@tanstack/react-table'
function CustomerTable() {
const [search, setSearch] = useState('')
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25 })
const [sorting, setSorting] = useState([{ id: 'createdAt', desc: true }])
const { data, isFetching } = useQuery({
queryKey: ['customers', search, pagination, sorting],
queryFn: () => listCustomersFn({
data: {
query: search,
page: pagination.pageIndex + 1,
pageSize: pagination.pageSize,
sortBy: sorting[0]?.id || 'createdAt',
sortDir: sorting[0]?.desc ? 'desc' : 'asc',
},
}),
placeholderData: keepPreviousData,
})
const table = useReactTable({
data: data?.rows ?? [],
columns,
pageCount: data ? Math.ceil(data.total / pagination.pageSize) : -1,
state: { pagination, sorting },
onPaginationChange: setPagination,
onSortingChange: setSorting,
manualPagination: true,
manualSorting: true,
getCoreRowModel: getCoreRowModel(),
})
return <TableUI table={table} search={search} onSearchChange={setSearch} />
}
Die manualPagination- und manualSorting-Flags deaktivieren TanStack Tables eingebaute Datenverarbeitung. Jede Sortieränderung oder Seiten-Navigation löst eine neue Server-Query via TanStack Query aus, die Caching und Deduplizierung automatisch übernimmt.
Debounced Search
Ein Debounce-Hook verhindert übermäßige Server-Requests während der Eingabe. Hier eine wiederverwendbare Implementierung, die die Such-Query verzögert, bis der Nutzer aufhört zu tippen:
function useDebounce<T>(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value)
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay)
return () => clearTimeout(timer)
}, [value, delay])
return debounced
}
// Verwendung
const [searchInput, setSearchInput] = useState('')
const debouncedSearch = useDebounce(searchInput, 300)
// Query nutzt debouncedSearch, Input nutzt searchInput
Mit 300 ms Debounce erhält der Server höchstens eine Suchanfrage pro 300 ms Tippen. Das Input bleibt responsiv, unnötige Datenbank-Queries werden vermieden.
Client vs. Server-Operationen
| Operation | Client-seitig | Server-seitig | Entscheidung |
|---|---|---|---|
| Textsuche | <500 Zeilen | >500 Zeilen | Datenmenge |
| Sortierung | <1.000 Zeilen | >1.000 Zeilen | Datenmenge |
| Pagination | <10 Seiten | Immer | UX-Präferenz |
| Multi-Column-Sort | Jede Größe | Große Datensätze | Komplexität |
| Row Grouping | Mittlere Datensätze | Große Datensätze | Feature-Bedarf |
Fazit
Serverseitiges Filtering mit TanStack Table skaliert von Hunderten bis zu Millionen Zeilen. Kombiniert mit TanStack Start Server Functions und D1 erhalten Sie typsichere, gedebouncete und URL-persistente Datentables für jede Datenmenge. Für eine vollständige Table-Implementierung sehen Sie sich den Data-Rich-Interfaces-Guide an, lernen Sie Cursor-basierte Pagination mit Infinite Queries, designen Sie Ihr DB-Schema mit dem SaaS Database Architecture Guide und kombinieren Sie Virtual Scrolling mit TanStack Virtual Scrolling für Datensätze über 10.000 Zeilen.