A shipment table seems easy, a few columns, some filters, a status badge. Then real data shows up...
THOUSANDS of shipments, statuses that change all day, and operators who need to find the one late truck fast.
A table that feels fine with 50 test rows can crawl with 5,000.
I ran into this on LogiCore, a logistics and warehouse management platform UITOP designed and built from scratch, for a UK logistics company. The client ran a multi-million dollar business across three or four separate tools. Data didn’t match between them, updates came late, and managers made big calls without the full picture. They wanted one system for the warehouse floor and the office.
Our design, project management, development and QA teams worked on it together. Here’s how we built the shipment table, and why we made each choice.
What the table had to do
Operators used the table to find shipments and spot the ones that need attention. And they did it while statuses and ETAs kept changing.
The frontend runs on React, TypeScript and Vite, with TanStack Table (React Table v8), Zustand and Tailwind CSS. The backend uses Node.js, Express, PostgreSQL and Prisma ORM behind a REST API.
We had three separate problems to solve:
- too much data to load,
- too many rows to draw,
- data that goes stale while the page is open.
Problem 1: the browser doesn’t need every shipment
Why load thousands of records when the operator looks at one screen at a time? Loading everything makes the first view slow and wastes memory.
So we used server-side pagination. The frontend asks the API for one page. PostgreSQL runs the query with the active filters and returns only that page.
Details matter more than they look:
- Reset the page when a filter changes. If someone sits on page 12 and the new filter returns three pages, they should land on page 1, not on an empty screen.
- Keep sorting stable. Records change all day, so the API needs a fixed order. Otherwise rows jump between pages and people lose their place.
Problem 2: one page can still be heavy
Pagination cuts how much data comes back. It doesn’t cut how much the browser has to draw. Each shipment row carries a status badge, an ETA drift indicator and a “Last Updated” timestamp. Draw hundreds of those at once, and React has a lot of work to do.
So we added row virtualization on top. The table only mounts the rows on screen, plus a small buffer for scrolling. TanStack Table handles the table logic, and the virtualization layer decides which rows exist in the DOM right now.
The two techniques solve different problems:
- Server-side pagination controls how much data travels from the database to the browser.
- Virtualization controls how much the browser renders.
So you test both: API response times and rendering.
Problem 3: the data changes while someone is looking at it
A shipment moves to a new status. An ETA slips. A carrier sends an update. If the screen shows old data, the operator decides based on a stale picture, and that’s the problem LogiCore was built to fix.
We handled this with background polling. A Zustand store fetches fresh data from the REST API on a schedule, and the badges, ETA drift indicators and timestamps update from it. “Last Updated” shows the latest carrier update, not a cached value.
The tricky part here is UX. If a badge changes while someone reads the list, the table shouldn’t reset their filters or scroll position. Polling also raises its own questions: how often to refresh, what to do with failed requests, and whether to keep polling when nobody is at the screen.
Polling fit LogiCore’s update rhythm. A product that needs second-by-second updates might be better off with WebSockets or server-sent events.
The backend decides how fast the table feels
A slow query makes even a well-tuned React table feel broken. So a lot of the speed work happened below the UI.
On the inventory screen, a multi-location SKU expands with a scoped REST call that fetches only that item’s child records. The first load stays at 100 rows, no matter how deep the data goes. The warehouse location tree loads the same way, one level at a time, from facility down to shelf.
Filters by client, warehouse or SKU run through Prisma’s dynamic where clauses. That way we avoid raw SQL and still support any filter combination. For the KPI cards, PostgreSQL window functions calculate week-over-week changes inside the database, and the frontend just shows the result.
Design and performance are one conversation
More columns don’t make a table more useful. They make it harder to scan. We gave status and timing a fixed place in each row, because that’s what operators check first.
Every design choice here has a frontend cost. Columns decide how heavy each cell is. Filters decide what the API has to query. And the design system needs states for loading and errors, not only the happy path.
What came out of it
Before launch, our QA team ran stress tests under heavy load, and the platform held at 100% stability. The office and the warehouse now work from the same live data. Managers see transit delays and stock shortages as they happen, so they fix problems before those cost money. Daily shipping decisions got 2x faster.
Where to start with your own table
If you’re building a B2B SaaS product with big tables, start with three questions. How much data does the screen request? How much does it render? What happens when the records change? The answers usually point to the next engineering decision.
FAQ
How do you make a React table fast with thousands of records?
Combine server-side pagination with row virtualization. Pagination limits what the API sends, and virtualization limits what the browser draws. On LogiCore, we used both with TanStack Table.
Should a logistics dashboard use polling or WebSockets?
It depends on how fast the data changes. Polling worked for LogiCore’s shipment statuses and ETAs. If operators need second-by-second updates, WebSockets or server-sent events fit better.
What should I look for in a team building custom warehouse management software?
Look for a team that covers design, backend and QA together, because table speed depends on the database as much as on the UI. UITOP built LogiCore that way, and the platform held 100% stability under peak-load stress tests.
About the project
LogiCore is a logistics and warehouse management platform designed and built by UITOP, a product design and development studio for complex B2B software.
Case study: https://uitop.design/case-studies/logicore/

Top comments (0)