partytown
partytown copied to clipboard
[🐞]partytown throws error in chrome developer console and not able to connect to GTM preview mode
Describe the bug
To improve the performance of the website and to reduce the impact of third party scripts i'm using partytown service worker to offload the GTM scripts from the main thread. Application - Gatsby "gatsby": "5.11.0" "@builder.io/partytown": "^0.9.2"
I will share my partytown gatsby implementation code here
Gatsby-ssr.js
import React from "react";
import { Partytown } from "@builder.io/partytown/react";
export const onRenderBody = ({ setHeadComponents, setPreBodyComponents }) => {
//const gtmTrackingId = process.env.GATSBY_GOOGLE_TAG_MANAGER_TRACKING_ID;
setHeadComponents([
<Partytown
key="partytown"
resolveUrl={(url, location) => {
var proxyUrl;
if (url.hostname === 'www.google-analytics.com' && url.pathname.endsWith('.js')) {
proxyUrl = new URL(`${location.origin}/google-analytics`);
proxyUrl.searchParams.append('url', url.href);
return proxyUrl;
}
if (url.pathname.includes("/debug/bootstrap")) {
proxyUrl = new URL(`${location.origin}/googletagmanager/debug/bootstrap`);
proxyUrl.searchParams.append("url", url.href);
return proxyUrl;
}
return url;
}}
debug={true}
forward={["dataLayer.push"]}
/>,
<script
key="plugin-google-tagmanager"
type="text/partytown"
dangerouslySetInnerHTML={{
__html:`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-MKPX3TV4'`
}}
/>
]);
setPreBodyComponents([
<noscript
key="gtm"
dangerouslySetInnerHTML={{
__html: `
<iframe src="https://www.googletagmanager.com/ns.html?id=GTM-MKPX3TV4" height="0" width="0" style="display: none; visibility: hidden" aria-hidden="true"></iframe>`,
}}
/>,
]);
};`
gatsby-node.js
`exports.onPreBuild = async ({ actions: { createRedirect } }) => {
//await copyLibFiles(path.join(__dirname, "static", "~partytown"));
createRedirect({
fromPath: `/google-analytics?url=:url`,
toPath: `:url`,
statusCode: 200,
});
// This is only for GTM preview mode for debugging
createRedirect({
fromPath: `/googletagmanager/debug/bootstrap?url=:url`,
toPath: `:url`,
statusCode: 200,
});
};
Handled reverse proxy for partytown
I got this error 404 in the console
I'm not able to preview the tags in GTM
- I have added the google tag assistant chrome extension
Please help me on this
Reproduction
None
Steps to reproduce
Mentioned above
Browser Info
Chrome
Additional Information
No response
The code snippet is hard to read, but I think you might indeed be missing a closing bracket in your dangerouslySetInnerHTML
:
dangerouslySetInnerHTML={{
__html:(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-XXXXXXXX')
}}
Notice the trailing bracket after 'GTM-XXXXXXXX'. It's missing in your code above and hence leading to the JavaScript error.
Again: GTM preview is not possible at the moment with Partytown. For more information refer to https://github.com/BuilderIO/partytown/issues/72.
@j0Shi82 thanks now the error is gone but how to fix the CORS error
can please help on this ?
Sure. As mentioned in the docs, requests from within a Web Worker require the correct CORS headers. It's a security measure that all modern browsers implement.
Apparently, CookieBot does not serve their assets with CORS headers. What you can do is host the script yourself (I have no experience with Gatsby, but there surely is a public
folder you can work with). In case the script is dynamic and you have to get it from the vendor directly, you will need to set up a proxy as described here.
Cloudflare offers 100k requests for free (https://developers.cloudflare.com/workers/platform/pricing/), but you can obviously use whatever service you wish or deploy your own server that proxies the requests with the correct CORS header. The Cloudflare worker could look like this:
export default {
async fetch(request) {
// node_modules/itty-router/dist/itty-router.min.mjs
function e({ base: t = "", routes: n = [] } = {}) {
return { __proto__: new Proxy({}, { get: (e2, a, o) => (e3, ...r) => n.push([a.toUpperCase(), RegExp(`^${(t + e3).replace(/(\/?)\*/g, "($1.*)?").replace(/\/$/, "").replace(/:(\w+)(\?)?(\.)?/g, "$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/, "\\.").replace(/\)\.\?\(([^\[]+)\[\^/g, "?)\\.?($1(?<=\\.)[^\\.")}/*$`), r]) && o }), routes: n, async handle(e2, ...r) {
let a, o, t2 = new URL(e2.url);
e2.query = Object.fromEntries(t2.searchParams);
for (var [p, s, u] of n)
if ((p === e2.method || "ALL" === p) && (o = t2.pathname.match(s))) {
e2.params = o.groups;
for (var c of u)
if (void 0 !== (a = await c(e2.proxy || e2, ...r)))
return a;
}
} };
}
var router = e();
router.get("/api/partytown/proxy", async (req) => {
let url = new URL(req.url);
if (!url.searchParams.has('url')) {
return new Response("Invalid", { status: 400 });
}
const decodedUrl = decodeURIComponent(url.searchParams.get('url'));
const res = await fetch(decodedUrl);
const newHeaders = new Headers(res.headers)
newHeaders.set('access-control-allow-origin', '*');
const newResponse = new Response(res.body, {
headers: newHeaders
})
return newResponse;
});
router.all("*", () => new Response("Not Found.", { status: 404 }));
return router.handle(request);
}
};
Then setup the resolveUrl
option in the Partytown config:
resolveUrl: (url, location, type) => {
if (type === 'script' && url.hostname.includes('cookiebot')) {
const proxyUrl = new URL('/api/partytown/proxy', YOUR_WORKER_DOMAIN);
proxyUrl.searchParams.append('url', url.href);
return proxyUrl;
}
}
You might need to adjust the snippet to fit your needs, but it should poke you into the right direction, I hope. Happy coding!
@j0Shi82 thank you for your quick response i have added the proxy in partytown config and I'm using AWS Cloudfront distribution for my website hosting Now the CORS error is fixed but 404 error comes. please help me on this This is my Partytown gatsby configuration
<Partytown
key="partytown"
resolveUrl={(url, location, type) => {
let proxyUrl;
if (type === 'script' && url.hostname.includes('cookiebot')) {
proxyUrl = new URL('/api/partytown/proxy', 'http://localhost:9000/');
proxyUrl.searchParams.append('url', url.href);
return proxyUrl;
}
if (type === 'script' && url.hostname.includes('analytics')) {
proxyUrl = new URL('/api/partytown/proxy', 'http://localhost:9000/');
proxyUrl.searchParams.append('url', url.href);
return proxyUrl;
}
return url;
}}
debug={true}
forward={['dataLayer.push']}
/>,
<script
key="plugin-google-tagmanager"
type="text/partytown"
dangerouslySetInnerHTML={{
__html: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-MKPX3TV4');`,
}}
/>
This is error i got in network tab
@j0Shi82 can we do using cloudfront ?
Sure. As mentioned in the docs, requests from within a Web Worker require the correct CORS headers. It's a security measure that all modern browsers implement.
Apparently, CookieBot does not serve their assets with CORS headers. What you can do is host the script yourself (I have no experience with Gatsby, but there surely is a
public
folder you can work with). In case the script is dynamic and you have to get it from the vendor directly, you will need to set up a proxy as described here.Cloudflare offers 100k requests for free (https://developers.cloudflare.com/workers/platform/pricing/), but you can obviously use whatever service you wish or deploy your own server that proxies the requests with the correct CORS header. The Cloudflare worker could look like this:
export default { async fetch(request) { // node_modules/itty-router/dist/itty-router.min.mjs function e({ base: t = "", routes: n = [] } = {}) { return { __proto__: new Proxy({}, { get: (e2, a, o) => (e3, ...r) => n.push([a.toUpperCase(), RegExp(`^${(t + e3).replace(/(\/?)\*/g, "($1.*)?").replace(/\/$/, "").replace(/:(\w+)(\?)?(\.)?/g, "$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/, "\\.").replace(/\)\.\?\(([^\[]+)\[\^/g, "?)\\.?($1(?<=\\.)[^\\.")}/*$`), r]) && o }), routes: n, async handle(e2, ...r) { let a, o, t2 = new URL(e2.url); e2.query = Object.fromEntries(t2.searchParams); for (var [p, s, u] of n) if ((p === e2.method || "ALL" === p) && (o = t2.pathname.match(s))) { e2.params = o.groups; for (var c of u) if (void 0 !== (a = await c(e2.proxy || e2, ...r))) return a; } } }; } var router = e(); router.get("/api/partytown/proxy", async (req) => { let url = new URL(req.url); if (!url.searchParams.has('url')) { return new Response("Invalid", { status: 400 }); } const decodedUrl = decodeURIComponent(url.searchParams.get('url')); const res = await fetch(decodedUrl); const newHeaders = new Headers(res.headers) newHeaders.set('access-control-allow-origin', '*'); const newResponse = new Response(res.body, { headers: newHeaders }) return newResponse; }); router.all("*", () => new Response("Not Found.", { status: 404 })); return router.handle(request); } };
Then setup the
resolveUrl
option in the Partytown config:resolveUrl: (url, location, type) => { if (type === 'script' && url.hostname.includes('cookiebot')) { const proxyUrl = new URL('/api/partytown/proxy', YOUR_WORKER_DOMAIN); proxyUrl.searchParams.append('url', url.href); return proxyUrl; } }
You might need to adjust the snippet to fit your needs, but it should poke you into the right direction, I hope. Happy coding!
Cloudfront functions are JavaScript as well afaik. So you should be able to use the code snippet with small adjustments. A quick search revealed that Cloudfront functions may be limited to 1ms of CPU time. Not sure it's still up-to-date. But our production workers use 1.5ms of CPU time on average. So you might run a quota issue there.
Sorry I cannot assist more specifically. You will need to work with the Cloudfront docs to set up your proxy.
@j0Shi82 thank you for your response CORS error is fixed and I have updated CORS in cloudfront behaviours.
Can we take it to production without the seeing the triggers/events are tracked in GTM preview mode ?
What you can do to verify that the data is flowing is go into your connected Analytics account, monitor the live view, and refresh your page a couple times. In case events appear there, you should be ok. I personally always set up GTM without Partytown to be able to use the debug mode and then move it into the worker. Usually it works fine, but you should absolutely keep an eye on your connected vendors to make sure the data is coming in as expected. Remember Partytown is an open source beta so you'll always need to do maintenance yourself. 👍
Yes, I can able to preview the tags in GTM without partytown and once i move the GTM into the worker i'm not able to preview the tags. Also i check the real time data in GA4 i can able to see the events appear there. Thanks for your quick response
@j0Shi82
I'm using hubspot as tag which added in GTM so it is requesting for https://js.hs-analytics.net/analytics/1710241500000/000000.js but receiving 404 not found
This my proxy setup please help me on this
<Partytown
key="partytown"
resolveUrl={(url, location) => {
if (url.hostname.includes('analytics')) {
// Use a secure connection
if (url?.protocol === 'http:') {
url = new URL(url.href.replace('http', 'https'));
}
// Point to our proxied URL
const proxyUrl = new URL(location.origin + '/__third-party-proxy');
proxyUrl.searchParams.append('url', url.href);
return proxyUrl;
}
return url;
}}
debug={true}
forward={['dataLayer.push']}
/>
@j0Shi82 I have added the code in cloudfare worker and i have update config in partytown but still CORS issue occurs can you please help on this ?
<Partytown
key="partytown"
resolveUrl={(url,type) => {
if (url?.protocol === 'http:') {
url = new URL(url.href.replace('http', 'https'))
}
if (type === 'script' && url.hostname.includes('analytics')) {
const proxyUrl = new URL('/api/partytown/proxy', 'https://reverse-proxy.maruthasalam-rajendran.workers.dev/');
proxyUrl.searchParams.append('url', url.href);
return proxyUrl;
}
return url;
}}
debug={true}
forward={['dataLayer.push']}
/>
@maruthasalamr Unfortunately I'm a bit busy at the moment, but I'll try to look into it in the coming days.
@j0Shi82 any update. It's look similar to resolveUrl
issue for Partytown
react component
https://github.com/BuilderIO/partytown/issues/553