Technical Documentation: QwikDev/partytown
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
Partytown 🎉
- Introducing Partytown: Run Third-Party Scripts From a Web Worker
- How Partytown's Sync Communication Works
- How we cut 99% of our JavaScript with Qwik + Partytown
- Partytown is now in Beta
A fun location for your third-party scripts to hang out
Partytown is a lazy-loaded library to help relocate resource intensive scripts into a web worker, and off of the main thread. Its goal is to help speed up sites by dedicating the main thread to your code, and offloading third-party scripts to a web worker.
Note: Partytown is not guaranteed to work in every scenario. Please see our FAQ and Trade-Off sections for more info.
The philosophy is that the main thread should be dedicated to your code, and any scripts that are not required to be in the critical path should be moved to a web worker. Main thread performance is, without question, more important than web worker thread performance.
Community
Related Projects
- Qwik: An open-source framework designed for best possible time to interactive, by focusing on resumability of server-side-rendering of HTML, and fine-grained lazy-loading of code.
- Mitosis: Write components once, run everywhere. Compiles to Vue, React, Solid, Angular, Svelte, and more.
- Builder: Drag and drop page builder and CMS for React, Vue, Angular, and more.
Overview (`.changeset/README.md`)
Changesets
Hello and welcome! This folder has been automatically generated by @changesets/cli, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it in our repository
We have a quick list of common questions to get you started engaging with this project in
our documentation
Overview (`docs/README.md`)
Qwik App ⚡️
Project Structure
This project is using Qwik with QwikRouter. QwikRouter is just an extra set of tools on top of Qwik to make it easier to build a full site, including directory-based routing, layouts, and more.
Inside your project, you'll see the following directory structure:
├── public/
│ └── ...
└── src/
├── components/
│ └── ...
└── routes/
└── ...src/routes: Provides the directory-based routing, which can include a hierarchy oflayout.tsxlayout files, and anindex.tsxfile as the page. Additionally,index.tsfiles are endpoints. Please see the routing docs for more info.src/components: Recommended directory for components.public: Any static assets, like images, can be placed in the public directory. Please see the Vite public directory for more info.
Add Integrations and deployment
Use the pnpm qwik add command to add additional integrations. Some examples of integrations includes: Cloudflare, Netlify or Express Server, and the Static Site Generator (SSG).
pnpm qwik add # or `pnpm qwik add`Development
Development mode uses Vite's development server. The dev command will server-side render (SSR) the output during development.
npm start # or `pnpm start`Note: during dev mode, Vite may request a significant number of
.jsfiles. This does not represent a Qwik production build.
Preview
The preview command will create a production build of the client modules, a production build of src/entry.preview.tsx, and run a local server. The preview server is only for convenience to preview a production build locally and should not be used as a production server.
pnpm preview # or `pnpm preview`Production
The production build will generate client and server modules by running both client and server build commands. The build command will use Typescript to run a type check on the source code.
pnpm build # or `pnpm build`Cloudflare Pages
Cloudflare's wrangler CLI can be used to preview a production build locally. To start a local server, run:
pnpm serveThen visit http://localhost:8787/
Deployments
Cloudflare Pages are deployable through their Git provider integrations.
If you don't already have an account, then create a Cloudflare account here. Next go to your dashboard and follow the Cloudflare Pages deployment guide.
Within the projects "Settings" for "Build and deployments", the "Build command" should be pnpm build, and the "Build output directory" should be set to dist.
Function Invocation Routes
Cloudflare Page's function-invocation-routes config can be used to include, or exclude, certain paths to be used by the worker functions. Having a _routes.json file gives developers more granular control over when your Function is invoked.
This is useful to determine if a page response should be Server-Side Rendered (SSR) or if the response should use a static-site generated (SSG) index.html file.
By default, the Cloudflare pages adaptor does not include a public/_routes.json config, but rather it is auto-generated from the build by the Cloudflare adaptor. An example of an auto-generate dist/_routes.json would be:
{
"include": [
"/*"
],
"exclude": [
"/_headers",
"/_redirects",
"/build/*",
"/favicon.ico",
"/manifest.json",
"/service-worker.js",
"/about"
],
"version": 1
}In the above example, it's saying all pages should be SSR'd. However, the root static files such as /favicon.ico and any static assets in /build/* should be excluded from the Functions, and instead treated as a static file.
In most cases the generated dist/_routes.json file is ideal. However, if you need more granular control over each path, you can instead provide you're own public/_routes.json file. When the project provides its own public/_routes.json file, then the Cloudflare adaptor will not auto-generate the routes config and instead use the committed one within the public directory.
Overview (`tests/nextjs/README.md`)
npm run devSrc Routes Index (`docs/src/routes/index.mdx`)
title: Introduction
Run third-party scripts from a web worker
Partytown is a lazy-loaded library to help relocate resource-intensive scripts into a web worker, and off of the main thread. Its goal is to help speed up sites by dedicating the main thread to your code, and offloading third-party scripts to a web worker.
Note: Partytown is not guaranteed to work in every scenario. Please see our FAQ and Trade-Off sections for more info.
Even with a fast and highly tuned website following all of today's best practices, it's all too common for your performance wins to be erased the moment third-party scripts are added. By third-party scripts we mean code that is embedded within your site, but not directly under your control. A few examples include: analytics, metrics, ads, A/B testing, trackers, etc.
Partytown is maintained by QwikDev.
- Partytown, Google Slides Presentation
- Smashing Magazine: How Partytown Eliminates Website Bloat From Third-Party Scripts
Goals
We set out to solve this situation, so that apps of all sizes will be able to continue to use third-party scripts without the performance hit. Some of Partytown's goals include:
- Free up main thread resources to be used only for the primary web app execution.
- Sandbox third-party scripts and allow or deny their access to main thread APIs.
- Isolate long-running tasks within the web worker thread.
- Reduce layout thrashing coming from third-party scripts by batching DOM setters/getter into group updates.
- Throttle third-party scripts' access to the main thread.
- Allow third-party scripts to run exactly how they're coded and without any alterations.
- Read and write main thread DOM operations synchronously from within a web worker, allowing scripts running from the web worker to execute as expected.
Web Workers
Partytown's philosophy is that the main thread should be dedicated to your code, and any scripts that are not required to be in the critical path should be moved to a web worker. Main thread performance is, without question, more important than web worker thread performance. Please see the test pages in the repository for examples you can run locally.
Negative Impacts from Third-Party Scripts
Below is a summary of potential issues after adding third-party scripts, referenced from Loading Third-Party JavaScript:
- Firing too many network requests to multiple servers. The more requests a site has to make, the longer it can take to load.
- Sending too much JavaScript which keeps the main thread busy. Too much JavaScript can block DOM construction, delaying how quickly pages can render.
- CPU-intensive script parsing and execution can delay user interaction and cause battery drain.
- Third-party scripts that were loaded without care can be a single-point of failure (SPOF).
- Insufficient HTTP caching, forcing resources to be fetched from the network often.
- The use of legacy APIs (e.g
document.write()), which are known to be harmful to the user experience. - Excessive DOM elements or expensive CSS selectors.
- Including multiple third-party embeds that can lead to multiple frameworks and libraries being pulled in several times, which exacerbates the performance issues.
- Third-party scripts also often use embed techniques that can block
window.onload, even if the embed is using async or defer.
Use-Cases
While full webapps "can" run from within Partytown, it's actually best intended for code that doesn't need to be in the critical rendering path. Most third-party scripts fall into this category, as they're not required for the first-paint. Additionally, the asynchronous nature of most third-party script works well with Partytown, as they can lazily receive user events and post data to their respective services.
Below are just a few examples of third-party scripts that might be a good candidate to run from within a web worker. The goal is to continue validating commonly used services to ensure Partytown has the correct API, but Partytown itself does not hardcode to any specific services. Help us test and contribute to this page in Github when you find good working scripts!
"Ready to Party" Plugins / Libraries
We try and keep a list of all the plugins and libraries that we know of that work out-of-the-box in Partytown, but we would love your help as plugin & library authors and contributors to keep this list growing.
We have some documentation on how you could create and check-in an integration test that shows how your library / plugin works on Partytown. And if it works, then we would love to have the configuration needed (if any) on our Common Services page
Articles
- <a
href="https://www.smashingmagazine.com/2022/04/partytown-eliminates-website-bloat-third-party-apps/"
target="_blank"
rel="noopener"
Smashing Magazine: How Partytown Eliminates Website Bloat From Third-Party
Scripts - <a
href="https://dev.to/adamdbradley/introducing-partytown-run-third-party-scripts-from-a-web-worker-2cnp"
target="_blank"
rel="noopener"
Introducing Partytown: Run Third-Party Scripts From a Web Worker - <a
href="https://dev.to/adamdbradley/how-partytown-s-sync-communication-works-4244"
target="_blank"
rel="noopener"
How Partytown's Sync Communication Works - <a
href="https://www.builder.io/blog/how-we-cut-99-percent-js-with-qwik-and-partytown"
target="_blank"
rel="noopener"
How we cut 99% of our JavaScript with Qwik + Partytown - <a
href="https://www.builder.io/blog/partytown-is-now-in-beta"
target="_blank"
rel="noopener"
Partytown is now in Beta - <a
href="https://www.patterns.dev/posts/third-party/"
target="_blank"
rel="noopener"
Patterns: Optimize loading third-parties
Src Routes Angular Index (`docs/src/routes/angular/index.mdx`)
title: Angular
Angular
To setup Partytown in an Angular project take the following steps.
Installation
Run the command below to install the dependencies
npm install @qwik.dev/partytown
yarn add @qwik.dev/partytown
pnpm install @qwik.dev/partytownAdd the path to the Partytown JS files into the assets array in your angular.json file
"projects": {
...
"{project-name}": {
...
"architect": {
...
"build": {
...
"options": {
"assets": [...,
{
"glob": "**/*",
"input": "node_modules/@qwik.dev/partytown/lib",
"output": "/~partytown"
}
]
}
}
}
}
}Script Setup
The snippet below shows you how to setup Partytown scripts in your index.html file. The first script tag takes the Partytown configuration. The second script tag references the Partytown JS file and the third file takes the third-party scripts to be loaded on the web-worker.
<head>
/* Partytown config properties */
<script>
partytown = {
...
};
</script>
/* Inlined Partytown Snippet */
<script src="/~partytown/debug/partytown.js"></script>
<script type="text/partytown">
/* Third-Party Script goes here */
</script>
</head>Src Routes Astro Index (`docs/src/routes/astro/index.mdx`)
title: Astro
Astro
There is a first-class Astro integration for partytown.
Automatic Setup
Astro includes a CLI tool for adding integrations. Using the astro add command will automatically install Partytown and configure your project.
npx astro add partytown
yarn astro add partytown
pnpx astro add partytownPartytown Script
Add type: 'text/partytown' attribute to any scripts you want to be handled by Partytown.
<script type="text/partytown" src="https://example.com/analytics.js"></script>Note that the Astro Partytown integration already handles copying the library files to the correct location.
Src Routes Atomics Index (`docs/src/routes/atomics/index.mdx`)
title: Atomics
Atomics
Partytown will use Atomics and SharedArrayBuffer when they're available by the browser. Some of the advantages include:
- 10x faster communication between threads compared to the service-worker requests
- 5% smaller build file compared to the service-worker build
- No
proxytownrequests in the network tab - Fewer http requests to initialize the Partytown library
- Partytown service-worker and iframe is no longer used
It's important to note that not all browsers support Atomics, and the server hosting the site must be configured with the correct response headers. If neither of those are true, then Partytown will fallback to using the service-worker communication layer instead.
Document Response Headers
To enable Atomics communication, the browser's crossOriginIsolated readonly property should be true. The crossOriginIsolated property is only set to true, by the browser, when the document includes these http response headers:
Cross-Origin-Embedder-Policy: credentialless
Cross-Origin-Opener-Policy: same-origin- Cross-Origin-Embedder-Policy (COEP) response header prevents a document from loading any cross-origin resources that don't explicitly grant the document permission. At the time of writing,
credentiallessis not a supported value in Safari, so Safari will fallback to using the Service Worker communication. An alternative tocredentiallessis to userequire-corp, which does work in Safari too. Please see Setting Cross-Origin Attribute for more info. - Cross-Origin-Opener-Policy (COOP) response header allows you to ensure a top-level document does not share a browsing context group with cross-origin documents.
Setting Cross-Origin Attribute
An alternative to setting Cross-Origin-Embedder-Policy: credentialless, is to instead use Cross-Origin-Embedder-Policy: require-corp. The biggest thing to highlight is that the require-corp does not allow images, scripts, videos, etc. to be loaded from another origin. For example, when this header is set, any scripts or images to a CDN will no longer work.
However, the crossorigin attribute can be used on individual elements, such as <audio>, ``, <link>, <script>, and <video>, which allows these requests to go through and not be blocked.
Below is an example of setting the crossorigin attribute on an image:
<img src="https://c.tenor.com/8wBCqZH60U8AAAAC/computer-cat.gif" crossorigin />Browser Atomics Support
- Desktop:
- Chrome 68+ (Jul 2018)
- Edge 79+ (Jan 2020)
- Firefox 78+ (June 2020)
- Safari 15.2+ (Dec 2021)
- Mobile:
- Android, Chrome 97+
- Android, Edge 97+
- Android, Firefox 98+
- iOS, Safari 15.2+ (Dec 2021)
Test Atomics
You can test if a browser is capable of enabling Atomics by visiting these test pages which respond with the correct headers.
Additional Resources
- Making your website "cross-origin isolated" using COOP and COEP
- Cross-Origin-Embedder-Policy: credentialless
- COEP: credentialless. Isolation w/o Opt-In?
- Understanding "same-site" and "same-origin"
Src Routes Browser Support Index (`docs/src/routes/browser-support/index.mdx`)
title: Browser Support
Browser Support
Partytown works to ensure that all browsers can still run third-party scripts, whether they have support for service workers, atomics, or neither. This means if you're supporting legacy browsers such as IE11, the scripts should continue to work as if Partytown wasn't being used.
At its core, Partytown uses Service Workers or Atomics to do its synchronous communication from the web worker to the main thread. However, when a browser does not support either of those, it'll fallback to run the scripts the traditional way (the way it works today without Partytown).
How Feature Support Works
- Browser checks to see if it has support for Atomics using the crossOriginIsolated boolean found on
window. - If the browser and site do not have support for Atomics, it'll then check to see if the browser has support for Service Workers. Currently this is the most commonly used one.
- If the browser does not have support for Service Workers or Atomics, it'll then find all the
type="text/partytown"scripts and reset them to be traditional scripts and execute normally.
Note: Atomics are still experimental, and also require specific server-side response headers for them to work. Please see the Enabling Atomics section for more info.
Web API Feature Support
Src Routes Common Services Index (`docs/src/routes/common-services/index.mdx`)
title: Common Services
Common Services
Partytown is built with the goal that any third-party script can be ran from within it. At the lowest level, any script can add the type="text/partytown" attribute to have it executed from a web worker.
Below is a list of plugins / libraries that are tested & known to be working with Partytown with their commonly used forward configs and proxied domains.
| Service | Forward Config | Proxy Domain | Additional Config |
|---|---|---|---|
| Facebook Pixel | "fbq" | "connect.facebook.net" | |
| FullStory | "FS.identify", "FS.event" | strictProxyHas: true (if loaded via GTM) |
|
| Google Tag Manager | "dataLayer.push" | ||
| Hubspot Tracking | "_hsq.push" | ||
| Intercom | "Intercom" | ||
| Klaviyo | "_learnq.push" | "static.klaviyo.com", "static-tracking.klaviyo.com" | |
| TikTok Pixel | "ttq.track", "ttq.page", "ttq.load" | ||
| Mixpanel | "mixpanel.track" |
If you would like to add to this list,
- Refer to the "for Plugin Authors / Developers" section to see how you can validate whether a library / plugin works with Partytown.
- Send us a PR so that we can have a scenario checked in that validates it.
- Please edit this doc to add your plugin / library and its configuration so that others can start using it!
FullStory with Google Tag Manager
When loading FullStory via Google Tag Manager (GTM), you need to enable the strictProxyHas configuration option. This is because FullStory checks for namespace conflicts using the in operator (e.g., if (!("FS" in window))), and by default, Partytown's window proxy always returns true for the in operator for backwards compatibility.
<script>
partytown = {
forward: ['FS.identify', 'FS.event'],
strictProxyHas: true,
};
</script>Without strictProxyHas: true, FullStory will detect a false "namespace conflict" and fail to initialize when loaded via GTM's Custom HTML tag. This configuration ensures the in operator accurately checks property existence on the window object.
Src Routes Configuration Index (`docs/src/routes/configuration/index.mdx`)
title: Configuration
Configuration
Partytown does not require a config for it to work, however a config can be set to change the defaults. At the lowest level, it's configured by setting the window.partytown = {...} object before the Partytown snippet script. However, higher-level integrations, such as the `` component found in @qwik.dev/partytown/react, should provide utilities to make setting the config easier
| Config | Description |
|---|---|
debug |
When true, Partytown scripts are not inlined and not minified. See the Debugging docs on how to enable more logging. |
forward |
An array of strings representing function calls on the main thread to forward to the web worker. See Forwarding Events and Triggers for more info. |
lib |
Path where the Partytown library can be found your server. Note that the path must both start and end with a / character, and the files must be hosted from the same origin as the webpage. Default is /~partytown/ |
loadScriptsOnMainThread |
An array of strings or regular expressions (RegExp) used to filter out which script are executed via Partytown and the main thread. An example is as follows: loadScriptsOnMainThread: ["https://test.com/analytics.js", "inline-script-id", /regex-matched-script\.js/]. |
resolveUrl |
Hook that is called to resolve URLs which can be used to modify URLs. The hook uses the API: resolveUrl(url: URL, location: URL, method: string). See the Proxying Requests for more information. |
resolveSendBeaconRequestParameters |
Hook that is called before every navigator.sendBeacon request to override its fetch parameters, e.g. keepalive. The hook uses the API: resolveSendBeaconRequestParameters(url: URL, location: URL) and returns a SendBeaconParameters object, or null/undefined to keep the defaults. |
mainWindowAccessors |
An array of global names the worker reads from the main thread window instead of its own, e.g. mainWindowAccessors: ["mySettings"]. Useful when a script running in the worker needs data or methods from an object created on the main thread. Note that forwarded globals stay local to the worker. |
mainElementProperties |
An array of property names worker scripts set on DOM elements for the main thread to read, e.g. mainElementProperties: ["loadAds"]. Setting one also sets it on the real element, a function as a main thread function that calls back into the worker. Useful when main thread code, such as an iframe's inline onload, calls a function a script stored on an element. |
nonce |
The nonce property may be set on script elements created by Partytown. This should be set only when dealing with content security policies and when the use of unsafe-inline is disabled (using nonce-* instead). |
strictProxyHas |
When true, the in operator will accurately check if properties exist on the window object. Required for scripts that check for namespace conflicts, such as FullStory loaded via GTM. Default is false for backwards compatibility. |
fallbackTimeout |
A timeout in ms until Partytown initialization is considered as failed & fallbacks to the regular execution in main thread. Set to 0 to disable the fallback entirely. Default is 9999 |
Vanilla Config
What we mean by "vanilla config", is that the Partytown config can be set without any higher-level integration. Below is an example of setting the debug config. Notice that the config script tag is before the Partytown snippet script. Additionally, the config or snippet scripts should NOT contain the type="text/partytown" attribute (this small amount of JavaScript we need to run on the main thread to initialize everything).
<html>
<head>
<title>Vanilla Config Example</title>
<script>
partytown = {
debug: true,
};
</script>
<script>
/* Inlined Partytown Snippet */
</script>
</head>
<body>
...
</body>
</html>Please see the integration guides for more information.
Src Routes Copy Library Files Index (`docs/src/routes/copy-library-files/index.mdx`)
title: Copy Library Files
Copy Library Files
The @qwik.dev/partytown NPM package provides the static lib files that need to be served from your site. Since Partytown is using a service worker, these files must be served from the same origin as your site, and cannot be hosted from a CDN. Each site is different, and how the Partytown lib files are hosted depends on individual setup.
The /~partytown/ directory should serve the static files found within @qwik.dev/partytown/lib. The quickest way is to copy the lib directory into a public /~partytown directory within your static server. Another option would be to set up a copy task within the project's bundler, or create a build step.
You can also use the lib config if your site must host these files from a directory other than the default /~partytown/. Please see the integration guides for more info on copying library files.
Copy Task Command
For convenience, the Partytown CLI provides a copylib task. The last argument should be the directory where the Partytown lib files should be copied to. In the example below, the lib files are copying to the directory public/~partytown, relative to the current working directory:
partytown copylib 'public/~partytown'This command can be used before a build script. Below is an example of copying the Partytown lib files before a Nextjs build command, using npm scripts:
{
"scripts": {
"build": "npm run partytown && next build",
"partytown": "partytown copylib 'public/~partytown'"
}
}Copy Task API
The same code that the partytown copylib CLI task uses, is also exposed as an API and can be imported by any NodeJS script. Below is an example of importing the @qwik.dev/partytown/utils API and copying the lib files to the given directory. Both examples of an ESM import or CommonJS require should work.
import { copyLibFiles } from "@qwik.dev/partytown/utils"; // ESM
// const { copyLibFiles } = require('@qwik.dev/partytown/utils'); // CommonJS
async function myBuildTask() {
await copyLibFiles("path/to/public/~partytown");
}Rollup
Available as of Partytown 0.3.6
Import the partytownRollup plugin from @qwik.dev/partytown/utils into your rollup.config.js config file. Next, add partytownRollup(opts) to the plugins option.
The Rollup plugin will copy Partytown lib directory to the given destination, which must be an absolute file path. The copying will happen at the time of the writeBundle() hook.
Below is an example of using a Rollup config to copy the Partytown lib to a dist build directory:
// rollup.config.js
import path from "path";
import { partytownRollup } from "@qwik.dev/partytown/utils";
export default {
plugins: [
partytownRollup({
dest: path.join(__dirname, "dist", "~partytown"),
}),
],
};Vite
Available as of Partytown 0.3.6
Import the partytownVite plugin from @qwik.dev/partytown/utils into your vite.config.js config file. Next, add partytownVite(opts) to the plugins option.
The Vite plugin will copy Partytown lib directory to the given destination, which must be an absolute file path. The copying will happen at the time of the writeBundle() hook. When in dev mode, the Partytown lib files will be served using the Vite Dev Server.
Below is an example of using a Vite config to copy the Partytown lib to a dist build directory:
// vite.config.js
import path from "path";
import { partytownVite } from "@qwik.dev/partytown/utils";
export default ({ command }) => ({
plugins: [
partytownVite({
dest: path.join(__dirname, "dist", "~partytown"),
}),
],
});Webpack
Below is an example of using Webpack's copy plugin to copy the source lib directory found in the @qwik.dev/partytown package, to the public/~partytown/ directory:
// webpack.config.js
const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const partytown = require("@qwik.dev/partytown/utils");
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{
from: partytown.libDirPath(),
to: path.join(__dirname, "public", "~partytown"),
},
],
}),
],
};Laravel Mix
Below is an example of using Mix's copy() to copy the source lib directory found in the @qwik.dev/partytown package, to the public/~partytown/ directory:
// webpack.mix.js
const mix = require("laravel-mix");
const partytown = require("@qwik.dev/partytown/utils");
mix.copy(partytown.libDirPath(), "public/~partytown");Src Routes Debugging Index (`docs/src/routes/debugging/index.mdx`)
title: Debugging
Debugging
With debug mode and logging enabled, below is an example of the Partytown logs showing all calls, getters and setters:
The debug config must be true in order to see more verbose logging messages. The config table below lists all of the possible log configurations.
If you are using Chrome DevTools to see these logs, make sure that the Verbose log level is selected in Console
| Log Config Property | Description |
|---|---|
logCalls |
Log method calls |
logGetters |
Log getter calls |
logSetters |
Log setter calls |
logForwardedEvents |
Log forwarded event calls |
logImageRequests |
Log Image() src requests |
logScriptExecution |
Log script executions |
logSendBeaconRequests |
Log navigator.sendBeacon() requests |
logStackTraces |
Log stack traces |
Please see the debug file docs more information about the distribution.
Src Routes Distribution Index (`docs/src/routes/distribution/index.mdx`)
title: Distribution
Distribution
Partytown is distributed from @qwik.dev/partytown using NPM. Within the distribution there are a few directories and submodules, each with their own purpose.
@qwik.dev/partytown/
├── integration/
├── lib/
├── react/
├── services/
└── utils/`@qwik.dev/partytown/lib`
The lib directory contains the static files that should be hosted from the same origin as the website. Hosting from the same origin is a requirement of the service worker scope. The lib static files are minified and meant for production use.
@qwik.dev/partytown/lib/
├── partytown-atomics.js
├── partytown-media.js
├── partytown-sw.js
└── partytown.jsThe partytown.js file must be in the <head> before all other third-party scripts, and should not have the async or defer attributes. Because the script must be executed immediately, and since it's such a small file, it's always preferred to inline the partytown.js script content, rather than it being an additional http request.
Note: When the service worker is correctly installed, the service worker itself handles the request to the
partytown-sandbox-sw.htmlfile and provides the content. The network tab will showpartytown-sandbox-sw.html, and the service worker provides the response, but the file itself is not in the distribution. If you receive a 404 for this file then it means the service worker is not installed correctly. Please try uninstalling any existing Partytown service workers and ensure the Partytown files are loading correctly.
`lib/debug/`
The lib/debug directory has the same purpose as the lib, but instead will console log more information and the files themselves are not minified. Additionally, you'll notice there are more debug files than the production lib files, and that's because the production lib files inline many scripts. For example, the web worker script is inlined, rather than being a separate request.
The lib/debug files are not meant to be used in production. See the Partytown Configuration docs on how to enable debug mode.
@qwik.dev/partytown/lib/debug/
├── partytown-atomics.js
├── partytown-media.js
├── partytown-sandbox-sw.js
├── partytown-sw.js
├── partytown-ww-atomics.js
├── partytown-ww-sw.js
└── partytown.js`@qwik.dev/partytown/integration`
The integration submodule provides functions that can be used for integrations.
| Export | Description |
|---|---|
| partytownSnippet() | Function that returns the Partytown snippet as a string which can be used as the innerHTML of the inlined Partytown script in the head. |
| SCRIPT_TYPE | The value for the script type attribute: text/partytown |
import { partytownSnippet } from "@qwik.dev/partytown/integration";
const snippetText = partytownSnippet();`@qwik.dev/partytown/react`
The react submodule is where you'll find the React specific components, which is just a wrapper around the partytown.js snippet code. Please see the React docs for more information.
import { Partytown } from "@qwik.dev/partytown/react";
export function Head() {
return (
<>
<Partytown />
</>
);
}`@qwik.dev/partytown/services`
The services submodule provides some of the commonly used forward events, such as Google Tag Manager or Facebook Pixel. Please see the Common Services docs for more information.
`@qwik.dev/partytown/utils`
The utils submodule provides some convenience utilities, such as the the Copy Task CLI and API.
| Export | Description |
|---|---|
| copyLibFiles(destDir) | Async copy the lib directory to the destDir. |
| libDirPath() | Returns an absolute path to the lib directory. |
| partytownRollup() | Rollup plugin. |
| partytownVite() | Vite plugin. |
Src Routes Drupal Index (`docs/src/routes/drupal/index.mdx`)
title: Drupal
Drupal
The Drupal integration relies on a contributed module called "PartyTown", which can either be installed manually, or by using composer.
Install
Using Composer
- Use composer to download the Drupal module
composer require drupal/partytown- Add the following code to your
composer.jsonin therepositoriessection
partytown": {
"type": "package",
"package": {
"name": "quikdev/partytown",
"version": "1.0.0",
"type": "drupal-library",
"dist": {
"url": "https://registry.npmjs.org/@qwik.dev/partytown/-/partytown-1.0.0.tgz",
"type": "tar"
},
"license": "MIT"
}
}- Install the PartyTown JS library
composer require quikdev/partytownUsing The Composer Merge Plugin
- Use composer to download the Drupal module
composer require drupal/partytown- If not already installed, install Merge Plugin:
composer require wikimedia/composer-merge-plugin- Merge the PartyTown library from the module's
composer.json:
composer config extra.merge-plugin.include --json '[ "web/modules/contrib/partytown/composer.libraries.json" ]'Manually
Note: this method does not install the PartyTown JS library for you and may be a little more difficult to manage. However, it is provided as a fallback because some Drupal web sites are not managed using composer but may still benefit from this module. You will need to refer to the HTML integration instructions or that of your favorite JS framework in order to make use of these instructions. In this context, the Drupal module will simply act as a GUI for managing your integration settings. Furthermore, you will need to configure the "Lib Path" setting in the Drupal module to point at your installation location.
Download and unzip the Drupal module from the PartyTown release page
Install the
partytownmodule in the Drupalmodulesdirectory (usually<webroot>/modulesor<webroot>/modules/contrib)Visit the Admin > Extend page of your Drupal installation and enable the PartyTown module
Install the PartyTown JS library in whatever method you deem appropriate
Ensure that the "Lib Path" setting points to your PartyTown installation location
Note: Because the Drupal module and PartyTown integration in this method are essentially de-coupled, you will need to ensure that the PartyTown JS library loads after the module-provided
partytown/configlibrary. This ensures that PartyTown can use the settings you set in the Drupal admin. You also may need to implement a Drupal hook that prevents the defaultpartytown/liblibrary from being loaded by Drupal.
Configure
All configuration for the PartyTown module takes place in the Drupal admin interface. You can manage most of the PartyTown API settings graphically at /admin/config/development/partytown.
Managing Scripts With PartyTown
Google Tag Manager
The PartyTown module for Drupal comes with a submodule called "PartyTown Google Tag" for PartyTown-ing Google Tag Manager. Once installed, you will probably want to configure the PartyTown module to forward dataLayer.push.
Other scripts
Other scripts can be PartyTown-ed by setting the type to be text\partytown. Using a *.libraries.yml this can be achieved with something similar to:
foo:
js:
js/foo.js: { attributes: { type: "text/partytown"} }Src Routes Facebook Pixel Index (`docs/src/routes/facebook-pixel/index.mdx`)
title: Facebook Pixel
Facebook Pixel
Add the Pixel Script
Partytown Script
Set the script element's type attribute to text/partytown. For example:
<script type="text/partytown">
// insert pixel script here
</script>Proxy Requests
The connect.facebook.net response does not provide the correct CORS header, and a reverse proxy should be used. Below is an example of setting the resolveUrl config to proxy the connect.facebook.net requests. Please see Proxying Requests for more information.
Proxying with Server Handlers
If you're using a server framework like Nuxt 3, you can handle proxying dynamically using API routes. Below is an example configuration that dynamically fetches resources based on the provided URL and returns the response with appropriate headers:
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const url = String(query?.url) || null;
if (!url || !url.includes("connect.facebook.net")) {
throw createError({
status: 500,
message: "Invalid URL",
});
}
// Fetch the file content dynamically based on the URL
const response = await $fetch(url).catch((error) => {
throw createError({
status: 500,
message: `Failed to fetch resource: ${error.message}`,
});
});
setResponseHeaders(event, {
"Access-Control-Allow-Origin": "*", // Ensures proper CORS handling
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE",
"Content-Type": "application/x-javascript; charset=utf-8",
"Cache-Control":
"public, max-age=5, s-maxage=5, stale-if-error=2678400, stale-while-revalidate=86400", // Add necessary caching headers
});
return response;
});Forward Events
Facebook Pixel uses the fbq() function to send events. In order for Partytown to forward the calls to window.fbq({..}), the forward config should add "fbq". Please see forwarding events and triggers for more information.
Example Config
// https://partytown.qwik.dev/configuration
{
resolveUrl: function(url) {
if (url.hostname === "connect.facebook.net") {
var proxyUrl = new URL('https://my-reverse-proxy.com/');
proxyUrl.searchParams.append('url', url.href);
return proxyUrl;
}
return url;
},
forward: [
"fbq"
]
}Please see the integration docs for framework specific configuration.
Src Routes Faq Index (`docs/src/routes/faq/index.mdx`)
title: FAQs
FAQs
Can all scripts use Partytown?
Not necessarily. While Partytown covers many cases that allow it to work great in many cases, there are also scenarios where your script may hit an internal error and silently fail. We encourage you to thoroughly test when running any scripts first in a development environment, and then in production. Please also see the Trade-Offs docs.
How do I know when a script is not working with Partytown?
Test test test! There are endless scenarios in which a script could have been written, and the tricks they could be doing. While Partytown has worked to test many of these scenarios, there's a good chance a certain example isn't being accounted for. We encourage you to thoroughly test when running any scripts first in a development environment, and then in production.
What do I do if my script is not working with Partytown?
Please first search through the Github issues for a similar issue. If a similar issue exists, please add a comment with as much additional information as possible.
If a similar issue does not already exist, please open an issue that includes:
- Minimal Reproduction Case: If maintainers and contributors are unable to reproduce the same error, then it makes it very difficult to debug and fix. Any issued opened without a minimal reproduction example will be closed.
- E2E Test: A good example of an End-to-end test is one where the test fails before the fix, and passes after the fix. But also, all the other tests pass too. Please see Submitting Issues And Writing Tests for more details.
Why do some scripts work, and others don't with Partytown?
Most of yesterday's and today's third-party scripts were built from the ground-up to work only on the main UI thread, rather than from a web worker which does not have access to the DOM or window. Partytown does its best to access the main thread from a web-worker, synchronously so the script continues to work as expected. However, because this architecture is different from what's the original designers expected, the script may have an internal error. For all these reasons we encourage you to first thoroughly test your scripts when using them with Partytown. Please also see the Trade-Offs docs.
Will you debug why my script is not working for me?
Unfortunately, the project contributors and maintainers don’t have the capacity to debug every single third-party script on external websites. As an Open-Source project, Partytown's goal is to help encourage the community to assist one another. Helping to improvement the project can come in many forms:
- Opening issues with minimal reproduction cases
- Creating fixes with Pull Requests
- Adding End-to-end tests
Additionally, Github issues is a place to explain a problem in detail, along with a reproduction case for maintainers. However, Github issues is not the best place to ask how-to questions since project contributors and maintainers don’t have the capacity to debug external websites and the many third-party scripts. Any issues opened asking that are not following these guidelines will be closed.
Is Partytown production ready?
Many sites are already successfully using Partytown in production. However, what's most important is testing your scripts first, as what works for one website using a certain third-party script, may not work for your site using a different script. If your scripts work in production that’s great, they shouldn't suddenly stop working (no different that a traditional third-party script).
However, if you're struggling with a certain scenario, please see the Submitting Issues And Writing Tests.
Src Routes Forwarding Events Index (`docs/src/routes/forwarding-events/index.mdx`)
title: Forwarding Events And Triggers
Forwarding Events And Triggers
Many third-party scripts add a global variable to window which user code calls in order to send data to the service. For example, Google Tag Manager uses a Data Layer array, and by pushing data to the array, the data is then sent on to GTM Servers. Because we're moving third-party scripts to a web worker, the main thread needs to know which variables to patch on window, and when these services are called, the data is correctly forwarded to the web worker. This includes queuing any events that may have happened before Partytown has even finished initializing.
For example, Google Tag Manager adds the dataLayer array to window, and Facebook Pixel adds the fbq function. Below is some quick pseudo code of what they're doing:
// Google Tag Manager
window.dataLayer = [];
// Facebook Events
window.fbq = function(){...};Because GTM and Facebook Pixel objects are added immediately in the <head> by each service, then anywhere within the webpage, your code and their code, can safely call dataLayer.push(...) or fbq(...).
However, since GTM and Facebook Pixel were actually loaded in the web worker, then we need to forward these calls. The forward config is used to set which window variables should be patched and forwarded on. The forward string value is of the function to call, and since GTM is pushing to an array, the function to call is dataLayer.push.
<script>
partytown = {
forward: ['dataLayer.push', 'fbq']
};
</script>Notice the forward configs are just strings, not actual objects. We're using strings here so we can easily serialize what service variable was called, along with the function argument values. When the web worker receives the information, it then knows how to correctly apply the call and arguments that were fired from the main thread.
You can customize each forwarded variable with the following settings:
preserveBehavior
In addition to the
forwardconfig, we also provide apreserveBehaviorproperty. This property allows you to customize each forwarded property, preserving the original behavior of the function.When
preserveBehavioris set totrue, the original function's behavior on the main thread is maintained, while also forwarding the calls to partytown. This is useful in cases where the function has side effects on the main thread that you want to keep.If
preserveBehavioris not explicitly set, its default value isfalse. This means that, by default, calls will only be forwarded to partytown and won't execute on the main thread.Here's an example of how to use it:
js<script> partytown = { forward: [ ['dataLayer.push', { preserveBehavior: true }], ['fbq', { preserveBehavior: false }], 'gtm.push' ] }; </script>In this example, calls to
dataLayer.pushwill execute as normal on the main thread and also be forwarded to partytown. Calls tofbqwill only be forwarded to partytown, and won't execute on the main thread. Forgtm.push, since preserveBehavior is not explicitly set, it will behave as if preserveBehavior was set to false, meaning it will only be forwarded to partytown.
Integrations
Please see the Integrations section for examples using the forward config.
Common Services
Please see the Common Services section for a list of known configs for various services.
Src Routes Gatsby Index (`docs/src/routes/gatsby/index.mdx`)
title: Gatsby
Gatsby
The Gatsby setup is largely the same as the React integration guide, except it goes further into how to use the Gatsby framework's setHeadComponents function, which is used to inject scripts and other common tags for websites.
For additional information, please see How to Add Google Analytics gtag to Gatsby Using Partytown.
Install
npm install @qwik.dev/partytown
yarn add @qwik.dev/partytown
pnpm install @qwik.dev/partytownConfigure
The `` component is imported from the @qwik.dev/partytown/react submodule. The config properties are JSX props.
The following is an example of including the component in `gatsby-ssr.js`. Notice the component is in setHeadComponents, and the example analytics script has the type="text/partytown" attribute.
import React from "react";
import { Partytown } from "@qwik.dev/partytown/react";
export const onRenderBody = ({ setHeadComponents, setPreBodyComponents }) => {
setHeadComponents([
<Partytown key="partytown" debug={true} forward={["dataLayer.push"]} />,
<script
key="analytics"
src="https://example.com/analytics.js"
type="text/partytown"
/>,
]);
// For GTM, we will need to add this noscript tag to the body of the HTML
setPreBodyComponents([
<noscript
key="gtm"
dangerouslySetInnerHTML={{
__html: `
<iframe src="https://www.googletagmanager.com/ns.html?id=xxx-xxxxxxx" height="0" width="0"
style="display:none;visibility:hidden"></iframe>
`,
}}
/>,
]);
};Partytown Script
Add the type="text/partytown" prop for each script that should run from a web worker. The example below is using the React specific way of inlining a script with dangerouslySetInnerHTML.
<script
type="text/partytown"
dangerouslySetInnerHTML={{
__html: "/* Inlined Third-Party Script */",
}}
/>Copy Library Files
Copy library files to static/~partytown. How the files are copied or served from your site is up to each site's setup. A partytown copylib CLI copy task has been provided for convenience which helps copy the Partytown library files to the correct directory. Below is an example of using Gatsby's onPreBuild extension point. This can be added to gatsby-node.js.
const path = require("path");
const { copyLibFiles } = require("@qwik.dev/partytown/utils");
exports.onPreBuild = async () => {
await copyLibFiles(path.join(__dirname, "static", "~partytown"));
};It's worth noting onPreBuild is only called during the build process. If you need to use copyLibFiles while in development you can use onPreBootstrap which is called when running both gatsby build or gatsby develop.
Src Routes Getting Started Index (`docs/src/routes/getting-started/index.mdx`)
title: Getting Started
Getting Started
Partytown is fairly different from most web development libraries in mainly what’s required for its setup and configuration. At the lowest level, Partytown can work with just vanilla HTML, meaning it doesn’t need to be a part of a build process, doesn’t need a bundler, doesn’t require a specific framework, etc. Because it can integrate with any HTML page, it also makes it much easier to then create wrapper components or plugins for almost any ecosystem, such as Shopify, WordPress, Nextjs, Gatsby and much more.
What's different from most web development libraries is that Partytown does not get bundled with your existing site's JavaScript. Instead it purposely wants to stay separate from your code so that it can run within a web worker, and allow your code to run on the main thread. If the two were bundled we've already lost the battle.
Install NPM package
npm install @qwik.dev/partytownNext Steps
- Add Partytown type attribute to Third-Party Scripts
- Add Partytown snippet to the
<head> - Copy Partytown library files
Src Routes Google Tag Manager Index (`docs/src/routes/google-tag-manager/index.mdx`)
title: Google Tag Manager
Google Tag Manager
Add the Google Tag Manager Script
Partytown Script
Set the script element's type attribute to text/partytown. For example:
<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');
</script>Google Analytics 4 (GA4)
GA4 responses come with the correct CORS headers, so you will not have to proxy the requests to www.google-analytics.com. Older versions of Google Analytics required proxying, but have been sunset by Google and are no longer supported.
Note that the automatic user_engagement event GA4 sends when the user leaves the page is not reliable from a web worker. Please see page exit events for more information.
Forward Events
Google Tag Manager uses the dataLayer array to send events. In order for Partytown to forward the calls to window.dataLayer.push({..}), the forward config should add "dataLayer.push". Please see forwarding events and triggers for more information.
Example Config
{
"forward": ["dataLayer.push"]
}Please see the integration docs for framework specific configuration.
Src Routes How Does Partytown Work Index (`docs/src/routes/how-does-partytown-work/index.mdx`)
title: How Does Partytown Work
How Does Partytown Work
Traditionally, communicating between the main thread and worker thread must be asynchronous. Meaning that for the two threads to communicate, they cannot use blocking calls.
Partytown is different. It allows code executed from the web worker to access DOM synchronously. The benefit from this is that third-party scripts can continue to work exactly how they're coded.
For example, the code below works as expected within a web worker:
const rect = element.getBoundingClientRect();
console.log(rect.x, rect.y);First thing you'll notice is that there's no async/await, promise or callback. Instead, the call to getBoundingClientRect() is blocking, and the returned rect value contains the expected x and y properties.
Partytown relies on Web Workers, Service Workers, JavaScript Proxies, and a communication layer between them all.
There are currently two ways to communicate synchronously between the web worker and main thread, and that's sync xhr requests combined with Service Workers, and Atomics.
Designating Web Worker Scripts
It's important to note that Partytown does not automatically move all scripts to the web worker, but prefers an opt-in approach. Meaning, it's best that the developer can pick and choose exactly which scripts should use Partytown, while all the others would go unchanged. Please see the Partytown Scripts for more info.
Partytown is only enabled for specific scripts when they have the type="text/partytown" attribute. This type attribute does two things:
- Prevent the main thread from executing the script.
- Provides a selector for Partytown to query, such as
document.querySelectorAll('script[type="text/partytown"]')
Adding Partytown Attribute
Below is an example of adding the type="text/partytown" attribute to an existing <script>.
- <script>...</script>
+ <script type="text/partytown">...</script>Service Worker
- Scripts are disabled from running on the main thread by using the
type="text/partytown"attribute on the<script/>tag. - Service worker creates an
onfetchhandler to intercept specific requests. - Web worker is given the scripts to execute within the worker thread.
- Web worker creates JavaScript Proxies to replicate and forward calls to the main thread APIs (such as DOM operations).
- Any call to the JS proxy uses synchronous XHR requests.
- Service worker intercepts requests, then is able to asynchronously communicate with the main thread.
- When the service worker receives the results from the main thread, it responds to the web worker's request.
- From the point of view of code executing on the web worker, everything was synchronous, and each call to the document was blocking.
Atomics
Please see the Atomics communication layer docs on how to enable them. When Atomics are not enabled, the fallback is to use the Service Worker instead. In the end, Atomics are preferred because they're roughly 10x faster in transferring data between the web worker and main thread.
- Scripts are disabled from running on the main thread by using the
type="text/partytown"attribute on the<script/>tag. - Main thread detects Atomics communication can be used, and loads the Atomics build instead of the Service Worker build.
- Web worker is given the scripts to execute within the worker thread.
- Web worker creates JavaScript Proxies to replicate and forward calls to the main thread APIs (such as DOM operations).
- Any call to the JS proxy will use Atomics.store() and
postMessage()to send the data to the main thread, and run Atomics.wait(). - Atomics.load() is used once the web worker receives the results data from the main thread.
- From the point of view of code executing on the web worker, everything was synchronous, and each call to the document was blocking.
Serialization
Data passed between the main thread and web worker must be serializable. Partytown automatically handles the serializing and deserializing of data passed between threads. This works for primitive values, such as a string, boolean or number, and also functions, which get unique IDs assigned that are passed to the opposite thread and can be called.
Src Routes Html Index (`docs/src/routes/html/index.mdx`)
title: HTML
HTML
Minimal Example
The smallest possible setup is a static page served next to the Partytown library files, with no build tool at all:
mkdir -p demo/~partytown && cd demo/~partytown
curl -O -L https://unpkg.com/@qwik.dev/partytown@latest/lib/partytown.js
curl -O -L https://unpkg.com/@qwik.dev/partytown@latest/lib/partytown-sw.js
cd .. && python3 -m http.server 8080<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script src="/~partytown/partytown.js"></script>
</head>
<body>
<script type="text/partytown">
console.log('Hello from a web worker!');
</script>
</body>
</html>Open http://localhost:8080/ and the message is logged from the web worker. The one requirement is that the /~partytown/ directory is served from your own origin.
At the lowest level, Partytown is not tied to one specific framework or build tool. Because of this, Partytown can be used in any webpage as long as the HTML is updated, and the library scripts are hosted from the origin.
While the partytown.js file could be an external request, it's recommended to inline the script instead. How the Partytown snippet is inlined into the page depends on each site's setup and an integration may already exist for your framework.
<head>
<script>
/* Inlined Partytown Snippet */
</script>
<script type="text/partytown" src="https://example.com/analytics.js"></script>
</head>One option to load the snippet is from the partytownSnippet() function, exported from the @qwik.dev/partytown/integration submodule.
import { partytownSnippet } from "@qwik.dev/partytown/integration";
const snippetText = partytownSnippet();Configure
The configuration should be added to window using the partytown global object.
Below is an HTML example of setting up the forwarding for Google Tag Manager. Note that the config is before the inlined partytown script.
<head>
<script>
partytown = {
forward: ["dataLayer.push"],
};
</script>
<script>
/* Inlined Partytown Snippet */
</script>
</head>Partytown Script
Add the type="text/partytown" attribute for each script that should run from a web worker.
<script type="text/partytown">
/* Inlined Third-Party Script */
</script>Copy Library Files
How the files are copied or served from your site is up to each site's setup. A partytown copylib CLI copy task has been provided for convenience which helps copy the Partytown library files to the public directory.
Src Routes Integrations Index (`docs/src/routes/integrations/index.mdx`)
title: Integration Guides
Integration Guides
Partytown can work with any HTML page, and doesn't require a specific framework. However, to make it easier to use in various frameworks or services, plugins/wrappers can be made for their ecosystem. Below is a current list of integrations. Please help us add more!
- Angular
- Astro
- Drupal
- Gatsby
- HTML
- NextJS
- Nuxt
- Qwik
- React
- Remix
- Shopify Hydrogen
- Solid
- SvelteKit
- TanStack Start
- WordPress
See something missing and would like to contribute? Please open an issue and we'd love to work with you to get your integration up and running and listed here. It may be as simple as just writing a doc, similar to the ones above, detailing how to best implement Partytown in the framework or service.
Src Routes Magento2 Index (`docs/src/routes/magento2/index.mdx`)
title: Magento 2
Magento 2
To integrate the partytown library functionality into your Magento 2 project, you can use the integration module for Magento2 :
Module for integrating BuilderIO/partytown library functionality into Magento 2 projectRequirements
Install
composer config repositories.perspective-magento2-partytown git [email protected]:rostilos/perspective-partytown.git
composer require perspective/magento2-partytown dev-master
php bin/magento setup:upgrade
bin/magento setup:di:compile
php bin/magento setup:static-content:deployNext, clear all the cache
Updating partytown lib npm packages
The library files provided by the npm package @qwik.dev/partytown are already added to the module files, but in case you want to update them, it is recommended to do the following :
cd <magento-root>/vendor/perspective/magento2-partytown
npm install ( node v16+ required )
npm run partytownConfigure
In order for scripts to be loaded and executed in a separate thread of the webworker - you need to set the appropriate type ( text/partytown ) when connecting them. Using the example of GMT :
<script type="text/partytown">
(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');
</script>Configuration of the module is managed through the admin panel, where you can select the list of available analytics services that will be allocated for execution in the webworker, as well as specify other configurations that affect the functionality of the library
You can find the configurations at the following path :
Stores -> Configuration -> Perspective -> Partytown
Optional: reverse-proxying scripts
Given the peculiarities of the partytown library, you may encounter CORS errors when using certain scripts whose resources do not give the required headers. To avoid this, use reverse-proxying, the peculiarities and possible implementations are described here:
You can also try using a self-hosted reverse-proxy solution
( see example here Node.js reverse-proxy example for partytown library and here Nginx reverse-proxy example for partytown library ( for warden ) ).
Troubleshooting
If you encounter errors when using the integration module for Magento 2 - you can also create an issue in the source repository
Src Routes Nextjs Index (`docs/src/routes/nextjs/index.mdx`)
title: Next.js
Next.js
The Next.js setup is largely the same as the React integration guide, except it goes further into how to add to the NextJS framework's <Head/> component.
Worker Strategy
The Next.js <Script/> component provides an experimental worker strategy, which uses Partytown under-the-hood. Please see the Next.js Script documentation for more information.
Note: The
workerstrategy is currently unsupported with the Next.js 13+appdirectory.
Below are the instructions if you are not using the experimental Worker Strategy.
Install
npm install @qwik.dev/partytown
yarn add @qwik.dev/partytown
pnpm install @qwik.dev/partytownConfigure
The `` component is imported from the @qwik.dev/partytown/react submodule. The config properties are JSX props.
The following is an example of including the component in a Nextjs page. Notice the component is in the <Head>, and the example analytics script has the type="text/partytown" attribute.
import Head from "next/head";
import { Partytown } from "@qwik.dev/partytown/react";
const Home = () => {
return (
<>
<Head>
<title>My App</title>
<Partytown debug={true} forward={["dataLayer.push"]} />
<script src="https://example.com/analytics.js" type="text/partytown" />
</Head>
<main>...</main>
</>
);
};
export default Home;Partytown Script
Add the type="text/partytown" prop for each script that should run from a web worker. The example below is using the React specific way of inlining a script with dangerouslySetInnerHTML.
<script
type="text/partytown"
dangerouslySetInnerHTML={{
__html: "/* Inlined Third-Party Script */",
}}
/>Copy Library Files
Below are the instructions if you are not using the experimental Worker Strategy.
Copy library files to public/~partytown. How the files are copied or served from your site is up to each site's setup. A partytown copylib CLI copy task has been provided for convenience which helps copy the Partytown library files to the public directory. Below is an example of creating a "partytown" NPM script which could run before the build:
"scripts": {
"build": "npm run partytown && next build",
"partytown": "partytown copylib public/~partytown"
}Src Routes Nuxt Index (`docs/src/routes/nuxt/index.mdx`)
title: Nuxt
Nuxt
There is a first-class Nuxt integration for partytown.
Install
Add @nuxtjs/partytown dependency to your project.
npm install --save-dev @nuxtjs/partytown
yarn add --dev @nuxtjs/partytown
pnpm install --save-dev @nuxtjs/partytownConfigure
Add @nuxtjs/partytown to the modules section of nuxt.config.ts. Use the partytown property for the configuration.
import { defineNuxtConfig } from "nuxt";
export default defineNuxtConfig({
modules: ["@nuxtjs/partytown"],
partytown: {
/* any partytown-specific configuration */
},
});Partytown Script
Add type: 'text/partytown' attribute to any scripts you want to be handled by partytown.
<template>
<div>
<script type="text/partytown" src="https://example.com/analytics.js" />
</div>
</template>Note that the Nuxt Partytown module already handles copying the library files to the correct location.
Src Routes Partytown Scripts Index (`docs/src/routes/partytown-scripts/index.mdx`)
title: Partytown Scripts
Partytown Scripts
Partytown Script Type
Add the type="text/partytown" attribute to each individual third-party script to run from a web worker. Note that each script is opt-in, meaning that the updated type attribute should only be added to scripts that should run with Partytown. Partytown will not automatically upgrade any scripts unless this attribute is added.
- <script>...</script>
+ <script type="text/partytown">...</script>Why `type="text/partytown"`?
The type="text/partytown" attribute does two things:
- Informs the browser to not process the script. By giving the script a type attribute which the browser does not recognize: "The embedded content is treated as a data block which won't be processed by the browser."
- Provides a query selector so Partytown can find all the scripts to run from within the web worker. When the document is ready and Partytown has initialized, Partytown will then query selector for all of the
script[type="text/partytown"]script elements. You'll notice that after a script has been executed in the web worker, it'll then get an updated type attribute oftype="text/partytown-x".
Dynamically Appending Scripts
Commonly, scripts will already be a part of the DOM when the document loads, and once the document is ready and the window has loaded, Partytown will find the scripts and start executing them in the web worker. However, if a script is dynamically appended to the DOM, after Partytown has initialized, you can still dispatch the ptupdate custom event on window to notify Partytown there are new scripts to find.
Below is an example of dynamically appending a script to the document, then notifying Partytown to run its update again. Notice that before the script is appended, the type property (or attribute) is already set to text/partytown.
const script = document.createElement("script");
script.type = "text/partytown";
script.innerHTML = `console.log("New partytown script!")`;
document.head.appendChild(script);
window.dispatchEvent(new CustomEvent("ptupdate"));Integrations
Please see the integration guides for more information on how to setup Partytown.
Src Routes Proxying Requests Index (`docs/src/routes/proxying-requests/index.mdx`)
title: Proxying Requests
Proxying Requests
Often third-party scripts are added to the page by appending a script tag, such as:
var script = document.createElement("script");
script.url = "http://some-third-party-script.com/tracking.js";
document.head.appendChild(script);When the <script> element is appended to the <head> using this traditional approach, the script's HTTP response does not require Cross-Origin Resource Sharing (CORS) headers.
However, because Partytown requests the scripts within a web worker using fetch(), then the script's response requires the correct CORS headers.
Many third-party scripts already provide the correct CORS headers, but not all do. For services that do not add the correct headers, then a reverse proxy to another domain must be used in order to provide the CORS headers.
CORS Response Header
Access-Control-Allow-Origin: *Configuring URL Proxies
When executed from within Partytown, every URL is resolved by Partytown, which also gives users the ability to inspect and modify any and every URL being resolved. The resolveUrl() config can be used to check for a specific URL, and optionally return the proxied URL instead. For example, in the code below we're checking if the URL to be resolved is for Google Analytics, and if so, return a different URL that points to a reverse proxy.
Vanilla Config Example
partytown = {
resolveUrl: function (url, location, type) {
if (type === "script") {
var proxyUrl = new URL("https://my-reverse-proxy.com/");
proxyUrl.searchParams.append("url", url.href);
return proxyUrl;
}
return url;
},
};Please see the integration guides and configuration for more information.
Reverse Proxy
Below are a few examples of reverse proxies that could be used.
- Apache
- AWS: Creating a Reverse Proxy for Partytown with AWS Cloudfront
- Cloudflare
- Gatsby Cloud: Redirects and Rewrites
- Netlify: Proxy to another service
- NGINX
- Vercel: Rewrites
Serving Resources Locally
Another option to work around the Access-Control-Allow-Origin issue without using a proxy is to serve your third-party JavaScript and other resources locally.
So instead of inserting something like this on your page for Google Analytics:
<script type="text/partytown">
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>You instead download the analytics.js JavaScript resource, and serve it locally from your domain like this:
<script type="text/partytown">
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://example.com/analytics.js','ga');
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>Since you are serving the resource from your own server, you control the headers that come along with it, including the Access-Control-Allow-Origin header that the proxy work-around is sometimes needed for.
As a bonus, serving these resources locally is one less DNS lookup that needs to happen before the client can load your site.
You can manually download these self-hosted third-party JavaScript resources, or you can use a webpack plugin like save-remote-file-webpack-plugin to do it as part of your build process:
const SaveRemoteFilePlugin = require("save-remote-file-webpack-plugin");
module.exports = {
plugins: [
new SaveRemoteFilePlugin([
{
url: "https://google-analytics.com/analytics.js",
filepath: "js/analytics.js",
},
]),
],
};Src Routes React Index (`docs/src/routes/react/index.mdx`)
title: React
React
The Partytown NPM package already comes with a React component, which is a thin wrapper to the Partytown snippet. This React component should be usable from most React/Preact projects.
Install
npm install @qwik.dev/partytown
yarn add @qwik.dev/partytown
pnpm install @qwik.dev/partytownConfigure
The `` component is imported from the @qwik.dev/partytown/react submodule. The config properties are JSX props.
Below is an example of setting the debug config to true, and forward config for Google Tag Manager.
import { Partytown } from "@qwik.dev/partytown/react";
export function Head() {
return (
<>
<Partytown debug={true} forward={["dataLayer.push"]} />
</>
);
}Partytown Script
Add the type="text/partytown" prop for each script that should run from a web worker. The example below is using the React specific way of inlining a script with dangerouslySetInnerHTML.
<script
type="text/partytown"
dangerouslySetInnerHTML={{
__html: "/* Inlined Third-Party Script */",
}}
/>Copy Library Files
Copy library files to public/~partytown. How the files are copied or served from your site is up to each site's setup. A partytown copylib CLI copy task has been provided for convenience which helps copy the Partytown library files to the public directory. Below is an example of creating a "partytown" NPM script which could run before the build:
"scripts": {
"partytown": "partytown copylib public/~partytown"
}Src Routes Remix Index (`docs/src/routes/remix/index.mdx`)
title: Remix
Remix
The Remix setup is largely the same as the React integration guide, except it goes further into how to add to the Remix framework's app root component.
Install
npm install @qwik.dev/partytown
yarn add @qwik.dev/partytown
pnpm install @qwik.dev/partytownConfigure
The `` component is imported from the @qwik.dev/partytown/react submodule. The config properties are JSX props.
The following is an example of including the component in the app's root component. Notice the component is in the <head> and before the <Meta/> component. The example below is setting the forward config for Google Tag Manager.
import { Partytown } from "@qwik.dev/partytown/react";
import { Links, Meta, Outlet } from "@remix-run/react";
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Partytown debug={true} forward={["dataLayer.push"]} />
<Meta />
<Links />
</head>
<body>...</body>
</html>
);
}Partytown Script
Add the type="text/partytown" prop for each script that should run from a web worker. The example below is using the React specific way of inlining a script with dangerouslySetInnerHTML.
<Partytown debug={true} forward={['dataLayer.push']} />
<script
type="text/partytown"
dangerouslySetInnerHTML={{
__html: '// inlined third-party script',
}}
/>Copy Library Files
Copy library files to public/~partytown. How the files are copied or served from your site is up to each site's setup. A partytown copylib CLI copy task has been provided for convenience which helps copy the Partytown library files to the public directory. Below is an example of creating a "partytown" NPM script which could run before the build:
"scripts": {
"build": "npm run partytown && cross-env NODE_ENV=production remix build",
"partytown": "partytown copylib public/~partytown"
}Src Routes Sandboxing Index (`docs/src/routes/sandboxing/index.mdx`)
title: Sandboxing
Sandboxing
Third-party scripts are often a black-box with large amounts of code. What's buried within the obfuscated code is difficult to tell. It's minified for good reason, but regardless it becomes very difficult to understand what third-party scripts are executing on your site and your user's devices.
Partytown on the other hand, is able to isolate and sandbox third-party scripts within a web worker, and allow, or deny, access to main thread APIs. This includes cookies, localStorage, or the entire document. Because the code is executed within the worker, and their access to the main thread must go through the proxy, Partytown is able to give developers control over what the scripts can execute.
Essentially, Partytown lets you:
- Isolate third-party scripts within a sandbox (web worker).
- Configure which browser APIs specific scripts can, and cannot, execute.
- Option to log API calls and passed in arguments in order to give better insight as to what the scripts are doing.
Src Routes Shopify Hydrogen Index (`docs/src/routes/shopify-hydrogen/index.mdx`)
title: Shopify Hydrogen
Shopify Hydrogen
The Shopify Hydrogen setup is largely the same as the React integration guide, except it goes further into how to add to the framework's app root component.
Install
npm install @qwik.dev/partytown
yarn add @qwik.dev/partytown
pnpm install @qwik.dev/partytownConfigure
The `` component is imported from the @qwik.dev/partytown/react submodule. The config properties are JSX props.
The following is an example of including the component in a root [Shopify component](https://shopify.dev/). Notice the component before the <DefaultSeo/> component.
import { DefaultRoutes } from "@shopify/hydrogen";
import { Suspense } from "react";
import DefaultSeo from "./components/DefaultSeo.server";
import NotFound from "./components/NotFound.server";
import AppClient from "./App.client";
import LoadingFallback from "./components/LoadingFallback";
import { Partytown } from "@qwik.dev/partytown/react";
export default function App({ log, pages, ...serverState }) {
return (
<Suspense fallback={<LoadingFallback />}>
<AppClient helmetContext={serverState.helmetContext}>
<Partytown debug={true} forward={["dataLayer.push"]} />
<DefaultSeo />
<DefaultRoutes
pages={pages}
serverState={serverState}
log={log}
fallback={<NotFound />}
/>
</AppClient>
</Suspense>
);
}The example above is setting the forward config for Google Tag Manager.
Partytown Script
Add the type="text/partytown" prop for each script that should run from a web worker. The example below is using the React specific way of inlining a script with dangerouslySetInnerHTML.
<Partytown debug={true} forward={['dataLayer.push']} />
<script
type="text/partytown"
dangerouslySetInnerHTML={{
__html: '// inlined third-party script',
}}
/>Copy Library Files
Copy library files to public/~partytown. How the files are copied or served from your site is up to each site's setup. A partytown copylib CLI copy task has been provided for convenience which helps copy the Partytown library files to the public directory. Below is an example of creating a "partytown" NPM script which could run before the build:
"scripts": {
"dev": "npm run partytown && vite",
"build": "npm run partytown && yarn build:client && yarn build:server && yarn build:worker",
"build:client": "vite build --outDir dist/client --manifest",
"build:server": "vite build --outDir dist/server --ssr src/entry-server.jsx",
"build:worker": "cross-env WORKER=true vite build --outDir dist/worker --ssr worker.js",
"partytown": "partytown copylib public/~partytown"
}Src Routes Shopify Os2 Index (`docs/src/routes/shopify-os2/index.mdx`)
title: Shopify OS 2.0
Shopify OS 2.0
Shopify OS 2.0 frontends are built with the Liquid templating language. Since it compiles to HTML, the HTML integration guide is a good starting point. There are however a few platform based challenges and limitations you have to work around.
Theme Files & Partydawn
The integration of Partytown largely depends on the theme you're using and the structure of its files. It's impossible to cover all different cases and you'll need to figure stuff out based on the examples given on this page. This should be viewed more as a template and not a full guide.
Shown code and steps are based on Partydawn, an example of Shopify's Dawn theme that integrates Partytown.
Requirements
- You need to be able to set up an app proxy that can serve partytown assets
- Learn the basics of Shopify Web Pixels
Can't Hurt
- Shopify's Customer Privacy API
- Experience with cookie consent management
- Make yourself comfortable with your theme's structure and file system
Install
In the root file of your theme, add the Partytown snippet and config object. This most likely is the file that contains the {{ contents_for_header }} variable.
<script>
window.partytown = {
forward: ['dataLayer.push' /**, ... */],
// you cannot host partytown within Shopify OS 2.0
// so you need to proxy assets
resolveUrl: function (url, location, type) {
if (type === 'script' /** extend as needed */) {
const proxyUrl = new URL('/apps/partytown/proxy', location.href);
proxyUrl.searchParams.append('url', url.href);
return proxyUrl;
}
return url;
};
};
</script>
<script>
/* Inlined Partytown Snippet */
</script>It's important to understand that you cannot host Partytown assets on Shopify OS 2.0. So your app proxy needs to serve them.
Then add the type="text/partytown" attribute for each script that should run from the web worker as usual.
Hooking Into the Web Pixel
Tracking events in Shopify isn't as straight forward as with other integrations. The checkout is fully controlled by Shopify, meaning you cannot fire some important events manually (purchase). Additionally some customization to your theme has to be done to find the right places to track cart events etc. The easiest way to deal with tracking in Shopify is through Web Pixels, which offer an API to hook into Shopify's own event stream.
You however cannot get Partytown to work inside Web Pixels because it's a sandboxed environment in an iframe without access to the top frame. To install the Tag Manager inside a Pixel you can follow the official documentation. The approach should be similar with other vendors.
The only exception is that you don't want to load the tracking scripts directly in the sandbox. Instead we inject the script as text/partytown in the regular page and create an event bridge between the Pixel and the main page using the sessionStorage that is available in the Pixel. We push events to the storage, pick them up on the main page, and forward them to Partytown.
// inside the Pixel
browser.sessionStorage.setItem(`pt_dl_` + inc++, JSON.stringify(data));
// on the main page
function isSessionStorageAvailable() {
var test = "test";
try {
sessionStorage.setItem(test, test);
sessionStorage.removeItem(test);
return true;
} catch (e) {
return false;
}
}
if (isSessionStorageAvailable()) {
var inc = 0;
window.setInterval(function () {
while (sessionStorage.getItem("pt_dl_" + inc) !== null) {
window.dataLayer.push(JSON.parse(sessionStorage.getItem("pt_dl_" + inc)));
sessionStorage.removeItem("pt_dl_" + inc);
inc++;
}
// adjust the interval as you see fit
}, 1000);
}In case sessionStorage is not available, the Pixel should install the tracking scripts conventionally. This also has to be done on checkout pages which don't work with Partytown. You have all the tools inside Pixels to perform these checks and operations.
You can certainly also use window.postMessage to build an event bridge between the Web Pixel (iframe) and the top frame. But sessionStorage is directly supported by the Web Pixel API.
Tracking Outside the Web Pixels
You can still implement manual tracking outside Web Pixels. In that case you need to add additional tracking inside Settings-> Checkout -> Order Status Page Additional Scripts to track purchases and conversions. Please note that this is deprecated. The current deadline is August 28, 2025. Although Shopify has pushed it back multiple times, you will eventually have to switch to Web Pixels or Checkout Extensions.
Use the Mutation Observer to Move Scripts into the Worker
Unfortunately you don't have full control over all scripts that are injected into the site. Some stuff will get added by Shopify automatically. We can thankfully still manipulate inline scripts using a MutationObserver. Changing the type to text/partytown will prevent script execution and the script will instead be processed by Partytown.
This way you can offload Shopify's own trekkie tracking and run the standard integrations for Google Analytics and Facebook through Partytown.
Src Routes Solid Index (`docs/src/routes/solid/index.mdx`)
title: Solid
Solid
To setup Partytown in an Solid project take the following steps.
Install
npm install @qwik.dev/partytown
yarn add @qwik.dev/partytown
pnpm install @qwik.dev/partytownUse your favorite package manager to install @qwik.dev/partytown dependency and
copy the Partytown files to the local filesystem using the Vite plugin.
Adopting this strategy from the Partytown + Vite docs:
// vite.config.js
...
import { join } from 'path';
import { partytownVite } from '@qwik.dev/partytown/utils';
export default defineConfig({
plugins: [
...
partytownVite({
dest: join(__dirname, 'dist', '~partytown'),
}),
],
...
});Script Setup
The snippet below shows you how to setup Partytown scripts in your index.html file. The first script tag takes the Partytown configuration. The second script tag references the Partytown JS file and the third file takes the third-party scripts to be loaded on the web-worker.
<head>
<script>
partytown = {
...
};
</script>
<script src="/~partytown/debug/partytown.js"></script>
<script type="text/partytown">
console.log("New Partytown script.");
console.log("Third-Party script goes here!");
</script>
</head>Src Routes Sveltekit Index (`docs/src/routes/sveltekit/index.mdx`)
title: SvelteKit
SvelteKit
SvelteKit uses Vite to build, so we can use partytownVite.
- Copy the Partytown files to the local filesystem using the Vite plugin
- Add the Partytown script to
src/routes/+layout.svelte - Then add 3rd party scripts
- Optional: reverse-proxying scripts
1. Copy the Partytown files to the local filesystem using the Vite plugin
Adopting this strategy from the Partytown + Vite docs:
// vite.config.js
import { join } from "path";
import { sveltekit } from "@sveltejs/kit/vite";
import { partytownVite } from "@qwik.dev/partytown/utils";
/** @type {import('vite').UserConfig} */
const config = {
plugins: [sveltekit(), partytownVite()],
};
export default config;2. Add the Partytown script to `src/routes/+layout.svelte`
Adapting from the HTML integration guide
// src/routes/+layout.svelte
<script>
import { partytownSnippet } from '@qwik.dev/partytown/integration'
</script>
<svelte:head>
{@html '<script>' + partytownSnippet() + '</script>'}
</svelte:head>3. Then add 3rd party scripts
This is where we use partytown to add those scripts (note type="text/partytown" below). This example shows Google Tag Manager. Putting it together with the previous changes, our +layout.svelte looks like:
// src/routes/+layout.svelte
<script>
import { partytownSnippet } from '@qwik.dev/partytown/integration'
</script>
<slot />
<svelte:head>
<script>
// Forward the necessary functions to the web worker layer
partytown = {
forward: ['dataLayer.push']
};
</script>
{@html '<script>' + partytownSnippet() + '</script>'}
<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');
</script>
</svelte:head>4. Optional: reverse-proxying scripts
This will only be necessary depending on which scripts you are using. The implementation will vary depending on hosting platform. See Partytown's recommended guides.
Acknowledgements: credit belongs to monogram.io for an earlier version of this guide.
Src Routes Tanstack Start Index (`docs/src/routes/tanstack-start/index.mdx`)
title: TanStack Start
TanStack Start
TanStack Start apps are Vite-based, so Partytown integrates with the snippet plus the library files served from your origin.
Copy Library Files
Copy the Partytown library files into the public directory so they're served from /~partytown/:
npx @qwik.dev/partytown copylib public/~partytownRun it from a package.json script so the files stay up to date:
{
"scripts": {
"dev": "partytown copylib public/~partytown && vite dev",
"build": "partytown copylib public/~partytown && vite build"
}
}Add The Snippet
In your root route, inline the snippet with the config before it, and mark third-party scripts with type="text/partytown":
// src/routes/__root.tsx
import { partytownSnippet } from '@qwik.dev/partytown/integration';
export const Route = createRootRoute({
head: () => ({
scripts: [
{
children: 'partytown = { forward: ["dataLayer.push"] };',
},
{
children: partytownSnippet(),
},
{
type: 'text/partytown',
src: 'https://example.com/analytics.js',
},
],
}),
component: RootComponent,
});Note that the config and snippet scripts must not have the text/partytown type — they're the small bit of JavaScript that runs on the main thread to initialize everything.
Services like Google Tag Manager also need forwarding configured, and services without CORS headers need request proxying — both work the same as any other Partytown setup.
Src Routes Trade Offs Index (`docs/src/routes/trade-offs/index.mdx`)
title: Trade-Offs
Trade-Offs
Nothing is without trade-offs. Using Partytown to orchestrate third-party scripts vs. adding them to your pages the traditional way has the following considerations:
Intercepted Network Requests
You'll notice in the network tab there may be hundreds of requests to proxytown. Even though these requests are listed in the network tab, each of these are intercepted by the service worker and handled by Partytown locally, and they are not external HTTP requests. Using these locally intercepted network requests is how the service worker communication layer is able to work.
These requests showing up are more of an annoyance than anything else, since they shouldn't be affecting performance or Lighthouse scores. Additionally, they can give you an insight into what certain third-party scripts are up to. See the debugging documentation for more info.
To hide them while debugging, filter them out of the network tab with -url:proxytown in Chrome DevTools.
Throttled DOM Operations
DOM operations within the worker are purposely throttled, slowing down their execution compared to the same code running on the main thread. (We also see this as a feature.) Because their code is running in another thread and needs to send and receive data between the two, it may take a few milliseconds to perform a blocking operation. This ties into why UI intensive scripts may not be the best fit for Partytown.
Partytown does however batch most operations together to reduce the calls between threads.
UI Intensive Third-Party Scripts
For a third-party script that is UI intensive, meaning the script builds up a mini-app within your page, Partytown may not be best suited for that script. For example, the third-party script may popup a dialog with a form inside of it.
Every script is different, and each are performing operations in different ways, so it's difficult to state exactly which services do and do not work well with Partytown. While Partytown may work great for some UI implementations, it may not work well for others. For this reason, Partytown is opt-in, and you can explicitly set which scripts to use it on.
Partytown is best suited for third-party scripts such as Google Tag Manager or Facebook Pixel, since they're only handling user events and lazily posting data to their services in the background. Third-party scripts that insert a large amount of DOM nodes to a page may not be the best candidates.
Third-Party Scripts Without CORS Headers
While most third-party scripts add the correct Cross-Origin Resource Sharing (CORS) headers, such as Access-Control-Allow-Origin: *, not all do. Please see the Proxying Requests documentation on how to use Partytown with any services that do not add the correct headers.
Events Cannot Prevent Default
Events handled by third-party scripts that call event.preventDefault() will have no effect. Partytown is able to block when the code is ran within the web worker, but not in the other direction.
For example, when a user clicks a link on the main thread, a third-party script may have an event handler on that same link, which may have event.preventDefault(). By the time the web worker receives an event that the link was clicked, it is no longer a synchronous operation and calling preventDefault() has no effect. As a side note, you could also see this as a feature when third-party scripts are abusing scroll events without using passive event listeners.
Cross-Origin IFrame Cookies And Storage
If an iframe was created with Partytown, and the iframe is cross-origin, then Partytown is unable to get or set the origin's document.cookie, localStorage or sessionStorage. The code will continue to work, however, cookies and storage will not persist.
Setting document.cookie is a noop and will not assign anything, and getting a cookie will always return an empty string. Both localStorage and sessionStorage work as expected, however, the localStorage data is not saved across browser sessions. In these scenarios a console.warn will be logged.
Note this is only an issue for iframes, created from within a Partytown script, and the created iframe is cross-origin. Partytown scripts, and iframes that have the same origin, do not have this issue.
Page Exit Events
Handlers that third-party scripts add for page exit events, such as pagehide, visibilitychange and beforeunload, are not reliable when the page is unloading (a real navigation, reload or tab close; SPA route changes are not affected).
The main thread only forwards the event to the web worker and returns right away, so the browser goes on to unload the page. The web worker belongs to the page and is terminated with it, often before the handler has run. Even when the handler does start, any DOM read it makes (such as document.cookie or document.hasFocus()) needs an answer from the main thread, which is no longer available. The main thread cannot wait for the web worker either, since browsers block synchronous requests during page dismissal.
For example, the automatic Google Analytics 4 user_engagement event, which reports the remaining engagement time when the user leaves the page, is not sent when GA4 runs in Partytown. Events sent while the page is open, such as page_view, are not affected. If exit tracking is important, load that script on the main thread instead.
Service Worker
For the Service Worker build, a total of three threads are used: Main Thread, Web Worker, and Service Worker. Note that the Atomics build only uses two threads.
Not Always The Best Fit
While Partytown works great for some third-party scripts, it may not be the best fit for every single script out there (and that's ok). For example, some third-party scripts have a setInterval() loop that continually reads through the entire document every X milliseconds. This is just bad practice to begin with, but when these scripts are run from Partytown then it's visible that Partytown is overworked trying to keep up the script's layout thrashing code. Since every script is different, it's difficult to state exactly which scripts do and do not work well with Partytown. For this reason, Partytown is opt-in, and you can explicitly set which scripts to use it on.
Src Routes Wordpress Index (`docs/src/routes/wordpress/index.mdx`)
title: WordPress
WordPress
Partytown can be added to any WordPress site from a theme (or child theme), without a plugin. The setup is the same as any HTML integration: host the library files from your own origin, inline the snippet, and mark the third-party scripts with type="text/partytown".
Copy Library Files
Copy the Partytown library files into your site's webroot so they're served from https://your-site.com/~partytown/:
npx @qwik.dev/partytown copylib ~partytownUpload the generated ~partytown directory to the root of your WordPress installation (next to wp-content). Any location works as long as it's served from your own origin — if you place it elsewhere, set the lib config to that path.
Inline The Snippet
In your theme's functions.php, print the config and the snippet in the <head> before any other scripts:
add_action('wp_head', function () {
// keep this before the snippet
echo '<script>partytown = { forward: ["dataLayer.push"] };</script>';
// partytown.js contents copied to the theme, or printed inline
echo '<script>' . file_get_contents(ABSPATH . '~partytown/partytown.js') . '</script>';
}, 1);Mark Scripts For Partytown
Scripts printed directly in your theme just need the type attribute:
<script type="text/partytown" src="https://example.com/analytics.js"></script>For scripts registered through wp_enqueue_script(), change their type with the script_loader_tag filter:
add_filter('script_loader_tag', function ($tag, $handle) {
$partytown_handles = ['google-analytics', 'my-tracking-script'];
if (in_array($handle, $partytown_handles)) {
return str_replace(" src", " type='text/partytown' src", $tag);
}
return $tag;
}, 10, 2);Plugins That Print Their Own Scripts
Analytics plugins that print their own <script> tags (e.g. GTM plugins) don't offer a way to change the script type. In that case either configure the plugin to output only a container ID and print the loader yourself with type="text/partytown", or skip Partytown for that plugin.
Note that a dataLayer.push forward config is required for Google Tag Manager, and services without CORS headers need request proxying — both work the same as any other Partytown setup.
Package.Json (`docs/package.json`)
{
"name": "my-qwik-empty-starter",
"description": "Blank project with routing included",
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"engines-annotation": "Mostly required by sharp which needs a Node-API v9 compatible runtime",
"private": true,
"trustedDependencies": [
"sharp"
],
"trustedDependencies-annotation": "Needed for bun to allow running install scripts",
"type": "module",
"scripts": {
"build": "qwik build",
"build.client": "vite build",
"build.preview": "vite build --ssr src/entry.preview.tsx",
"build.server": "vite build -c adapters/cloudflare-pages/vite.config.mts",
"build.types": "tsc --incremental --noEmit",
"deploy": "wrangler pages deploy ./dist",
"dev": "vite --mode ssr",
"dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
"fmt": "prettier --write .",
"fmt.check": "prettier --check .",
"lint": "eslint "src/**/.ts"",
"preview": "qwik build preview && vite preview --open",
"serve": "wrangler pages dev ./dist --compatibility-flags=nodejs_als",
"start": "vite --open --mode ssr",
"qwik": "qwik"
},
"devDependencies": {
"@qwik.dev/core": "2.0.0-beta.27",
"@qwik.dev/router": "2.0.0-beta.27",
"@types/eslint": "8.56.10",
"@types/node": "^20.19.0",
"@typescript-eslint/eslint-plugin": "7.16.1",
"@typescript-eslint/parser": "7.16.1",
"autoprefixer": "^10.4.19",
"clsx": "^2.1.1",
"eslint": "8.57.0",
"eslint-plugin-qwik": "2.0.0-beta.27",
"postcss": "^8.4.39",
"prettier": "3.6.2",
"rollup": "^4.52.0",
"prettier-plugin-tailwindcss": "^0.5.4",
"rehype-pretty-code": "^0.14.0",
"sass": "^1.83.0",
"shiki": "^1.24.3",
"tailwind-merge": "^2.5.5",
"tailwindcss": "^3.4.6",
"typescript": "5.4.5",
"unist-util-visit": "^5.0.0",
"vite": "^7.3.1",
"vite-tsconfig-paths": "^4.2.1",
"wrangler": "^3.0.0"
}
}
Tsconfig.Json (`docs/tsconfig.json`)
{
"compilerOptions": {
"allowJs": true,
"target": "ES2017",
"module": "ES2022",
"lib": ["es2022", "DOM", "WebWorker", "DOM.Iterable"],
"jsx": "react-jsx",
"jsxImportSource": "@qwik.dev/core",
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"allowImportingTsExtensions": true,
"moduleResolution": "Bundler",
"esModuleInterop": true,
"skipLibCheck": true,
"incremental": true,
"isolatedModules": true,
"outDir": "tmp",
"noEmit": true,
"paths": {
"~/": ["./src/"]
}
},
"files": ["./.eslintrc.cjs"],
"include": ["src", "./.d.ts", "./.config.ts"]
}
.Vscode Launch.Json (`docs/.vscode/launch.json`)
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Chrome",
"request": "launch",
"type": "chrome",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}"
},
{
"type": "node",
"name": "dev.debug",
"request": "launch",
"skipFiles": ["
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/node_modules/vite/bin/vite.js",
"args": ["--mode", "ssr", "--force"]
}
]
}
Public Manifest.Json (`docs/public/manifest.json`)
{
"$schema": "https://json.schemastore.org/web-manifest-combined.json",
"name": "qwik-project-name",
"short_name": "Welcome to Qwik",
"start_url": ".",
"display": "standalone",
"background_color": "#fff",
"description": "A Qwik project app."
}
Src Routes Menu (`docs/src/routes/menu.md`)
Docs Table Of Contents
Introduction|/
Getting Started|/getting-started
Integrations|/integrations
- Angular
- Astro
- Drupal
- Gatsby
- HTML
- Magento 2
- Next.js
- Nuxt
- Qwik
- React
- Remix
- Shopify Hydrogen
- Shopify OS 2.0
- Solid
- SvelteKit
- TanStack Start
- WordPress
Configuration|/configuration
Third-Party Services|/common-services
Community
--- METRICS ---
- Files Extracted: 44
- Estimated Token Budget: ~31326 tokens
- Canonical Reference: https://codewiki.google/github.com/QwikDev/partytown