PubGenius Logo

BLOG

Building SaaS on React: Performance Lessons from 10 Builds
tag iconDev Advice

Building SaaS on React: Performance Lessons from 10 Builds

Kevin Stubbs
Written by Kevin Stubbs
Co-founder | CEO

After shipping ten production SaaS products on React, the same performance mistakes keep surfacing. Here's what we learned, so you don't have to learn it the hard way.

  • 40.6% of all websites use React, the dominant SaaS frontend framework

  • 1s delay in load time reduces conversions by up to 7%

  • 53% of mobile users abandon a site that takes over 3 seconds to load

React is not slow. But React applications built without discipline absolutely are. After shipping ten production SaaS products, from solo-founder MVPs to multi-tenant platforms serving tens of thousands of users, we've seen the same performance bottlenecks surface again and again, at every scale.

This isn't a theoretical post about React internals. It's a field report. Each lesson below comes directly from a real React SaaS development project, with the specific mistake, the fix, and the measurable outcome wherever we have it. The goal is simple: if you're building a SaaS product on React right now, skip the mistakes we made.

The 10 lessons

1. Re-renders were killing our dashboard before we understood why 

On our third build, a project management SaaS, the dashboard became visibly sluggish by sprint two. The culprit wasn't complex logic. It was a deeply nested component tree where a state update at the top level triggered re-renders across 40+ child components, most of which hadn't changed. We were passing object literals and inline arrow functions as props without a second thought, creating new references on every render cycle. The fix was systematic: React.memo on pure display components, useMemo for derived data, useCallback for event handlers passed as props. The React DevTools Profiler made the problem visible within 20 minutes. We just hadn't been looking.

2. Global state was the wrong default for everything

On builds four and five, we defaulted to Redux for all state, UI state, server state, form state, and ephemeral modal state. The store became enormous. Every action triggered re-subscriptions across components that had no business caring about that state. We were using a sledgehammer where a useState call would have been sufficient.

The shift was conceptual before it was technical: server state (data from APIs) belongs in a dedicated library like TanStack Query or SWR, not in Redux. Local UI state belongs in component state. Only genuinely global, cross-cutting client state, auth, feature flags, and theme belong in a global store. Applying this three-bucket model alone cut our Redux store size by roughly 70% on the next build.

  • Stat: According to the 2024 State of JS survey, TanStack Query is now used in over 31% of React projects, reflecting widespread recognition that server state needs a dedicated solution. Source: State of JS, 2024.

3. Bundle size didn't matter until it really did

Build six was an analytics SaaS. The initial JS bundle hit 2.8MB uncompressed. First Contentful Paint was 4.1 seconds on a standard broadband connection. We'd added dependencies freely, a charting library, a date picker, a rich text editor, without thinking about what ended up in the main bundle.

Running source-map-explorer and webpack-bundle-analyzer was a wake-up call. The charting library alone accounted for 600KB. We replaced it with a lighter alternative and lazy-loaded the rich text editor behind a dynamic import. Bundle dropped to 940KB. FCP fell to 1.7s.

  • Result: Bundle reduced from 2.8MB to 940KB, a 66% reduction. FCP improved from 4.1s to 1.7s. Lighthouse performance score moved from 41 to 79.

4. Picking the wrong rendering strategy cost us SEO for six months

Build Seven was a B2C SaaS with heavy content marketing ambitions. We built the entire thing as a client-side React SPA, including the marketing pages and blog. Six months in, organic traffic was flat despite consistent content output. An SEO audit revealed the problem: Googlebot was seeing empty HTML shells with JavaScript that hadn't executed. Our content didn't exist as far as the crawler was concerned.

We migrated marketing and content pages to Next.js with static site generation (SSG). The core app stayed as a client-rendered SPA behind auth. Within three months of the migration, indexed pages increased by 340% and organic sessions doubled.

5. Context API at scale is a trap

React Context is elegant for passing data through a component tree without prop drilling. It's also a performance footgun if you put frequently-changing data in it. Every component that consumes a context will re-render whenever any value in that context changes, regardless of whether it uses the changed value. On build eight, we put user preferences, notifications, and live activity data into a single context. Any real-time update triggered a cascading re-render across the entire app.

The solution was context splitting, one context per domain, with values that change at different rates kept strictly separate. Real-time data moved to a dedicated subscription model using Zustand. The lesson: Context is great for low-frequency, read-mostly data. It's the wrong tool for anything that updates frequently.

6. Image handling was an afterthought until it broke production

Build five was an asset-heavy SaaS for creative teams. Users uploaded images constantly, profile photos, project thumbnails, and attachments. We rendered them as raw <img> tags with no sizing constraints, no lazy loading, and no format optimization. On a typical project page, the browser was fetching 40+ uncompressed images at original resolution.

Moving to Next.js Image component (with automatic WebP conversion and lazy loading) and adding CDN-level image resizing via Cloudflare cut median page weight by 78% on image-heavy pages. Cumulative Layout Shift, which had been destroying our Core Web Vitals score, dropped to near zero.

7. Waterfall API calls compounded on every page load

A common pattern in early React SaaS builds: a parent component fetches data, renders children, each of which then fetches its own data. On build nine, a single dashboard page was triggering seven sequential API calls before anything meaningful was rendered. Each call waited for the previous response before firing. On a 100ms average API latency, that's 700ms of waterfall overhead before a single pixel of real content appeared.

The fix was to request parallelization, using Promise. All to fire independent fetches simultaneously, and restructuring data fetching with TanStack Query's parallel queries to eliminate parent-child fetch dependencies wherever possible.

8. Virtualizing long lists was non-negotiable at scale

Build ten was a CRM SaaS. Power users had contact lists in the thousands. We were rendering all of them as DOM nodes simultaneously. On a list of 5,000 contacts, the initial render took 3.8 seconds and scrolling was visibly janky at 15-20fps. The DOM contained over 80,000 nodes for a single page.

Implementing TanStack Virtual (formerly react-virtual) reduced the rendered DOM to only the visible rows plus a small overscan buffer. Render time fell from 3.8s to 190ms. Scroll performance became 60 fps consistently. The only trade-off: slightly more complex implementation logic, well worth it above a few hundred rows.

9. Error boundaries saved us from blank screens in production

On three of our first five builds, a runtime error in a third-party component or an unexpected API response shape caused the entire React tree to unmount, leaving users with a blank white screen and no feedback. We weren't wrapping anything in error boundaries because in development, the error overlay masked the problem. In production, it was catastrophic for trust.

Systematic error boundary placement, at the route level, at the widget level for independent dashboard sections, and around all third-party integrations, contained failures to the smallest possible blast radius. A broken chart widget no longer took down the entire dashboard. Users saw a graceful fallback instead of a white screen.

10. Accessibility was always the last thing, and always regretted it

On every build where accessibility was treated as a post-launch concern, it became an expensive retrofit. Custom dropdown menus that weren't keyboard navigable. Modals that didn't trap focus. Form errors that screen readers never announced. The fixes weren't technically hard, but making them retroactively required touching hundreds of components.

The operational change was straightforward: install eslint-plugin-jsx-a11y from day one, use Radix UI or Headless UI for interactive primitives (which ship with ARIA support built in), and run an axe-core accessibility audit in CI before every release. Accessibility built in from the start costs roughly 10% more development time. Retrofitted, it costs 3–5× more.

The performance metrics that actually matter for SaaS

Vanity performance metrics don't move the revenue needle. These are the four Core Web Vitals and SaaS-specific benchmarks that correlate with business outcomes based on Google's research and our own builds.

Metric

Target

Largest Contentful Paint (LCP)

< 2.5s

Interaction to Next Paint (INP)

< 200ms

Cumulative Layout Shift (CLS)

< 0.1

Gzipped initial payload (JS bundle)

< 170KB

What we'd do differently from building one

Mistakes made on build 1 that still appear on build 8

  • Treating performance as a phase-two concern, it compounds technically and becomes harder to fix at scale

  • Installing every well-known library without checking bundle impact, lodash, moment.js, and full icon sets, routinely adds 200–400KB unnecessarily

  • Single monolithic global store for all state, server, UI, ephemeral, and form state all mixed together

  • No performance budget is enforced in CI, bundles grow silently without automated checks

  • Client-side rendering for marketing pages that needed organic search traffic

  • No structured logging or error monitoring was set up before the first production deploy

The bottom line

React SaaS development rewards teams that treat performance as a discipline, not a feature. The lessons above aren't edge cases, they're the most common failure modes we've seen across real production builds, at every stage from MVP to mature multi-tenant platform.

The good news: every one of these problems is solvable, most with well-established patterns that take a day or less to implement correctly. The cost of doing it right from the start is almost always lower than the cost of diagnosing and fixing it under production load with paying customers waiting.

Start with the React DevTools Profiler. Run Lighthouse against your staging environment this week. Set a bundle size budget before your next dependency lands in the build. These three habits alone will separate your SaaS from the majority of React applications shipping in production today.