The React app performance checklist I run before every launch
A pre-launch checklist to make sure your React app loads fast on real devices, on real networks — not just on a developer laptop with fiber.
Your app feels fast on your M-series MacBook on a fiber connection. Your users are on a mid-range Android on 4G in a building with one bar. The gap between those two experiences is where most React apps live — and where most launches quietly underperform. Here is the exact checklist I run before every launch.
Measure on real conditions first
Open Chrome DevTools, throttle to Slow 4G and 4x CPU slowdown, then load the page. That is your real baseline. Anything you measure without throttling is a vanity number.
If you have real-user monitoring already, use the 75th percentile (p75) on mobile. The median lies — half your users are slower than it.
The non-negotiables
- LCP under 2.5s on the throttled profile
- CLS under 0.1 — no layout shift after first paint
- INP under 200ms — interactions feel instant
- Total JS under 200KB gzipped on the landing route
- Fonts loaded with `font-display: swap` and a system fallback stack
- Images served as WebP or AVIF with explicit
widthandheight
If you fail any of those, the rest of the list will not save you. Fix them first.
The high-leverage fixes (in order)
1. Code-split the routes
Every route should be its own chunk. In TanStack Start and Next.js this is the default; verify it actually happened by inspecting the network tab on a fresh load. A common regression: a shared layout imports a heavy chart library, which pulls it into every route's chunk.
2. Lazy-load everything below the fold
The hero section ships in the initial chunk. Everything else — testimonials, FAQ, footer-heavy widgets — should be React.lazy() or dynamic import. If a component is not visible on first paint, it does not need to block first paint.
3. Defer third-party scripts
Analytics, chat widgets, A/B test runners, and pixels are almost always the single biggest cause of poor INP. Load them with defer or after load, never with async in <head>. If a vendor refuses to support deferred loading, that is a vendor problem disguised as a performance problem.
4. Strip the libraries you forgot about
Run a bundle analyzer. Sort by gzipped size. The usual suspects:
- Moment.js — replace with
date-fns(tree-shakeable) or nativeIntl - Lodash — import per-function (
lodash/debounce), never the whole library - Icon sets —
lucide-reactand@radix-ui/react-iconsare fine if you import only the icons you use; the wildcard re-export will betray you - Chart libraries —
rechartsandchart.jsare large; lazy-load the dashboard route
5. Reserve space for images
Every <img> must have width and height attributes (or a CSS aspect-ratio container). Without them you get a layout shift the moment images load — and your CLS score collapses.
6. Preload the hero
For the LCP image, add <link rel="preload" as="image" href="..." fetchpriority="high"> and an fetchpriority="high" attribute on the <img> itself. This is a 200–600ms win on most landing pages and costs you nothing.
The diminishing-returns zone
After the above, the next fixes get expensive fast. Server-side rendering, edge caching, partial hydration, React Server Components — all real wins, all justifiable, but only if the non-negotiables are already green. Optimizing your hydration strategy while shipping a 4MB hero image is theater.
Most of the wins are subtraction, not optimization. Delete first. Measure second. Optimize third.
The launch-day check
Right before you flip DNS, run a fresh WebPageTest from a slow location with a low-end device profile. Check LCP, CLS, INP, and the waterfall. If anything is red, the launch waits a day. A slow launch is harder to undo than a delayed one.
Common questions
- What is a good LCP for a React app?
- Under 2.5 seconds on the 75th percentile of real-user traffic, measured on mobile. If your synthetic test is under 2.5s but field data is over 4s, you are testing on the wrong device — re-test on Slow 4G with 4x CPU throttling.
- Is React slow for performance-critical apps?
- React itself is rarely the bottleneck under 1 MB of components. The usual culprits are unoptimized images, blocking third-party scripts, and unused JavaScript shipped in the main bundle. Fix those before blaming the framework.
- Should I use Next.js or TanStack Start for performance?
- Both are competitive when configured well. TanStack Start has a smaller default footprint and a more predictable router; Next.js has a deeper image and font story out of the box. Pick on routing and DX, not benchmark deltas.
- How do I know which JavaScript to remove?
- Run `vite-bundle-visualizer` (or the equivalent for your bundler) and sort by gzipped size. The top three offenders almost always include a date library, an icon set, and a chart library you import wholesale instead of by entry point.
Oxymore is a one-person studio shipping MVPs, landing pages, React apps and Telegram bots for founders who would rather move than meet.
Last updated