perf: faster mobile LCP on the landing page#2466
Merged
Merged
Conversation
Four narrow fixes targeting the PageSpeed mobile-field LCP failure (3.2 s): - Drop the orphaned /bulma-switch.css <link>. The file does not exist in web/public/ and is unreferenced in source — but the browser still blocked paint waiting for the 404. Single biggest render-blocking win. - Make the Google Fonts stylesheet non-blocking via the standard preload + onload swap, with a <noscript> fallback. font-display: swap is already set so the fallback font shows immediately. - Defer Hotjar and Google Analytics inline IIFEs until the load event. Both attached an async <script> during head parse — that contributed to the long main-thread tasks Lighthouse flagged. - Add explicit width/height + fetchpriority="high" to the navbar logo (the LCP candidate on /). Width differs per theme because the two source PNGs have very different aspect ratios — a follow-up should give the dark theme a properly sized navbar logo. Server side: serve /assets/* (hashed Vite output, content-addressed) with Cache-Control: max-age=1y, immutable. Repeat visits stop re-validating every chunk. Changelog entry added (user-visible — first paint is faster on mobile). Verified: server tsc, web tsc, web build (5.7s), Biome lint, 774 tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for notion2anki ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
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
Targets the PageSpeed Insights mobile failure (LCP 3.2 s field / 6.9 s lab, CWV not passing). Four small, independently-shippable fixes:
/bulma-switch.css<link>. The file does not exist inweb/public/and is not imported anywhere in source — yet the browser still blocked paint waiting for the 404 response. Sneaky win.rel="preload"+onloadswap, with a<noscript>fallback.font-display: swapis already set, so the fallback font shows immediately and Inter swaps in when it lands.loadevent. Both inline IIFEs attached anasyncscript during head parse — small in bytes, but they contributed to the four long main-thread tasks Lighthouse flagged. Session-replay completeness drops by ~1 s on first paint; we keep all the data, just later./) gets explicitwidth/heightandfetchpriority=\"high\". The dark-theme logo is a separate problem — it's currently a 780×661 mascot PNG scaled to ~33×28, which is wasteful and probably ugly. Flagging for the designer; this PR just reserves the layout space.Server side: serve
/assets/*(hashed Vite output, content-addressed) withCache-Control: max-age=1y, immutableso repeat visits stop re-validating every chunk.Why this set, why this order
Mobile field LCP is the actual user-felt problem — students on Android landing here from search rank pressure. Per CrUX, the metric is failing. The four fixes above hit the top three Lighthouse opportunities (render-blocking 600 ms, cache lifetime 98 KiB, image delivery / LCP candidate). Unused-JS (204 KiB) and the deeper bundle splitting are deliberately out of scope — that's a separate, larger PR.
Test plan
pnpm tsc --noEmit(server)pnpm --filter 2anki-web typecheckpnpm --filter 2anki-web build(5.7s)pnpm --filter 2anki-web lint(Biome clean)pnpm --filter 2anki-web test --run(101 files / 774 tests pass)https://2anki.net/and confirm mobile LCP dropsFollow-ups (separate PRs)
navbar-logo.png's 140×38 aspect.HomePage/UploadPageinApp.tsx, defer Bugsnag init viarequestIdleCallback, lazy-load Google Picker / Dropbox Chooser SDKs on first interaction. Needs a bundle analyzer pass first.landingPrerenderplugin to/. The plugin inweb/vite.config.tsalready statically prerenders SEO routes (Notion, marketplace, answers). Extending the same pattern to/would put the LCP element in HTML at first byte — much bigger LCP move than full Express SSR, much smaller engineering lift. (See SSR notes in the chat — full SSR is not recommended here.)Changelog
The landing page paints faster on phones over slow connections— sentence case, no period, no fake warmth, per VOICE.md.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmithwith what you need.