Skip to content

ENG-9611: defer tracking/analytics scripts so they don't block render#6568

Open
adhami3310 wants to merge 2 commits into
mainfrom
khaleel/eng-9611-defer-tracker-scripts
Open

ENG-9611: defer tracking/analytics scripts so they don't block render#6568
adhami3310 wants to merge 2 commits into
mainfrom
khaleel/eng-9611-defer-tracker-scripts

Conversation

@adhami3310
Copy link
Copy Markdown
Member

Summary

  • External src tracker scripts (gtag loader, clearbit) get async_=True so they no longer block HTML parsing while downloading.
  • Inline tracker bootstraps (posthog, koala, common_room, rb2b, unify, default, gtag init) get type="module", which the browser defers until after HTML parsing.
  • All eight telemetry script components switch from rx.script to rx.el.script. These trackers reach the DOM via App(head_components=...), which reflex/compiler/utils.py:create_document_root compiles straight into <Head> — so the Helmet wrapper that rx.script adds is redundant reconciliation cost.
  • gtag_report_conversion is intentionally left non-module: it defines a global function gtag_report_conversion() that page onclick handlers call by name; module scope would hide it from window.

Linear: ENG-9611

Cross-repo follow-up

get_common_room_trackers is currently rendered inside rx.theme(rx.cond(...)) in two consumers (cloud, flexgen) rather than head_components. The old rx.script/Helmet wrapper hoisted it to <head> regardless; switching to rx.el.script means it now stays in the body where the theme renders it. The IIFE is idempotent and type="module" defers the same way regardless of DOM position, so it still works — but the head/body location changes for those two consumers.

A follow-up PR to cloud and flexgen should move the get_common_room_trackers(...) call into head_components= and adapt the rx.cond(~AuthState.in_session & is_prod_mode(), ...) gate accordingly.

Test plan

  • uv run ruff check . clean
  • uv run ruff format . clean
  • uv run pyright clean on the telemetry package
  • After merge/deploy: open DevTools → Network on a site using get_pixel_website_trackers() and confirm tracker requests are no longer in the render-blocking section of the waterfall

External `src` trackers get `async_=True` (gtag loader, clearbit). Inline
trackers get `type="module"` so the browser defers them until after HTML
parsing (posthog, koala, common_room, rb2b, unify, default, gtag init).

Also switches all of these from `rx.script` to `rx.el.script`. The
trackers are placed via `App(head_components=...)` which is compiled
directly into `<Head>`, so the Helmet wrapper that `rx.script` adds is
redundant runtime cost.

`gtag_report_conversion` is intentionally left non-module — it defines
a global function called by name from `onclick` handlers.
@adhami3310 adhami3310 requested a review from a team as a code owner May 27, 2026 19:34
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 27, 2026

ENG-9611

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 27, 2026

Merging this PR will not alter performance

✅ 24 untouched benchmarks


Comparing khaleel/eng-9611-defer-tracker-scripts (9e37f01) with main (cf5e4ed)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (8ad0f56) during the generation of this report, so cf5e4ed was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 27, 2026

Greptile Summary

This PR defers all telemetry/analytics scripts to eliminate render-blocking by applying async_=True to external src scripts (Clearbit, gtag loader) and type="module" to inline bootstrap scripts (PostHog, Koala, Common Room, RB2B, Unify, Default, and the gtag init snippet). All eight tracker components are also migrated from rx.script (Helmet wrapper) to rx.el.script (raw element) since they are placed directly into <head> via head_components.

  • The previous review concern about function gtag() being module-scoped and therefore invisible to window has been addressed: the bootstrap now uses window.gtag = window.gtag || function gtag() {...} to explicitly assign to the global object.
  • Every other inline IIFE was already module-safe: all globals they expose (window.posthog, window.reb2b, window.ko, window.signals, window.unify, window.__default__) are assigned via explicit window.* properties.
  • gtag_report_conversion intentionally remains a plain (non-module) rx.el.script so that the function gtag_report_conversion() declaration lands on window and is reachable by onclick handlers, and so its internal bare gtag() call can resolve window.gtag from the global scope.

Confidence Score: 5/5

Changes are safe to merge; all inline scripts correctly expose their globals via explicit window.* assignments, and the intentional non-module exception for gtag_report_conversion is well-reasoned.

The previous concern about window.gtag not being set from within a module script was the key risk here, and it has been correctly resolved by explicitly assigning window.gtag. Every other converted script already used window.* for any globals it exposes, so the type="module" deferral is safe across the board.

No files require special attention. The one file worth a second glance is google.py for the window.gtag fix and the intentional non-module treatment of gtag_report_conversion, both of which are handled correctly.

Important Files Changed

Filename Overview
packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/google.py GTAG init bootstrap now uses `window.gtag = window.gtag
packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/posthog.py Inline PostHog bootstrap converted to rx.el.script with type="module"; IIFE assigns to window.posthog explicitly, and bare posthog.init() call at module top-level correctly resolves from global scope.
packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/rb2b.py Both RB2B pixel scripts switched to rx.el.script with type="module"; both IIFEs assign to window.reb2b explicitly, making them module-safe.
packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/clearbit.py External Clearbit src script correctly gets async_=True to stop blocking HTML parsing.
packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/common_room.py Converted to rx.el.script with type="module"; IIFE uses window.signals assignment, so the global is properly accessible after deferral.
packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/koala.py Converted to rx.el.script with type="module"; IIFE sets window.ko explicitly before using the bare ko identifier, which resolves correctly from the global scope in a module context.
packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/default.py Converted to type="module"; the minified IIFE uses named-function-expression idiom (not declaration), so window.__default__ assignment via the e parameter is unaffected by module scope.
packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/unify.py Converted to rx.el.script with type="module"; IIFE assigns to window.unify and window.unifyBrowser explicitly, compatible with module scope.

Reviews (2): Last reviewed commit: "fix: assign gtag to window so type=modul..." | Re-trigger Greptile

A top-level `function gtag(){}` in a type=module script is module-scoped,
not installed on `window`. That breaks `gtag_report_conversion` and any
page code that calls `gtag('event', ...)` before the external gtag.js
loader finishes — `window.gtag` is undefined and the call throws.

Assign `window.gtag` explicitly. The `|| window.gtag` guard handles the
case where the async external loader executes before the deferred module
inline (so we don't clobber gtag.js's real implementation).

Caught by Greptile on PR #6568.
@adhami3310
Copy link
Copy Markdown
Member Author

@greptile

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.

2 participants