Spike: Optimize third-party script through prefetch/connect
### Background
Third-party scripts (OneTrust, GTM, Mutiny, Optimizely, Marketo, Bizible, Munchkin) contribute significantly to main-thread blocking time and impact Core Web Vitals. A competitive analysis of top marketing sites (Vercel, MetaMask, Shopify, Stripe, Linear, Framer, HubSpot, Samsung, Adobe, Chewy, Aetna) was conducted to identify optimization patterns we can adopt without sacrificing analytics or consent functionality.
**Constraint:** OneTrust is required for cookie consent and cannot be removed or replaced. Our site is SSG (static), so server-side proxying is not available at runtime.
### Competitive Analysis Summary
| Site | Key Pattern |
|------|-------------|
| **Vercel** | Zero third-party scripts — all analytics are first-party, bundled into the app |
| **Stripe** | `defer` on all scripts, own CDN, first-party attribute-based analytics, custom consent |
| **Linear** | Near-zero third-party overhead — only Sentry via headers |
| **MetaMask** | Next.js Script strategies: consent = beforeInteractive, GTM = component-level, LinkedIn = lazyOnload, full CSP nonce support |
| **Shopify** | Streaming SSR, third-party scripts not in SSR response, custom consent, dns-prefetch/preconnect hints only |
| **Framer** | Most sophisticated — scheduler.postTask("background") for event handler deferral, consent-gated GTM with GCM v2, dataLayer.push patched to defer execution |
| **HubSpot** | Heaviest footprint — multiple defer scripts, GTM wrapped in compliance layer, dns-prefetch for ~10 domains |
| **Samsung** | Adobe Launch loaded `async`, Akamai RUM isolated in hidden iframe, preconnect for image CDN, CSS preload-as-stylesheet pattern |
| **Adobe/Marketo** | Same-origin proxying for Launch + analytics (eliminates 3P DNS/TLS), consolidated Alloy Web SDK replaces 4+ scripts, OneTrust SDK loaded via tag manager not in HTML |
| **Chewy** | GTM + Segment deferred via app callback (not in head), Segment CDN self-hosted/proxied, Optimizely datafile inlined in SSR (no client fetch), CSS lazy-load via media="print" swap |
| **Aetna** | OneTrust not in initial HTML (loaded via Adobe Launch), Akamai mPulse in iframe isolation, Qualtrics deferred to window.onload |
**Key takeaway:** Best-in-class sites either eliminate third-party scripts entirely (Vercel, Stripe, Linear) or heavily defer/consent-gate everything (Framer, MetaMask). None of the 11 sites analyzed load OneTrust synchronously in their server-rendered HTML the way we do — they either use lighter/custom consent, or load OneTrust through a tag manager. DNS hints are universally used by sites with third-party dependencies (Shopify, HubSpot, Chewy, Samsung).
### Current State
Our render-blocking scripts:
| Script | File | Blocking? |
|--------|------|-----------|
| **OneTrust AutoBlock** (OtAutoBlock.js) | `scripts/onetrust-scripts.ts:32-34` | **YES — sync, no defer/async** |
| **OneTrust SDK Stub** (otSDKStub.js) | `scripts/onetrust-scripts.ts:37-40` | **YES — sync, no defer/async** |
| **OneTrust Geolocation** (geofeed) | `scripts/onetrust-scripts.ts:43-45` | **YES — sync, no defer/async** |
| **Consent Observer Init** (inline) | `nuxt.config.ts:138-197` | **YES — inline, no defer** |
Non-blocking but could be improved:
- **GTM** — loaded via @nuxt/scripts (async) in `app/plugins/google-tag-manager.client.ts`
- **Mutiny client** — consent-gated via optanon-category-C0004, type="text/plain"
- **Optimizely** — consent-gated via optanon-category-C0002, type="text/plain"
- **Marketo Forms SDK** — defer with fetchpriority: high in `app/composables/marketo/useMarketoFormInit.ts`
- **Bizible/Munchkin** — consent-gated, loaded via Partytown web worker
---
### Proposed Optimization
#### Add `dns-prefetch` / `preconnect` for All Third-Party Domains
**Files:** `nuxt.config.ts` — `app.head.link[]`
Currently we only preconnect to OneTrust domains (`cdn.cookielaw.org`, `geolocation.onetrust.com`). Every third-party script we load requires a DNS lookup + TLS handshake on first visit. Since we're SSG and can't proxy these through our own domain, DNS hints are the most impactful zero-risk optimization available.
Add `dns-prefetch` and `preconnect` hints for:
- `https://www.googletagmanager.com` (GTM)
- `https://client-registry.mutinycdn.com` (Mutiny)
- `https://cdn.optimizely.com` (Optimizely)
- `https://page.gitlab.com` (Marketo Forms)
- `https://cdn.bizible.com` (Bizible)
- `https://munchkin.marketo.net` (Munchkin)
This is the same pattern used by Shopify, HubSpot, Samsung, and Chewy.
**Why this matters for SSG:** Unlike SSR sites that can proxy third-party scripts through their own domain (Adobe, Chewy), our static pages are served from a CDN with no runtime server. The browser must resolve every third-party domain cold on first visit. Preconnect hints let the browser start DNS + TLS during HTML parsing, saving 100-300ms per domain.
---
<details>
<summary>Interesting AI slop worth reading</summary>
### Explored and Deferred (Not In Scope)
#### Making OneTrust SDK Stub + Geolocation `async` — DEFERRED
Originally proposed as a high-impact optimization. After reviewing prior art, the risk is too high without better testing infrastructure.
**Prior art:**
- [buyer-experience#3167](https://gitlab.com/gitlab-com/marketing/digital-experience/buyer-experience/-/issues/3167) — Dennis investigated OneTrust's async/SSR option and explicitly flagged race condition concerns: *"The asynchronous methodology is a concern to me, as we've had issues in the past regarding race conditions between OneTrust and other marketing scripts, which caused data to drop."* Closed because there was no safe way to test ($12k/year UAT environment vs $540 plan).
- [buyer-experience#1855](https://gitlab.com/gitlab-com/marketing/digital-experience/buyer-experience/-/issues/1855) — Multiple attempts to change OneTrust/GTM script ordering caused data loss. `data-ot-ignore` caused GA traffic drops. `addEventListener` chaining broke GTM entirely. Moving OneTrust above GTM had no effect. Loading OneTrust from GTM broke the consent banner.
- [buyer-experience#1391](https://gitlab.com/gitlab-com/marketing/digital-experience/buyer-experience/-/issues/1391) — Removing OneTrust's `data-ot-ignore` attribute from GTM was done urgently after traffic dropped.
- [buyer-experience#3503](https://gitlab.com/gitlab-com/marketing/digital-experience/buyer-experience/-/issues/3503) — Google Consent Mode V2 implementation caused data drops on first attempt and had to be reverted. The current `wait_for_update: 500` is calibrated for synchronous OneTrust loading; making OneTrust async could cause gtag to proceed before consent state resolves.
**Why deferred:** History shows that any change to OneTrust/GTM loading order has repeatedly caused analytics data drops that required reverts. Without a safe testing environment, the risk-reward ratio is not favorable.
#### Deferring the Consent Observer Init Script — DEFERRED
Originally proposed as safe when paired with async OneTrust. Since we're not making OneTrust async, this becomes risky on its own — `OptanonWrapper()` must be defined before the synchronous OneTrust SDK calls it.
**Why deferred:** If OneTrust SDK remains synchronous, it may call `OptanonWrapper()` during HTML parsing before a deferred script defines it. This would silently fail and break consent-gated script loading (Mutiny opt-in, Bizible/Munchkin loading).
#### Limiting Scripts By Page Type — NOT VIABLE
- [about-gitlab-com#1282](https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/issues/1282) — Closed April 2026: "After further investigation, limiting scripts is not an option at this time. This may result in tracking errors."
#### Lazy-Loading GTM After OneTrust — DEPRIORITIZED (NOT FAILED)
- [about-gitlab-com#217](https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/issues/217) and [#216](https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/issues/216) — Closed for prioritization reasons (migrations), not because the approach failed. Analytics DRI flagged concerns about data loss. Could be revisited with careful testing.
</details>
---
### Future Considerations (Not In Scope)
- **Marketo Forms `fetchpriority: high`** — Noted for future review. Currently needed; do not change.
- **`scheduler.postTask("background")` pattern** (Framer-style) — Defer non-critical event handlers after consent. Needs thorough QA before adopting.
---
### Acceptance Criteria
- [ ] `dns-prefetch` and `preconnect` hints added for all third-party domains (GTM, Mutiny, Optimizely, Marketo, Bizible, Munchkin)
- [ ] No regressions in Lighthouse/CWV scores (expect minor improvement in connection times)
- [ ] Verified with `pnpm generate --route / && pnpm preview`
### Testing Plan
1. **Local:** `pnpm dev` — verify hints appear in `<head>`, no console errors
2. **Static build:** `pnpm generate --route / && pnpm preview` — verify hints in production HTML
3. **Lighthouse:** Compare before/after connection timing in waterfall
issue