Verified against v0 · 2026-07-20
Build a data-table dashboard with server-side filters and charts
A v0 brief for a data-table dashboard built against an explicit fetchRows contract, with sorting, filtering, and pagination all server-driven and the current view synced to the URL so a shared link reproduces it exactly.
The prompt
Ready to copy — highlighted parts are example details you can swap.
Generate a DataDashboard page for a Next.js App Router project using shadcn/ui, Tailwind CSS, and TanStack Table for the data grid.
DATA CONTRACT
Assume the actual data comes from a function I'll wire up myself — do not fetch from a real API inside this component. Define a typed fetchRows(params: { page: number; pageSize: number; sort: SortState; filters: FilterState }) => Promise<{ rows: { id: string; orderNumber: string; customer: string; status: "pending" | "shipped" | "delivered"; total: number; placedAt: string }; totalCount: number }> function signature as a prop, and build the whole dashboard against that contract, with a working mock implementation of it for preview purposes clearly marked as mock data to swap out.
TABLE REQUIREMENTS
- Columns: Order #, Customer, Status (as a colored badge), Total, Placed date. Support column sorting (single-column, ascending/descending toggle) that calls fetchRows again with the new sort state — sorting must not be done client-side against only the current page's rows, since that would silently sort a subset and call it a full sort.
- Server-side pagination, not a client-side slice of a large in-memory array — tens of thousands rows is the realistic scale, and loading all of them into the browser at once to paginate client-side defeats the entire point of a server-driven table.
- a date range for placedAt, and a status select as filter controls above the table — a date-range picker and one or more select dropdowns. Applying a filter resets to page 1 and calls fetchRows with the new filter state; filters and pagination must never disagree about what's currently shown.
- Sync the current page, sort, and filter state to the URL's search params, so a reload or a shared link reproduces the exact same view — use useSearchParams and router.replace, not component state alone, or a reload silently resets the user back to an unfiltered page 1.
CHARTS
Above the table, 2 summary charts built with Recharts (or another chart library already common with shadcn/ui) reflecting orders per day over the filtered range, and order count by status — these should update when a filter changes, using the same filter state as the table, not a separate unfiltered dataset that quietly disagrees with what the table below is showing.
LOADING AND EMPTY STATES
Show a skeleton table matching the real table's column structure while fetchRows is pending — not a generic spinner that causes a layout jump when real rows arrive. Show an explicit "No results match these filters" state, distinct from the loading state, when fetchRows resolves with zero rows.
CONSTRAINTS
Type everything — the row shape, the filter state, the sort state — with explicit TypeScript interfaces, no inline any. dense, data-forward, minimal color beyond the status badgesCustomize
Optional — swap in your own details for the highlighted parts above.
Why this works
The explicit fetchRows prop contract addresses v0's actual scope correctly: v0 generates the component and page layer, not a backend, and a prompt that just says "build a dashboard with real data" forces it to either invent a call to an API that doesn't exist yet — which fails the instant the component renders — or silently switch to a hardcoded array of fake rows without saying so. Defining the fetch function's exact signature up front, with an explicitly-labeled mock implementation for preview, is what makes the generated component genuinely a drop-in piece to wire a real backend into, rather than something that has to be gutted and rebuilt once real data enters the picture. Requiring server-side sort, pagination, and filtering rather than client-side operations on the current page targets a specific and easy-to-miss correctness bug: sorting only the rows currently loaded on one page produces a result that looks like a sort — the visible rows are in order — but is not actually a sort of the full dataset at all. This exact mistake is common in generated data-table code because client-side sorting is genuinely simpler to write, and TanStack Table supports both modes through a very similar-looking API, so nothing in the resulting code makes the mistake visually obvious without explicit instruction pointing at server-side behavior specifically. Syncing page, sort, and filter state to the URL rather than keeping it purely in component state closes a real UX gap specific to data-heavy dashboards: a colleague sharing a link to "orders shipped last week, sorted by total" only works if that view state actually lives in the URL. A generated dashboard that keeps this state purely in React state resets to page 1 with no filters on every reload or share, silently breaking the exact workflow — bookmarking or sharing a specific filtered view — that a table like this exists to support in the first place.
Verified against
v0 v0 by Vercel (Next.js + shadcn/ui default stack) · 2026-07-20
Changelog
- 2026-07-20 — Initial publish, verified against v0 with an explicit fetchRows contract and URL-synced table state.
Need this built into your business?
If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.
EXPLORE CUSTOM SOFTWARE
