Frontend modernization for data-heavy B2B teams
Query builders, virtualised grids, and large-dataset charts that stay responsive when the result set is a million rows.
Domain context
Data-product frontends break in ways marketing dashboards never do. A grid that re-renders from scratch every time a user reorders a column is unusable at 200 columns. A chart library that ships every point to the DOM falls over at 100,000 rows, never mind a million. A query builder that round-trips on every keystroke punishes the analyst who actually knows what they want. A saved view that doesn't survive a schema change quietly loses customer trust the first time a column gets renamed. We rebuild these surfaces assuming the data is large, the user is sophisticated, and the screen will be open for the whole working day. Performance budgets get framed in milliseconds-per-keystroke, not page-weight kilobytes. The first thing we measure on most engagements is keystroke-to-paint, because that's the number the analyst feels.
Why this combination
Most modernization shops will hand back a faster build pipeline and a cleaner component library. Data-product modernization needs more. It needs a virtualisation strategy that survives column reorder, freeze, resize, and hide without losing scroll position. It needs a charting layer that downsamples on the client cleanly so a million-point time series renders without dropping interaction. It needs a query-builder UX an analyst can actually fall in love with: keyboard-first, schema-aware, with a previewable result count before they hit run. None of this comes out of a framework. It comes from engineers who've shipped these surfaces before and know which abstractions break first.
“We rebuilt a 240-column saved-views grid that had become unusable above 50k rows. The new surface holds 60fps scroll and column reorder at 2M rows on the analyst's laptop, with per-user view persistence.”
Frequently asked
- We start with the third-party. AG Grid, TanStack Table, and Glide Data Grid each cover the common 80% well. The decision to roll our own only comes when there's a specific behaviour the customer cares about, usually around column-level interactions, custom cell rendering at scale, or a peculiar permission model, that the off-the-shelf component can't do without forking. We document the trade-off explicitly before writing a line of grid code.
- We default to a canvas-based renderer (uPlot, ECharts, Plot with the right config) and treat downsampling as a first-class layer, not a hack. The client receives a downsampled series matched to viewport width, with the option to request raw points on zoom. SVG-based libraries get vetoed for any series above ~5,000 points. The DOM isn't the right primitive for analytical charts at scale.
- Keyboard first. Schema-aware autocomplete that matches IDE expectations. A live result-count preview before the user commits to running the query, so they can iterate without waiting for the warehouse. Saved queries that survive schema evolution. The opinionated bit: we don't try to hide SQL. Analysts who use these tools all day know SQL, and the right interface meets them there rather than translating to a lossy visual builder.
- Permissions are enforced server-side, but the UI has to surface them coherently. Masked columns show a deliberate placeholder rather than an error. Restricted rows are filtered with a count of how many were hidden. Saved views remember the permission context they were created in. We work with the backend team to ensure the permission decision flows through to the client as structured metadata, not just as a 403.
- Saved views and shared dashboards are versioned against the schema they were authored on. When a column is renamed or removed, views that reference it are marked as needing migration rather than silently breaking. The user sees a clear notice with a one-click remap to the new column. Sharing a dashboard captures the schema version, so a colleague opening the link sees the same numbers the author did, even months later.