Adding Partytown to a SvelteKit Project
Published: 2022-05-10 00:38:15

Originally posted on [monogram.io](https://monogram.io/blog/add-partytown-to-svelte).

## 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

```bash
npm init svelte my-app
```

### Install Partytown

```bash
pnpm add @builderio/partytown
```

### Add the Partytown script to src/routes/__layout.svelte

```js
// 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. 

```js
// 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.

```json
{
	"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:

```js
// 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:

```js
// svelte.config.js

const config = {
	preprocess: [
		preprocess({
			preserve: ['partytown']
		})
	],
  ...
}
```

Our svelte:head element now looks like this:

```js
// 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.

```js
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.