DEV Community

Cover image for Adding Partytown to a SvelteKit Project
Liam Davis
Liam Davis

Posted on • Updated on

Adding Partytown to a SvelteKit Project

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
Enter fullscreen mode Exit fullscreen mode

Install Partytown

pnpm add @builderio/partytown
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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']
        })
    ],
  ...
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Events can now be sent to GTM using the typical dataLayer.push method, since we're ‘forwarding’ those function calls to Partytown.

dataLayer.push({ ... })
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
cesarnml profile image
Cesar Napoleon Mejia Leiva

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 copying partytown-sw.js intostatic/~partytown/partytown-sw.js`

Minimal example here:
partytown-bug.vercel.app/

github.com/cesarnml/partytown-bug

Image description

Collapse
 
candidosales profile image
Cândido Sales Gomes

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

Collapse
 
gabrielmlinassi profile image
Gabriel Linassi

That's a very useful article. I'll use it to try to implement it on my NextJS site instead.