Originally posted on monogram.io.
The power of analytics
Website analytics are a useful tool for business owners and web developers alike. The data gathered by services like Google Tag Manager and Segment can be used to tweak content and ensure it reaches the target audience.
What’s the catch?
Ironically, 3rd party analytics scripts can have the greatest negative impact on performance out of all JavaScript served on a website. This is due to the processing power needed to download, parse and execute these scripts. This results in reduced user retention and increased bounce rates.
While script tag attributes like async and defer can help, they don’t guarantee improved performance, especially as the number of 3rd party scripts on a site grows.
Partytown: a home for 3rd party scripts
Long story short, JavaScript is CPU intensive. When used in excess, it creates a processing bottleneck. This is a common cause of website feeling 'janky' and unresponsive.
Partytown is an innovative approach to handling 3rd party scripts that solves this problem. It runs scripts in the background using web workers, ensuring that script execution doesn’t delay the TTI.
Recently, we added Google Tag Manager to two SvelteKit projects (including this website!) using Partytown. Let's walk through the process step-by-step, addressing some of the roadblocks I encountered along the way.
Partytown is still in beta. Some scripts may not behave correctly when used with it. We suggest opening a GitHub issue or reaching out on the Partytown Discord if you run into problems.
Adding Partytown to SvelteKit
Start a fresh SvelteKit project
Bootstrap a new project by running
npm init svelte my-app
Install Partytown
pnpm add @builderio/partytown
Add the Partytown script to src/routes/__layout.svelte
// src/routes/__layout.svelte
<script>
import { onMount } from 'svelte'
import { partytownSnippet } from '@builder.io/partytown/integration'
// Add the Partytown script to the DOM head
let scriptEl
onMount(
() =>
scriptEl &&
(scriptEl.textContent = partytownSnippet())
)
</script>
<svelte:head>
<!-- Config options -->
<script>
// Forward the necessary functions
// to the web worker layer
partytown = {
forward: ['dataLayer.push']
}
</script>
<!-- `partytownSnippet` is inserted here -->
<script bind:this={scriptEl}></script>
</svelte:head>
Copy Partytown library files to the local filesystem
Partytown’s internal scripts need to be served from the same origin as your site because it uses a service worker. Thankfully, the library comes with a Vite plugin that accomplishes this.
// svelte.config.js
import adapter from '@sveltejs/adapter-auto'
import { partytownVite } from '@builder.io/partytown/utils'
import path from 'path'
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter(),
// The Vite config is accessible inside the SvelteKit config
vite: {
plugins: [
partytownVite({
// `dest` specifies where files are copied to in production
dest: path.join(
process.cwd(),
'static',
'~partytown'
)
})
]
}
}
}
export default config
On the dev server, the files are served locally. In production, they are copied to the path specified in dest.
Proxying scripts
Some 3rd party scripts run into CORS issues when making HTTP requests through a web worker. This is the case for Google Tag Manager.
Partytown recommends reverse-proxying the requests to prevent cross-origin errors.
At Monogram, we deploy our SvelteKit websites using Vercel. Thus, we’ll create a vercel.json file in the root directory and add the reverse proxy config. Google Tag Manager requires two scripts to be proxied because it makes a second request to fetch the Google Analytics script.
{
"rewrites": [
{
"source": "/proxytown/gtm",
"destination": "https://www.googletagmanager.com/gtag/js"
},
{
"source": "/proxytown/ga",
"destination": "https://www.google-analytics.com/analytics.js"
}
]
}
To complete the proxy config, add a resolveUrl function to the Partytown config in __layout.svelte:
// src/routes/__layout.svelte
<script>
partytown = {
forward: ['dataLayer.push'],
resolveUrl: (url) => {
const siteUrl = 'https://monogram.io/proxytown'
if (url.hostname === 'www.googletagmanager.com') {
const proxyUrl = new URL(`${siteUrl}/gtm`)
const gtmId = new URL(url).searchParams.get('id')
gtmId && proxyUrl.searchParams.append('id', gtmId)
return proxyUrl
} else if (url.hostname === 'www.google-analytics.com') {
const proxyUrl = new URL(`${siteUrl}/ga`)
return proxyUrl
}
return url
}
}
</script>
If you’re unable to use a reverse proxy, you can serve the scripts from the same domain as your website.
Using 3rd party scripts with Partytown
The moment we’ve all been waiting for!
Add any scripts you want to be processed by Partytown to a svelte:head element. Simply place the script there and add the attribute type="text/partytown".
Instruct SvelteKit to bypass preprocessing for Partytown scripts by adding the following to svelte.config.js:
// svelte.config.js
const config = {
preprocess: [
preprocess({
preserve: ['partytown']
})
],
...
}
Our svelte:head element now looks like this:
// src/routes/__layout.svelte
<svelte:head>
<script>
// Config options
partytown = {
forward: ['dataLayer.push'],
resolveUrl: (url) => {
const siteUrl = 'https://example.com/proxytown'
if (url.hostname === 'www.googletagmanager.com') {
const proxyUrl = new URL(`${siteUrl}/gtm`)
const gtmId = new URL(url).searchParams.get('id')
gtmId && proxyUrl.searchParams.append('id', gtmId)
return proxyUrl
} else if (
url.hostname === 'www.google-analytics.com'
) {
const proxyUrl = new URL(`${siteUrl}/ga`)
return proxyUrl
}
return url
}
}
</script>
<!-- Insert `partytownSnippet` here -->
<script bind:this={scriptEl}></script>
<!-- GTM script + config -->
<script
type="text/partytown"
src="https://www.googletagmanager.com/gtag/js?id=YOUR-ID-HERE"></script>
<script type="text/partytown">
window.dataLayer = window.dataLayer || []
function gtag() {
dataLayer.push(arguments)
}
gtag('js', new Date())
gtag('config', 'YOUR-ID-HERE', {
page_path: window.location.pathname
})
</script>
</svelte:head>
Events can now be sent to GTM using the typical dataLayer.push
method, since we're ‘forwarding’ those function calls to Partytown.
dataLayer.push({ ... })
That's all there is to it! Let me know in the comments if you noticed a typo or bug, or if you have anything to add.
Top comments (3)
Great article! Thank you.
I, however, receive a 404 after implementing the above steps in production.
It seems that the
partytownVite
util is the culprit, because I can resolve the 404 by manually copyingpartytown-sw.js into
static/~partytown/partytown-sw.js`Minimal example here:
partytown-bug.vercel.app/
github.com/cesarnml/partytown-bug
Thanks for your repository and your solution!! :D
@dayvista , could you update your blog post with the solution which Cesar made?
github.com/cesarnml/partytown-bug/...
That's a very useful article. I'll use it to try to implement it on my NextJS site instead.