perf(landing): stop eager 2.7MB video + 581KB codec on mobile landing#296
perf(landing): stop eager 2.7MB video + 581KB codec on mobile landing#296ignromanov wants to merge 2 commits into
Conversation
On mobile, autoPlay was set to !prefersReducedMotion which overrides preload="none" per HTML spec, causing the full 2.7MB MP4 to download on the critical path. Gate autoPlay on !isMobile && !prefersReducedMotion so the deferred fetch actually defers on mobile. - Show poster with native controls on mobile (tap-to-play) - Skip IntersectionObserver autoplay resume on mobile - Desktop behavior unchanged (autoplay when not reduced-motion) - Add tests: mobile no-autoplay, mobile controls present
… on landing (P1) DemoSection was calling getDemoInvoices() in a useEffect, triggering a 581KB brotli-wasm fetch on every landing page visit. Lift the async call to the server boundary (LandingPage RSC) so encodeInvoice runs in Node at request time. Pass resolved DemoInvoice[] as a prop through LandingContent → LazyBelowFold → BelowFoldSections → DemoSection. - DemoSection: remove useEffect fetch, accept demoInvoices prop - demo-invoices.ts: export DemoInvoice type, fix stale "build time" comment - BelowFoldSections: thread demoInvoices prop through - LandingContent: accept + pass demoInvoices prop - page.tsx: async RSC — await getDemoInvoices() before render - Tests: prop-driven fixture via getDemoInvoices() in beforeAll (mocked codec)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
HOLD — split. Official PSI (mobile, 3× median) shows the P1 RSC lift introduced an LCP +1.4s / SI +1.8s regression (demo-invoice render pulled into the critical path) despite the payload/TBT wins. P0 (video) extracted to a clean PR. P1 (codec-off-landing) being reworked: precompute demo hashes at build + keep DemoSection lazy client-side below the fold. This PR will be closed once P0 + reworked-P1 land. |
…m client (#300) DemoSection no longer loads brotli-wasm or @/features/invoice-codec at runtime. createHash strings are generated once during `pnpm build` (prebuild npm hook) via scripts/generate-demo-hashes.ts + a Node zlib brotli stub that replaces brotli-wasm for the build-time script. DemoSection imports static data + the generated demo-invoices.generated.ts module — synchronous, no useEffect async loading. This reverts the #296 RSC lift (which regressed LCP +1.4s / SI +1.8s) while still keeping brotli-wasm off the landing client bundle. Files: - scripts/generate-demo-hashes.ts — prebuild script that writes createHash strings - scripts/brotli-node-stub.ts — Node zlib brotli shim for the prebuild tsconfig - scripts/tsconfig.prebuild.json — tsconfig alias: brotli-wasm → brotli-node-stub - src/widgets/landing/constants/demo-invoices.ts — exports RAW_DEMO_INVOICES + buildDemoInvoices() - src/widgets/landing/constants/demo-invoices.generated.ts — placeholder (overwritten at build) - src/widgets/landing/demo-section/DemoSection.tsx — imports static data, no codec - src/widgets/landing/demo-section/__tests__/DemoSection.test.tsx — updated for static shape
|
Superseded. Split into #299 (P0 video — merged) + #300 (reworked P1 codec-off-landing via build-precompute — merged). This version's RSC-lift caused an LCP regression (5.4s vs develop 4.1s on official PSI mobile median); #300 achieves the same codec-off-landing goal without the regression (4.2s). Closing in favor of the two merged PRs. |
What
Landing-page performance fix — two regressions/inefficiencies on the mobile landing path.
VideoSectionno longer autoplays.autoPlaywas overridingpreload="none"(HTML spec), forcing eager download of the 2.7MBvoidpay-9x16-v2.mp4on the critical path. Now: poster + tap-to-play via native controls;preload="none"actually defers the fetch. Desktop autoplay + reduced-motion paths preserved.getDemoInvoices()(runsencodeInvoice→ brotli-wasm) out of a clientuseEffectto the landing RSC.page.tsxis now async; resolvedDemoInvoice[]is threaded as a prop downLandingContent → BelowFoldSections → DemoSection. The client no longer imports the codec → 581KB brotli-wasm no longer loads on the landing (still loads on /create + /pay).Why — measured (Lighthouse mobile)
The develop byte-weight spike (4,044 KiB) was the 2.7MB autoplay MP4; P0 removes it from the initial trace. The first cold-preview sample showed FCP 2.6/LCP 4.3 — a cold-lambda artifact; warm median matches/beats baseline.
Verification
Iris quality-gate: PASS — type/lint/build clean, 2831 tests (2 skipped), build compiles landing as Static ○ (confirms async-RSC + prop boundary), acceptance 13/13 (P0 + P1), FSD clean, RSC→client prop boundary serializable.
Follow-up (P2, non-blocking)
useReducedMotion=truetest mock (observer/desktop-autoplay paths unreachable). Tracked separately.🤖 Generated with Claude Code