Why does switching from client-side rendered hero text to server-rendered HTML sometimes worsen LCP instead of improving it?

Server-rendering the hero text moves the text into the initial HTML response, which in principle lets the browser paint it as soon as the document is parsed, without waiting on JavaScript to fetch data and render. That’s a real structural advantage, and it’s why SSR is generally recommended for LCP-critical content. But “in principle” is doing a lot of work in that sentence. If the SSR implementation makes the server slower to respond (because it now has to fetch data and render markup before it can send any bytes at all) or adds render-blocking weight to the response (a larger initial HTML payload, synchronous blocking data fetches on the server, additional critical CSS/JS that has to load before the browser can paint) that the client-side version didn’t have, the net effect can be a slower Time to First Byte or a slower path to first paint that outweighs the rendering-side gain. The architecture is “better” in the sense that it removes one bottleneck, but it can simultaneously introduce a worse one, and LCP only measures the net result.

Mechanism

Under client-side rendering, the browser typically gets a minimal HTML shell very quickly, since the server doesn’t need to do much beyond serving static or lightly templated markup. The tradeoff is that the actual hero content isn’t in that shell: the browser has to download and parse JavaScript, execute it, fetch whatever data the hero needs, and only then render and paint the text. That rendering path is slow in a way that’s visible and well understood, which is exactly why SSR gets recommended as the fix, per web.dev’s guidance on rendering strategies: SSR’s core LCP advantages are that image and text resources become discoverable directly from HTML source, and the page’s content doesn’t have to wait on a JavaScript round-trip to exist.

The failure mode appears when the move to SSR changes what the server has to do before it can send that first byte. A CSR app’s server could get away with serving a static shell almost instantly because the actual work was deferred to the client. Once that work (fetching the hero’s data from a database or an API, running the rendering logic, assembling the full HTML string) moves to the server, and it has to complete before the response can start, the server’s response time goes up. If that data fetch is slow, or if it wasn’t optimized because under CSR it happened in the background after paint (so no user ever waited on it directly), TTFB under the new SSR path can become a bottleneck that didn’t functionally exist before, because previously the browser was busy doing other things (parsing, loading JS) while that fetch happened in parallel rather than serially blocking the response.

A second common failure is payload size and blocking resources. A naive SSR migration sometimes ships a much larger initial HTML document (fully rendered markup for content that used to arrive incrementally via client-side interactivity), and if that HTML is generated synchronously as one large blocking operation, or if the SSR framework’s default setup still requires a large hydration bundle to load and execute before the page is interactive or before other resources unblock, the effective time to first paint can end up similar to or worse than before, just with the bottleneck relocated from “client-side render wait” to “server response wait” or “resource contention on the initial document.” LCP doesn’t care where in the pipeline the delay lives, it only measures the total elapsed time until the largest content element paints, so a naive SSR conversion can trade one bottleneck for a comparably sized one and show no real improvement, or a regression if the new bottleneck is actually worse than the old one for that specific page and traffic profile.

A worked example of the tradeoff

Suppose a hypothetical site, Site X, measures its CSR hero at a 2.1-second LCP: roughly 0.3 seconds to get the initial HTML shell, then close to 1.8 seconds spent downloading and executing JavaScript before the hero text paints. The team migrates to SSR, expecting LCP to drop toward that 0.3-second shell time. Instead, LCP lands at 2.6 seconds, worse than before, because the new SSR path added a synchronous database fetch for the hero’s content before the server could send any bytes at all, pushing Time to First Byte out to 2.0 seconds on its own, and the JavaScript bundle for hydration still has to load afterward.

Once the team switches that fetch to run inside a streaming SSR response, letting the server send the HTML shell immediately and stream the hero markup in as soon as the fetch resolves rather than blocking the entire response on it, TTFB drops back to roughly 0.4 seconds and LCP lands around 1.1 seconds, better than the original CSR version. The architectural label (SSR) didn’t determine the outcome either time; the underlying cost of the newly-introduced server-side work did.

Practical implication

The fix is to make sure the SSR path itself is fast, not to treat “SSR versus CSR” as a settled binary choice. The standard mitigation, described generically across SSR frameworks including React’s and Next.js’s documentation, is streaming SSR: rather than waiting for the entire page’s data and markup to be ready before sending any response, the server begins sending HTML as soon as the first chunks are ready and streams the rest as it becomes available. This gives a TTFB much closer to what a CSR shell could achieve, while still getting real content (including the hero) into the HTML stream early, rather than forcing a choice between “wait for everything server-side” and “send nothing until the client fetches it.”

Practically, that means auditing the actual data-fetching path introduced by the SSR change: is the hero’s data fetch fast and cacheable, is it happening in parallel with other server work rather than serially blocking the response, and is the server itself (or edge/CDN layer, if rendering happens there) fast enough that the added computation doesn’t meaningfully raise response time. Edge rendering, where the SSR computation runs physically closer to the user and often against a faster runtime than a traditional origin server, is a common way to keep the added server-side work from dominating the response time budget. The broader point is that SSR removes a client-side bottleneck by construction, but it introduces a server-side one by construction too, and whether the swap nets out as an improvement depends entirely on whether that new server-side cost was engineered to be smaller than the bottleneck it replaced, not on the architectural label attached to the approach.

Leave a Reply

Your email address will not be published. Required fields are marked *