Skip to content

Perf: defer third party widgets#1045

Merged
ngnijland merged 2 commits into
masterfrom
perf/defer-third-party-widgets
Jun 10, 2026
Merged

Perf: defer third party widgets#1045
ngnijland merged 2 commits into
masterfrom
perf/defer-third-party-widgets

Conversation

@ngnijland

@ngnijland ngnijland commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Why

Antoine reported the landing page felt slow — usable only once the Intercom bubble appeared. That's the tell: the page looks done fast but stays unresponsive while third-party scripts fire on window.load.

Findings (Lighthouse, mobile/throttled)

Page content is healthy; third-party is the cost. Speed Index 2.1 s but TTI 11.7 s — the "looks done but isn't" gap.

  • Senja (testimonials): heaviest third party — ~430 KB, ~1.5 s JS, plus render-blocking Google Fonts. Renders below the fold but fetched eagerly on parse.
  • Intercom: ~344 KB JS, a long task still running ~13 s in. Loaded on window.load — the worst moment for interactivity.

Solution

  • Lazy-load Senja (fec0cdf): inject loaders via a shared IntersectionObserver (200px margin) only as widgets near the viewport; reserve min-height so CLS stays ~0.
  • Defer Intercom (ac0d33a): replace the window.load trigger with first-interaction + a requestIdleCallback idle fallback (5 s backstop). Passive readers still get the launcher via idle; window-level guards prevent double-loading across view transitions.

Verification

  • A/B test in headless Chrome: old code fired Intercom on load, new code fires nothing until interaction/idle.
  • Senja: zero senja.io / Google-Fonts requests on initial load; CLS ~0.
  • build, lint, format:check, typecheck, knip all pass.

Intercom returns 403 on localhost (untrusted domain) — expected, unrelated; launcher rendering is verifiable only on production.

🤖 Generated with Claude Code

ngnijland and others added 2 commits June 10, 2026 11:54
Both Senja embeds rendered below the fold but fetched their loader
scripts eagerly on page parse — ~430 KB and ~1.5 s of JS plus a
render-blocking Google Fonts (Inter) request on the initial path.

Move each loader URL onto a `data-senja-src` attribute and inject it
via a shared IntersectionObserver (200px rootMargin), mirroring the
lazy feature-video pattern in Feature.astro. Reserve min-height on each
embed so the late-loading widget does not shift content (CLS stays ~0).

Verified via Lighthouse: zero senja.io / google-fonts requests on
initial load; CLS ~0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace Intercom's window.load trigger with a first-interaction trigger
(scroll/mousemove/touchstart/keydown/click) plus a requestIdleCallback
idle fallback (5s backstop), so the ~344KB widget no longer competes for
network and main thread during the critical initial load.

Passive readers still get the launcher via the idle fallback. Window-level
guards (__intercomArmed/__intercomLoaded) keep it from double-loading across
Astro view-transition navigations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ngnijland ngnijland marked this pull request as ready for review June 10, 2026 10:28
@ngnijland ngnijland merged commit ad7218d into master Jun 10, 2026
1 check passed
@ngnijland ngnijland deleted the perf/defer-third-party-widgets branch June 10, 2026 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant