Definition
The moment the largest piece of content on a page (usually the hero heading or a big image) is painted to the screen is called LCP (Largest Contentful Paint). When this LCP is slow but the image bytes are small and the main thread is idle (TBT≈0), the bottleneck is not "download" but rather the fact that the LCP element sits inside a client component boundary, so it only paints once JavaScript hydration finishes. Lighthouse surfaces this as the "element render delay" portion of the LCP breakdown (the delay between the element being ready and it actually being painted).
To unpack the terms: hydration is the process where React later attaches events to the static HTML produced by the server, bringing it to life; TBT (Total Blocking Time) is the total time the main thread was blocked by long tasks and unable to respond to input.
Why it matters
Symptom: LCP is slow, in the 5-second range. Intuitively, "let's shrink the hero image" comes to mind. But no matter how much you compress the image, the score doesn't improve. If the LCP element is inside a "use client" component plus an animation library, the server HTML is empty or incomplete, and the final pixels only appear after the browser fetches that chunk and finishes hydration. In other words, the paint is held hostage by JavaScript execution. The target here is not the asset but "removing the client JS at that boundary." Get the diagnosis wrong and you can waste days on image optimization while the score stays exactly the same.
How it works
Lighthouse splits LCP into four segments.
| Segment | Meaning | If this is large |
|---|---|---|
| TTFB | Up to the server's first byte | Server/network |
| load delay | LCP resource discovered late | Late preload/discovery |
| load time | LCP resource download time | Large image |
| render delay | The resource is ready but the delay until painted | Hydration/render block |
If the image is small and TTFB is normal but render delay is large, that's a signal the LCP element is inside a client boundary, so the download plus execution of that chunk is blocking the paint. Rendering it as a server component means the final markup is already in the first HTML, so there's no need to wait for hydration.
Practical application
-
Check the breakdown — look at the render delay value in Lighthouse's LCP insight. If it's large, suspect hydration.
-
Server-ify the LCP surface — convert the hero and top story sections to server components, and if you need motion, replace the animation library with CSS (scroll-driven animations, etc.).
-
Isolate heavy providers — push heavy context providers such as auth/query/motion/service worker down into an app-surface-only layout rather than the top level, so they drop out of the static landing chunk entirely.
-
If state is truly required, minimize it — for a one-shot entrance effect, keep only a single IntersectionObserver fragment on the client.
Trade-offs
- Server-ifying + switching to CSS is a large diff, but it reliably removes the whole library from the landing chunk, so the effect is certain. By contrast, a partial optimization like the library's
LazyMotionis a small diff but still leaves that library in the chunk. - Measurement trap: the Lighthouse simulation (Lantern, which assumes a 4x CPU slowdown) creates a lower bound that can differ greatly from the actually observed LCP. Past a certain point it's the simulation's lower bound, not the code, that stops it from dropping further — at that point stopping is the right call.
When not to use this
- If the LCP element really is a large image so load time dominates, the answer is image optimization, not the prescription here (server-ify). First confirm which segment it is via the breakdown.
- On app screens that genuinely need strong interaction up top (dashboards, etc.), forcibly removing client JS breaks functionality — focus on landing/marketing surfaces.
Common mistakes
- Assuming slow LCP = image problem and never looking at render delay.
- Leaving a brand mark that displays a large source image at small size — the framework auto-preloads the
<img>in the initial HTML, loading big bytes onto the critical path (a dedicated small asset is needed). - Continuing to spend time optimizing after hitting the simulation's lower bound (over-investment).
Related concepts
- largest-contentful-paint — the LCP metric itself and general optimization
- critical-rendering-path — the HTML→render path and blocking factors
- scroll-driven-narrative-layout — a server-friendly layout that replaces motion with CSS
- lighthouse-measurement-accuracy — the difference between the simulation's lower bound and observed
- readonly-viewer-bundle-splitting — removing heavy dependencies per surface