eduardweb.
shadcn/uiAvansat#nextjs#typescript#react#shadcn-ui#tanstack-table

DataTable în Next.js cu shadcn/ui și TanStack: server-side pagination fără bătăi de cap

De Alexandru Matei, 27 iul. 2026 · 12 vizualizări · 2 like-uri

Postat 27 iul. 2026
typescript
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?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.