perf(landing): below-fold render + LCP/CLS/compositing hardening#271
Merged
Conversation
…n FaqItem (1-3/4) Kai lag-analysis 2026-05-25: 12 matchMedia subscriptions (one per FaqItem) collapsed to 1 in FaqSection. FaqItem wrapped in React.memo; handleToggle stabilised via useCallback + index passed as arg to avoid per-render arrow wrappers. backdrop-blur-sm removed from section root (Fix 2, FAQ part).
…oot (2/4) Kai lag-analysis 2026-05-25: backdrop-blur-sm on section roots forces GPU compositing layers during scroll. Inner accordion card retains backdrop-blur-sm (visually load-bearing). Section root is now flat bg.
…pattern (4/4) Kai lag-analysis 2026-05-25: ComparisonTable has no hooks/state/handlers and does not need to be bundled inside BelowFoldSections client chunk. Moved to comparisonTable: ReactNode prop; LandingContent passes <ComparisonTable />. Full RSC gain requires LandingContent server-shell split (follow-up for Kai).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…Server Component <h1> now paints from SSR HTML without waiting for hydration. HeroCta owns the track() onClick call as a thin 'use client' boundary.
…m, guard transition under reduced-motion Reduces compositing cost on mobile. motion-reduce:transition-none prevents color-transition animation for users who opted out. Test updated to match blur-2xl.
…nd cards Removes 4px blur layers that trigger GPU compositing on mobile with no visible effect on zinc-950 bg. Replaced with solid semi-opaque bg tokens. Affected: SocialProofStrip, HowItWorks, FaqSection (section + inner accordion), WhyVoidPay cards (×3), AudienceCard. ComparisonTable already done (cc2dc46). Snapshots updated.
Stops the 10s interval while the tab is backgrounded so it stops re-rendering the InvoicePaper subtree when the user is not watching. Composes independently with the existing manual pause/resume and hover-pause — visibility state is a separate isHidden flag, not overwriting isPaused. Tests: TDD red→green verified.
…default already 'ethereum' uiSlice.ts initializes networkTheme: 'ethereum'. The useEffect mutation on mount was redundant. All other pages (create, pay, invoice) set their own theme explicitly from networkId — landing's removal is safe. Also consolidates below-fold placeholder to pass BelowFoldPlaceholder as skeleton prop (single consistent placeholder with min-h-[300vh] both pre- and post-trigger, eliminating CLS on the swap).
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.
Summary
Landing-page performance hardening across load, render, Core Web Vitals, and mobile compositing. Started as a below-fold render-cost pass (FAQ + ComparisonTable), expanded after a 4-dimension perf audit (bundle / render / web-vitals / paint) into a focused, low-risk batch. All landing-scoped; one global tweak (NetworkBackground) called out below.
Changes
Below-fold render cost (original)
useReducedMotionto one parent call,React.memoFaqItem,useCallbackhandlersbackdrop-blur-smfromComparisonTablesection root — kills a full-screen blur passLandingContent→ smaller re-render scope on scrollLCP / load
HeroCtaclient child; the<h1>now paints from SSR HTML without waiting for hydration<script>(RSC) instead ofnext/script— structured data lands in initial HTML (crawler-safe) + removes 4 hydration registrationsoptimizePackageImports+=framer-motion,@web3icons/reactCLS / paint / compositing (mobile)
blur-3xl→blur-2xl+will-change-transform+ transition guarded underprefers-reduced-motion— global (background on all pages); visually equivalent at 10% opacity, snapshot updatedbackdrop-blur-smfrom below-fold section roots + nested FAQ double-blur + cards (SocialProofStrip, HowItWorks, FaqSection, WhyVoidPay cards, AudienceCard) — replaced with solid translucent bgBelowFoldPlaceholderpassed asskeletonprop (consistentmin-h-[300vh]pre/post-trigger), eliminating CLS on the skeleton→content swapRuntime / render
visibilitychange— no interval ticks / heavyInvoicePaperre-renders while tab is hiddensetNetworkTheme('ethereum')— store default is alreadyethereum; drops a redundant repaint in the LCP windowReact.memoonAudienceCard/HeroFeatureCard/TimelineStep; hoisttopCards(static derivation) to module scopeTest plan
type-check:build— 0 errorslint— 0 errorsNotes
VideoSection, 945 KB poster, 10 MB mp4s) is out of scope here — it lives on the video branch; captured separately as a handoff.