Skip to content

Bundling the Snapchat pixel breaks it: scevent.min.js resolves its config URL against the page origin #822

Description

@niklasfjeldberg

Environment

  • @nuxt/scripts 1.2.1 (registry entry unchanged in 1.3.0), Nuxt 4, Nitro cloudflare_module preset (Cloudflare Workers)
  • Snapchat pixel registered at runtime via useScriptSnapchatPixel({ id }), bundling enabled via runtimeConfig.public.scripts.snapchatPixel.scriptOptions.bundle = true

Bug

Bundling (self-hosting) the Snapchat pixel breaks it. scevent.min.js derives the host of its own <script> URL from an Error().stack trace at runtime, and when that host is not sc-static.net it assumes it is running behind Snap's CAPI gateway and loads its config from the serving origin:

GET https://<page-host>/config/<tld-of-page-hostname>/<pixel-id>.js?v=3.59.0-...

On a normal Nuxt app that path 404s (and Nitro error responses carry X-Content-Type-Options: nosniff, so the browser refuses the response as a script even when the 404 page renders). The SDK only falls back to tr.snapchat.com/config/… after a ~5 s timeout, and that fallback is exactly the third-party request that tracking blockers kill — so for many visitors the pixel never initializes at all.

Observed live: https://imleavinit.no/ (bundled scevent.min.js served from /_scripts/assets/…) requested https://imleavinit.no/config/no/<pixel-id>.js → 404 → NS_ERROR_CORRUPTED_CONTENT / MIME-type block in Firefox.

Evidence (de-minified scevent.min.js)

var S = y(function () {            // own script URL, extracted from a stack trace
  return m().split("\n")[0].replace(/^.*(http(s){0,1}:\/\/.*\.js):[0-9]+.*$/, "$1");
}, p);                             // fallback p = "https://sc-static.net/scevent.min.js"
var xe = y(() => new URL(S).host, s);   // s = "sc-static.net"

// config loader — i = "/config/<tld>/<id>.js?v=..."
qr(xe !== s ? v + xe + i : De(i)); // v = "https://", De() → https://tr.snapchat.com + path

<tld> is literally hostname.split('.').pop() of the page.

Why first-party proxy mode doesn't help

The build-time AST rewriting replaces domain literals (sc-static.net/_scripts/p/sc-static.net, …), and the runtime patches match third-party domains in URLs. A host recovered from a stack trace is reachable by neither: the SDK's first config request still goes to the page origin (not /_scripts/p/…), 404s, and only then times out into the direct tr.snapchat.com fallback — which defeats the proxy's purpose anyway.

Suggested fix

Either:

  1. Drop the bundle: true capability for snapchatPixel in the registry (bundling produces a strictly worse outcome than loading from sc-static.net), or
  2. Add an sdkPatches entry for snapchatPixel that pins the SDK's origin detection to sc-static.net, and/or have the proxy handler answer the CAPI-gateway convention (/config/<tld>/<id>.js, plus the SDK's other origin-relative endpoints like /cm/i, /cm/si) when snapchatPixel proxying is enabled.

Workaround

snapchatPixel: { scriptOptions: { bundle: false } } — load the SDK from sc-static.net so its origin detection matches and config loads from tr.snapchat.com directly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions