import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
export function useServerTable({ data, pageCount, columns }: any) {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const page = Number(searchParams.get("page")) || 1;
const perPage = Number(searchParams.get("per_page")) || 10;
const table = useReactTable({
data,
columns,
pageCount,
manualPagination: true,
state: {
pagination: { pageIndex: page - 1, pageSize: perPage },
},
onPaginationChange: (updater) => {
const nextState = typeof updater === "function"
? updater({ pageIndex: page - 1, pageSize: perPage })
: updater;
const params = new URLSearchParams(searchParams.toString());
params.set("page", (nextState.pageIndex + 1).toString());
params.set("per_page", nextState.pageSize.toString());
router.push(`${pathname}?${params.toString()}`);
},
getCoreRowModel: getCoreRowModel(),
});
return table;
}Am văzut prea mulți dev-i care încarcă 10.000 de rânduri în client doar ca să le meargă sortarea din TanStack Table instant. E super ok pe un demo sau un admin panel mic, dar la ultimul proiect – o platformă e-commerce cu 85k de comenzi – browserul pur și simplu agăța la fiecare filtru. Azi vă arăt cum am mutat totul pe server în Next.js App Router, folosind URL-ul ca single source of truth.
De ce URL state și nu useState?
Când faci paginare sau filtrare pe client, starea trăiește în memoria componentei. Dacă utilizatorul dă refresh sau trimite link-ul unui coleg din echipă, tot contextul se pierde. În plus, clientul descarcă un payload uriaș de JSON la primul render.
Sincronizând starea tabelei direct în searchParams (de exemplu ?page=2&sort=created_at&order=desc&q=ion), obții două mari avantaje: URL-ul devine shareable și payload-ul inițial scade drastic. În cazul nostru, am redus transferul de date de la 4.8MB la doar 12KB per pagină.
Trade-off-ul e evident: pierzi acele animații de 0ms pe care le ai când datele sunt deja în RAM. Fiecare schimbare de pagină sau filtru face un mini-roundtrip către server, dar cu Server Components și un index bun pe DB, response time-ul rămâne sub 80ms.
Cum legăm TanStack Table de URL
Secretul este să treci manualPagination: true, manualSorting: true și manualFiltering: true în opțiunile useReactTable. În momentul ăla, TanStack Table nu mai încearcă să proceseze array-ul de date în browser, ci doar afișează ce îi dai tu și îți trimite evenimentele de schimbare.
Când userul dă click pe o pagină sau schimbă ordinea coloanei, interceptăm callback-urile (onPaginationChange, onSortingChange) și actualizăm query params din URL folosind useRouter sau pachetul nuqs (pe care îl recomand cu căldură pentru tipare safe).
Atenție la filtrare (Debouncing-ul e obligatoriu)
Dacă legi un input de căutare direct la router-push pe fiecare onChange, o să-ți distrugi baza de date. La un input de 10 caractere, trimiți 10 request-uri în câteva secunde. Pune neapărat un debounce de 300-400ms pe input-ul de text înainte să scrii în URL.
O altă capcană e resetarea paginii. Când userul e pe pagina 5 și tastează ceva în filtrul de search, trebuie să resetezi obligatoriu pagina la 1. Altfel, query-ul tău va căuta rezultatele pentru filtrarea nouă la offset 50 și probabil va returna o tabelă goală.
Voi cum gestionați tabelele mari în Next.js? Mergeți pe Server Components pure sau preferați să trageți datele prin SWR/React Query pe client?