Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
World desk7 min

Next.js Server-Side Tables: URL State, Data Fetching, and TanStack Table

Use App Router search parameters as durable table state, process the authorized result set on the server, and let a small Client Component handle interactive controls.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a server-side table in the Next.js App Router, make the URL the durable source of page, filter, and sort state; parse and validate that state in the server-rendered page; and have the server return only the authorized, correctly processed rows. Keep interactive controls in small Client Components. A table library such as TanStack Table can manage table UI and render rows, but in manual mode your application or backend—not the library—must filter, sort, and paginate the data.

Choose which side processes the rows

TanStack Table’s documentation puts it plainly: “Important TanStack Table supports both client-side and server-side row processing!” The right choice depends on the data and experience you need, not a universal row-count threshold. Its guide frames the decision around how much data reaches the browser, the cost of transfer and processing, and the desired user experience (TanStack Table: Row Models).

Decision point Server-side processing Client-side processing
Data sent to the browser The requested page or another bounded result More or all of the relevant dataset
Where filtering, sorting, and pagination run Backend, database, or service Browser row models
Often suits Larger, expensive, permission-sensitive, or frequently changing datasets Small bounded datasets already available to the page
URL behavior Page query state can drive server data loads and shareable views URL state can still be used, but operations may apply only to data already loaded
Main concern Validate state and coordinate requests, caching, resets, and loading Transfer and process enough data to make global operations accurate

Do not sort or filter just the current server-returned page in the browser while presenting the result as globally sorted or filtered. Global operations must use the same complete filtered dataset before pagination; otherwise, records on other pages are omitted from the operation (TanStack Table: Sorting).

Put URL-driven data loading in the App Router page

In the App Router, pages and layouts are Server Components by default. The page’s searchParams prop is the server-side entry point for URL state that determines fetched data. Current Next.js documentation types this prop as a Promise, so await it rather than copying older synchronous examples. Reading it opts the page into dynamic rendering. Repeated query keys can arrive as arrays, so decide which keys accept multiple values and normalize them deliberately (Next.js: page.js reference).

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The useSearchParams hook is for Client Components and returns a read-only URLSearchParams interface; it is not a substitute for the page prop when loading server data. Shared layouts do not receive searchParams, because they are not rerendered on navigation and could otherwise expose stale values. Use the page prop for server queries, or a Client Component hook for browser-side interaction (Next.js: useSearchParams).

A practical division is a Server Component page that parses state, authorizes the request, fetches the rows, and passes the result and normalized state to a Client Component containing the controls. Client Components are appropriate when you need event handlers, local interaction, or browser APIs; they need not encompass the full data page (Next.js: Server and Client Components).

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Example page with validated query state

This example treats page, pageSize, and sort as single-valued keys. It clamps numeric input, permits only known sort columns and directions, and uses a stable identifier as a secondary sort. Replace the authorization and data-layer functions with your application’s own implementations; both must enforce access to the requested dataset.

type SearchParams = Promise<Record<string, string | string[] | undefined>>;

type SortField = "createdAt" | "name" | "status";
type SortDirection = "asc" | "desc";

const sortableFields = new Set<SortField>([
  "createdAt",
  "name",
  "status",
]);

function single(value: string | string[] | undefined): string | undefined {
  // This route defines these keys as single-valued; repeated values use the default.
  return typeof value === "string" ? value : undefined;
}

function boundedInteger(
  value: string | undefined,
  fallback: number,
  min: number,
  max: number,
): number {
  const parsed = Number(value);
  return Number.isInteger(parsed) ? Math.min(max, Math.max(min, parsed)) : fallback;
}

export default async function OrdersPage({
  searchParams,
}: {
  searchParams: SearchParams;
}) {
  const params = await searchParams;
  const page = boundedInteger(single(params.page), 1, 1, 1_000_000);
  const pageSize = boundedInteger(single(params.pageSize), 25, 1, 100);

  const rawSort = single(params.sort) ?? "createdAt:desc";
  const [field, direction, extra] = rawSort.split(":");
  const sortField = sortableFields.has(field as SortField)
    ? (field as SortField)
    : "createdAt";
  const sortDirection: SortDirection =
    !extra && (direction === "asc" || direction === "desc")
      ? direction
      : "desc";

  const statusValues = Array.isArray(params.status)
    ? params.status
    : params.status ? [params.status] : [];
  const allowedStatuses = new Set(["open", "closed", "pending"]);
  const statuses = statusValues.filter((value) => allowedStatuses.has(value));

  const user = await requireAuthenticatedUser();
  const result = await listOrders({
    user,
    page,
    pageSize,
    statuses,
    sort: [
      { field: sortField, direction: sortDirection },
      { field: "id", direction: "asc" },
    ],
  });

  return (
    <OrdersTable
      rows={result.rows}
      rowCount={result.rowCount}
      page={page}
      pageSize={pageSize}
      sortField={sortField}
      sortDirection={sortDirection}
      statuses={statuses}
    />
  );
}

The parsing policy is part of the route contract: this example defaults a repeated single-valued key, while allowing repeated status filters. Choose and document a policy that matches your UI. Whitelisting sortable and filterable fields prevents arbitrary URL input from becoming a database column or query expression.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Define one backend contract for the table

Every server-owned operation should be represented in the request or query key: normalized filters, allowed sort field and direction, page or cursor, and page size. The backend applies filtering and sorting consistently, then paginates the resulting dataset. Return the requested rows plus either a total count or a signal that another page exists. TanStack’s server-side guide assigns this processing to the backend or service and expects the application to pass the already-processed rows to the table (TanStack Table: Row Models).

  • Validate: Treat every URL parameter as untrusted input. Bound numeric values, allow only supported filters and sort fields, and normalize invalid directions.
  • Authorize: Authenticate and authorize every query for the requested records. Server execution alone does not make data access safe.
  • Order deterministically: Add a stable unique secondary sort key when requested sort values can tie, so page boundaries do not depend on an unspecified tie order.
  • Keep count and rows aligned: Compute totals against the filtered dataset, not the unfiltered table, and return rows from that same filter and ordering contract.

Configure TanStack Table for backend-owned operations

TanStack Table does not fetch server data. In manual mode, provide the current server-processed rows and state; the application is responsible for making requests that reflect that state. Do not combine a partial server page with client row models in a way that suggests the browser has the entire dataset (TanStack Table: Row Models).

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
const table = useReactTable({
  data: rows,
  columns,
  state: {
    pagination: { pageIndex: page - 1, pageSize },
    sorting: [{ id: sortField, desc: sortDirection === "desc" }],
    columnFilters: statuses.map((value) => ({ id: "status", value })),
  },
  manualFiltering: true,
  manualSorting: true,
  manualPagination: true,
  rowCount,
  getCoreRowModel: getCoreRowModel(),
});

When the total is known, pass rowCount or pageCount. For the v8 pagination API, pageCount: -1 is supported when the total is unknown, but it does not tell the table when the backend has run out of rows. In that case, return an explicit hasNextPage value and use it to disable the next-page control accurately (TanStack Table v8: Pagination APIs).

Manual pagination disables automatic page-index reset by default in the cited v8 API. Reset the page index yourself when filters, sorting, or page size changes, and validate it against the available pages after a response. Otherwise, a user can remain on a page number that no longer exists in the changed result set (TanStack Table v8: Pagination APIs).

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Make controls update the URL, not a second hidden query state

A Client Component can change query parameters in response to interaction. The URL then describes the current view, making it refreshable and shareable, while the page’s server-side parsing remains the source of truth for the next data load. Use the same canonical keys throughout the control, page parser, backend request, and any cache key.

"use client";

import { usePathname, useRouter, useSearchParams } from "next/navigation";

export function TableControls() {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();

  function update(values: Record<string, string | null>) {
    const next = new URLSearchParams(searchParams.toString());
    for (const [key, value] of Object.entries(values)) {
      if (value === null) next.delete(key);
      else next.set(key, value);
    }
    // A new query changes the result set; begin at its first page.
    next.set("page", "1");
    router.push(`${pathname}?${next.toString()}`);
  }

  return (
    <div>
      <button onClick={() => update({ sort: "createdAt:desc" })}>
        Newest first
      </button>
      <button onClick={() => update({ status: "open" })}>
        Open orders
      </button>
    </div>
  );
}

For multi-select filters, update repeated keys with deliberate append/delete behavior rather than using set, which replaces all values for a key. When changing sort or page size, reset to page one as this example does; when moving between pages, preserve the active filters and sort.

Plan for loading and route behavior

Server Component data fetching runs during server rendering. A slow database or API request can delay the route’s rendered result; Next.js recommends streaming where appropriate. For a table, decide whether a route-level loading state or a Suspense boundary around the table region best preserves the rest of the page’s responsiveness (Next.js: Fetching Data).

Because reading the page’s searchParams opts it into dynamic rendering, consider the consequences for caching and rendering behavior when designing the route. Keep the fetched result bounded, and make sure any cache or request key includes every state value that can change the returned rows.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common mistakes to avoid

  • Reading current query state from a shared layout’s props instead of the page prop or a Client Component hook.
  • Using useSearchParams as though it were a Server Component API for the initial database query.
  • Assuming server-side execution automatically authorizes access to every requested dataset.
  • Sending only page number to the data layer while omitting a filter or sort value, which can produce stale or mismatched rows.
  • Sorting or filtering one returned page in the browser while implying the operation covers all records.
  • Assuming the table knows the total or whether another page exists without a count or explicit next-page signal.
  • Relying on automatic page-index reset in manual mode when a new filter or sort invalidates the current page.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.