⚡ Stop the fade-in wrapper hiding content that has already painted - #4934
Draft
joshbermanssw wants to merge 1 commit into
Draft
⚡ Stop the fade-in wrapper hiding content that has already painted#4934joshbermanssw wants to merge 1 commit into
joshbermanssw wants to merge 1 commit into
Conversation
useInView returns false before its IntersectionObserver first reports, and the effect wrote that false into state — so on hydration every block on the page flipped to opacity-0 and faded back over 300ms. Chrome will not accept an opacity-0 element as an LCP candidate, so the LCP element was disqualified until the fade finished. Replace it with a useFadeIn hook that stays "pending" until the observer actually reports, so a block can only ever go hidden -> visible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
No linked issues found. Please add the corresponding issues in the pull request description. |
joshbermanssw
marked this pull request as draft
July 27, 2026 00:47
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
On hydration every
V2ComponentWrapperflipped toopacity-0and faded back over 300ms — 39 of them on/events/ai-for-business-leaders, including the one wrapping the LCP element. Chrome will not accept anopacity: 0element as an LCP candidate, so the LCP element stayed disqualified until the fade finished.Why
useInViewreturnsfalseon the first client render, before its IntersectionObserver has reported. The old effect wrote thatfalsestraight into state, which retroactively hid content that had already painted from SSR.PageSpeed on that page shows FCP 1.3s against LCP 2.9s, with Speed Index 1.7s and CLS 0 — the viewport is visually settled more than a second before Chrome records the LCP, which is the signature of an element that is on screen but ineligible.
How
useFadeInreplaces theuseInView+ effect +useStatetrio with a three-state machine. A block stays"pending"(no opacity class) until the observer's first report, so it can only ever go hidden → visible, never visible → hidden.Reviewer notes
"pending"and get no opacity class at all, which is the fix."hidden", and fade in on scroll. The user cannot see that transition and it cannot affect LCP.display. Deliberately kept —display: nonewould disqualify the LCP element just the same, and would drop blocks out of layout and break the current CLS of 0.framer-motiondropped from this component. NegativerootMarginon a rawIntersectionObservermatches what itsmarginoption did.fadeInMarginis retypedstring; no caller passes it, so every block keeps the-100pxdefault.Verify
LCP is the metric to watch, not the overall score. Single PageSpeed runs on this page fluctuate heavily —
.lighthouserc.jsonis still"numberOfRuns": 1on the desktop preset, so compare medians over ~5 mobile runs before and after.