Skip to content

firstParty proxy: multiple bugs in proxy URL construction, script bundling, and registry matching #608

@felixgabler

Description

@felixgabler

Environment

Description

Two bugs in the firstParty proxy implementation:

1. Missing path separator in proxy URL construction

Both the beacon intercept plugin and the SW template construct proxy URLs by concatenating rule.target + pathWithoutPrefix, but when pathPrefix is / (e.g., Meta Pixel at connect.facebook.net/en_US/fbevents.js), the stripped path loses its leading /:

pathPrefix = "/"
pathname = "/en_US/fbevents.js"
pathWithoutPrefix = pathname.slice(pathPrefix.length) = "en_US/fbevents.js"  // no leading /
proxyUrl = "/_proxy/meta" + "en_US/fbevents.js" = "/_proxy/metaen_US/fbevents.js"  // WRONG

Expected: /_proxy/meta/en_US/fbevents.js

Affected locations:

  • module.mjs ~line 1175 (beacon intercept plugin)
  • runtime/sw/proxy-sw.template.js ~line 39 (SW fetch intercept)

Fix: Add a separator:

const separator = pathWithoutPrefix.startsWith('/') ? '' : '/';
const proxyUrl = rule.target + separator + pathWithoutPrefix + parsed.search;

Note: rewriteScriptUrls in runtime/utils/pure.js correctly uses joinURL() for the same operation, so this is only a bug in the two intercept paths.

2. Registry scripts with static src (no scriptBundling) are never bundled

Scripts like Meta Pixel and Reddit Pixel define src in their registry entry but don't have a scriptBundling function:

// registry.mjs - Meta Pixel
{
  proxy: "metaPixel",
  src: "https://connect.facebook.net/en_US/fbevents.js",
  // no scriptBundling
}

In the AST bundle transformer (NuxtScriptBundleTransformer), registryNode.src is only checked in the guard at line ~496 but never used as a fallback source for bundling:

// Line 496: guard passes because registryNode.src is truthy
if (!registryNode.scriptBundling && !registryNode.src) return;

// Line 513-518: but src is only set from scriptBundling, never from registryNode.src
if (!scriptSrcNode) {
    src = registryNode.scriptBundling && registryNode.scriptBundling(mergedOptions);
    // registryNode.src is never used as fallback!
}

// Line 520: both are falsy → early return, bundling skipped entirely
if (!scriptSrcNode && !src) { return; }

Result: The script loads from the third-party URL at runtime. The SW must intercept it, but the SW isn't active on first page load → script only loads through proxy on refresh, not on initial visit.

Fix: Add registryNode.src as fallback after line 517:

if (!src && registryNode.src)
    src = registryNode.src;

This makes scripts with static src get properly bundled at /_scripts/hash.js with proxyRewrites applied to their content, removing the SW dependency for initial script loading.

Reproduction

  1. Configure scripts: { firstParty: { privacy: 'proxy' } } with metaPixel in registry
  2. Deploy to Vercel
  3. Observe /_proxy/metaen_US/fbevents.js (404) in network tab instead of /_proxy/meta/en_US/fbevents.js
  4. Observe that Meta Pixel script is NOT bundled at /_scripts/ — it tries to load from the third-party URL
  5. On first page load, SW isn't active → script loads directly from connect.facebook.net (bypassing proxy entirely)
  6. On refresh, SW intercepts but constructs wrong proxy URL (bug 1)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions