From 6ac2c17ce70a8850f849322cb6b85e734ff88ade Mon Sep 17 00:00:00 2001 From: isaaclombardssw Date: Thu, 23 Jul 2026 10:24:45 +1000 Subject: [PATCH 1/2] perf: defer GTM to lazyOnload without dropping early events (#4908) GTM loaded via @next/third-parties' (afterInteractive), pulling the container's marketing tags (GA4/Ads/FB/Hotjar) into the hydration window. Replace it with the equivalent GTM snippet split across two next/script tags: the dataLayer + gtm.start init stays eager so early sendGTMEvent / dataLayer.push calls (e.g. eventbrite_order_complete) queue and flush, while gtm.js itself loads on idle (lazyOnload). sendGTMEvent keeps working: it pushes to window.dataLayer directly and does not depend on rendering. Adds a Playwright regression test. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Q8genJyZboXaa4vgYhJF4P --- app/components/google-tag-manager.tsx | 33 +++++++++++++++++ app/layout.tsx | 2 +- ui-tests/gtm-tracking.spec.ts | 53 +++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 app/components/google-tag-manager.tsx create mode 100644 ui-tests/gtm-tracking.spec.ts diff --git a/app/components/google-tag-manager.tsx b/app/components/google-tag-manager.tsx new file mode 100644 index 0000000000..2752f1bab0 --- /dev/null +++ b/app/components/google-tag-manager.tsx @@ -0,0 +1,33 @@ +import Script from "next/script"; + +/** + * GTM split out of @next/third-parties' , which only exposes an + * afterInteractive load. The dataLayer is created and `gtm.start` pushed eagerly, so early + * `sendGTMEvent` / `dataLayer.push` calls (e.g. the eventbrite_order_complete conversion) queue + * and flush once GTM arrives — but gtm.js itself loads on idle (lazyOnload) instead of during + * hydration, keeping the container's marketing tags (GA4/Ads/FB/Hotjar) out of the critical window. + * sendGTMEvent from @next/third-parties keeps working: it pushes to window.dataLayer directly and + * doesn't depend on this component rendering. + * Retiming the individual tag triggers is GTM container config, not code — see #4908. + */ +export const GoogleTagManager = ({ gtmId }: { gtmId?: string }) => { + if (!gtmId) return null; + return ( + <> +