← Back to Blogs
Frontend Performance Optimization: From 35% Faster Loads to Better UX

Frontend Performance Optimization: From 35% Faster Loads to Better UX

Code splitting, TanStack Query caching, and render discipline that cut WealthHat dashboard load times by 35% — practical patterns for React apps at scale.

Performance is a product feature

Users do not care about your bundle analyzer screenshot. They care whether the portfolio dashboard feels instant when markets move.

While optimizing WealthHat's advisor portal, we targeted initial page load and interaction latency. The result: ~35% reduction in initial load time on core dashboards through code splitting, smarter data fetching, and render discipline.

At TheFoundersLab, similar work improved Lighthouse performance and accessibility scores by ~25% through strategic caching and component memoization. Performance wins compound across retention and support load.


Measure before you optimize

We started with real data:

  • Web Vitals in production (LCP, INP, CLS)
  • Route-level bundle analysis via @next/bundle-analyzer
  • React Profiler on heaviest dashboard views
  • Network waterfalls for aggregation endpoints

Guessing is expensive. One dashboard route was 420KB gzipped because it imported an entire charting library for a single sparkline.


Code splitting that matches user journeys

Not every route needs every dependency. We split by route and by interaction:

import dynamic from "next/dynamic";

const AllocationChart = dynamic(
  () => import("@/features/portfolio/AllocationChart"),
  { loading: () => <ChartSkeleton />, ssr: false }
);

Heavy visualizations loaded only when the user navigated to analytics. Skeletons preserved layout stability (good CLS).

Rule: if a component is below the fold or behind a tab, defer it.


Data fetching: TanStack Query as a performance layer

Fetching is usually the bottleneck, not React reconciliation. TanStack Query gave us:

  • Stale-while-revalidate — instant cached data, background refresh
  • Request deduplication — multiple widgets, one network call
  • Prefetching on hover for likely next routes
export function usePortfolioSummary(clientId: string) {
  return useQuery({
    queryKey: ["portfolio", clientId, "summary"],
    queryFn: () => fetchPortfolioSummary(clientId),
    staleTime: 60_000,
    gcTime: 5 * 60_000,
  });
}

This pattern also reduced data aggregation latency by ~40% perceived at the UI layer because advisors saw cached snapshots immediately while fresh data synced.


Render discipline in React 18+

Common fixes that mattered:

  1. Colocate state — do not lift filter state to a parent that re-renders the entire table
  2. Memoize expensive derived data with useMemo, not every callback with useCallback
  3. Virtualize long lists — financial transaction histories can be thousands of rows
  4. Avoid anonymous inline objects passed to memoized children when profiling shows churn

We profiled before memoizing. Premature React.memo everywhere adds complexity without gains.


Asset and font strategy

  • Serve images in WebP/AVIF with explicit dimensions
  • Subset fonts and use font-display: swap
  • Tree-shake icon libraries — import named icons, not entire packs

At Timbu, optimizing CSS and Tailwind usage reduced stylesheet bloat by ~20% while keeping design consistency.


Performance budgets in CI

We set soft budgets per route (JS KB, LCP threshold). Pull requests that regressed beyond tolerance required justification. This cultural shift mattered as much as the technical work.


Key takeaways

  1. Profile production first — optimize what users actually hit
  2. Split code by journey, not just by folder structure
  3. Treat TanStack Query (or similar) as a caching performance layer
  4. Pair bundle work with data-fetch strategy — they win together