Reducing Client JavaScript

Move Work to the Server

  • Keep 'use client' at the leaves; a client layout pulls everything below it into the bundle.
  • Format dates/prices, sort and filter lists, render markdown in Server Components.
  • Pass server-rendered JSX as children into client wrappers instead of importing it.
  • Use Server Actions for forms instead of client fetch logic + validation libraries.
  • Prefer CSS (:hover, details/summary, transitions) over JS for simple UI.

Long Lists

Rendering 2,000 order rows creates 2,000 DOM nodes. Paginate on the server, or virtualise on the client (render only rows in view):

Example

The maths behind list virtualisation (what libraries like @tanstack/react-virtual compute):

function visibleRange(scrollTop: number, viewport: number, rowHeight: number, total: number, overscan = 3) {
  const first = Math.floor(scrollTop / rowHeight);
  const count = Math.ceil(viewport / rowHeight);
  const start = Math.max(0, first - overscan);
  const end = Math.min(total - 1, first + count + overscan);
  return { start, end, rendered: end - start + 1, offsetY: start * rowHeight };
}

const total = 2000, rowHeight = 48, viewport = 720;
for (const scrollTop of [0, 4800, 95_000]) {
  console.log('scrollTop', String(scrollTop).padStart(6), JSON.stringify(visibleRange(scrollTop, viewport, rowHeight, total)));
}
console.log('DOM rows instead of', total, '-> about', visibleRange(4800, viewport, rowHeight, total).rendered);

Output

scrollTop      0 {"start":0,"end":18,"rendered":19,"offsetY":0}
scrollTop   4800 {"start":97,"end":118,"rendered":22,"offsetY":4656}
scrollTop  95000 {"start":1976,"end":1997,"rendered":22,"offsetY":94848}
DOM rows instead of 2000 -> about 22