Your server sends the HTML. The browser receives it. But the user sees a blank white screen for another 800 milliseconds. That gap is the Hydration Tax, and it is where your Largest Contentful Paint (LCP) score goes to die.
Search engines index what they can see immediately. Modern AI crawlers and even Google's initial rendering pass often skip heavy JavaScript execution. This leaves significant portions of React sites invisible to indexing. You have a fully functional app, but to the search engine, it is a skeleton with no meat.
The Hydration Tax: Where Your LCP Goes to Die
The mechanism is simple but costly. In a standard Client-Side Rendering (CSR) setup, the browser downloads the HTML shell, then fetches the JavaScript bundles. Only after parsing and executing that JS does React "hydrate" the DOM, attaching event listeners and rendering the actual content.
Worked Example: The 3.2-Second Hero Image
Consider a typical SaaS landing page built with a standard React setup.
- T=0.0s: User requests the page.
- T=0.8s: HTML arrives. It is a 4KB shell.
- T=0.8s–2.5s: The browser downloads a 450KB JavaScript bundle. It parses and compiles the code.
- T=2.5s: Hydration begins. React calculates the initial state.
- T=3.2s: The hero image component mounts. The browser requests the image file.
- T=3.8s: The image renders.
Result: LCP is 3.8 seconds. Google’s threshold for a "Good" LCP score is 2.5 seconds. You have missed the mark by 52%.
Contrast this with a static HTML site or a Static Site Generator (SSG) output. The HTML arrives with the <img> tag already present. The browser begins downloading the image immediately while parsing the rest of the DOM. On a 4G network, that same hero image often renders in under 1.5 seconds. The difference is more than speed; it's the difference between a page that ranks and a page that gets skipped.
Measuring the Invisible: TBT and Main Thread Contention

LCP is the headline metric, but Total Blocking Time (TBT) is the silent killer of user experience. TBT measures the total time during which the main thread is blocked by long tasks, preventing the user from interacting with the page.
If a JavaScript task takes longer than 50ms, it is classified as a "long task." These tasks accumulate. A 300ms analytics script, a 150ms state management update, and a 200ms third-party widget tracker create 500ms of blocking time. The page feels sluggish. Clicks register late.
Step-by-Step: Finding the Blocking Scripts
You cannot fix what you cannot see. Here is how to identify the culprits using Chrome DevTools:
- Open Chrome DevTools and navigate to the Performance tab.
- Click the record button (circle), then refresh the page. Stop recording after the load event.
- Look at the "Main" track in the timeline. Identify colored bars that span more than 50ms. These are your long tasks.
- Click on a long task to expand the "Call Tree" in the bottom pane.
- Trace the function name. If it says
evalor points to a minified file, note the source. - Differentiate the type: Is it CPU-bound (complex calculations, rendering) or I/O-bound (waiting for network)? React rendering is typically CPU-bound. Third-party scripts are often I/O-bound but block the main thread while waiting.
A common culprit is large analytics suites. They often execute complex logic on load, competing with your critical rendering path for CPU cycles. If your state management library triggers a deep re-render on mount, you are adding to the tax.
Framework Choices That Matter: Next.js vs. Vite

Your framework choice dictates your ceiling for performance. You are more than choosing a build tool; you are choosing a rendering strategy.
| Feature | Next.js (SSG/SSR) | Vite (SPA) |
|---|---|---|
| LCP Strategy | HTML + critical assets arrive first. | JS bundle must load before content. |
| Bundle Size | Larger (includes React + framework overhead). | Smaller (lighter core, but no SSR). |
| SEO Viability | High. Crawler-friendly initial HTML. | Low. Requires heavy JS execution. |
| Best For | Content-heavy sites, blogs, marketing pages. | App-centric dashboards, internal tools. |
For content-heavy sites, Static Site Generation (SSG) is non-negotiable for SEO performance. As Toptal notes, your rendering strategy is the biggest factor in how your React app performs in search. If you are publishing a blog post, SSG ensures the text and images are in the initial HTML payload. The LCP is determined by the slowest resource (usually an image), not the JavaScript execution time.
Vite is excellent for speed of development and lighter bundles, but it lacks built-in SSR. If you use Vite for a public-facing marketing site, you are essentially building a SPA that requires JavaScript to render. For app-centric sites where users log in and interact with data, LCP is less critical than TBT. Here, optimizing for interactivity matters more than initial paint. But if you are chasing organic traffic, you need the SSG or SSR approach.
Code-Level Fixes: Reducing the Critical Path
You cannot wait for a framework rewrite to fix your Core Web Vitals. You can fix them in the code you write today.
Checklist: Reducing the Critical Path
- Code-Split Non-Critical Components: Use
React.lazyandSuspenseto split your bundle. If a user lands on your homepage, they do not need the JS for your "Contact Us" form or your "Blog Archive" modal. Load those only when the user navigates to them. This reduces the initial JavaScript payload. - Defer Third-Party Scripts: Do not load analytics, chat widgets, or A/B testing tools in the
<head>. Inject them after theloadevent or userequestIdleCallback. These scripts compete with your critical rendering path. - Prioritize LCP Images: Add
fetchpriority="high"to your hero image. This tells the browser to download this image before other low-priority resources like fonts or non-critical images. - Reduce Initial Bundle Size by 20%: A 20% reduction in initial JavaScript size can directly lower TBT. Audit your dependencies. Are you importing the entire
lodashlibrary for one function? Tree-shake your imports. Remove unused code.
These changes do not require a migration. They require discipline. Every kilobyte you remove from the initial load is a millisecond you save on LCP.
Monitoring in Production: Don't Trust Lab Data

Lab data is a simulation. It uses a throttled network and a generic CPU profile. It tells you what could happen. It does not tell you what is happening.
A client had a React site with a perfect Lighthouse score of 95 in the lab. In production, their LCP p75 was 4.2 seconds. Why? The lab simulated a "Moto G4" profile. Their actual users were on mid-range Android devices with 3G connections. The JavaScript bundle was fine on the lab's emulated CPU, but on real-world low-end hardware, the parsing time spiked. The lab data was misleading because it did not account for the variance in device performance.
You need Real User Monitoring (RUM). Tools like Chrome User Experience Report (CrUX) provide field data based on real users. Google uses CrUX data for its Core Web Vitals ranking signals. If your CrUX p75 LCP is over 2.5 seconds, you are losing rankings, regardless of your Lighthouse score.
Set up RUM to track LCP and TBT for your actual user base. Review this data monthly. Performance regressions are subtle. A new analytics script added in March might not break your lab tests but could push your p75 LCP over the threshold by June. Catching this early saves you a quarter of lost traffic.
If you are struggling to balance development speed with SEO performance, our team can help you audit your React SEO performance and implement the fixes that move the needle.
Related reading: React SEO Performance Metrics · React SEO Implementation Plan · React SEO Case Study
Try it on your own site
Run the free audit on one page: real speed scores and on-page checks, with the fixes in order.
Frequently asked questions
Why does React hurt SEO?
Client-side rendering delays content visibility for crawlers that do not execute JavaScript, and hydration delays increase LCP.
Is Next.js better than Vite for SEO?
Yes, for public-facing content sites. Next.js offers SSG/SSR, which provides immediate HTML for crawlers, whereas Vite SPAs rely on JS execution.
What is the Hydration Tax?
The time gap between HTML arrival and interactive JS execution, during which the user sees a blank screen and LCP is delayed.




