# Performance

URL: https://supapulse.app/docs/performance

What the Supapulse tracker costs your page, how it loads and sends, and how to keep it off the critical path.

## The numbers [#the-numbers]

| Build | Minified | Gzipped |
| --- | --- | --- |
| Script tag (`dist/browser.js`) | about 6 KB | about 2.3 KB |
| `@supapulse/core` from npm | about 6 KB | about 2.2 KB |
| `@supapulse/react` on top of core | about 1 KB | about 0.6 KB |

Neither package has runtime dependencies. Measure your own bundle to confirm; these come from the 1.0 builds.

## What the tracker does at runtime [#what-the-tracker-does-at-runtime]

- **One request per pageview or event.** Sent with `navigator.sendBeacon`, which doesn't block the page or delay unload. Events aren't batched.
- **No work on the main thread beyond building a small JSON payload.** No polling, timers or DOM observers. The only timers are retries after a failed send.
- **History patching.** `pushState` and `replaceState` are wrapped to schedule a pageview on the next tick.
- **Nothing persisted.** No storage reads or writes.

## Loading the script tag [#loading-the-script-tag]

Use `defer`:

```html
<script
  defer
  src="https://cdn.jsdelivr.net/npm/@supapulse/core@1/dist/browser.js"
  data-token="YOUR_PUBLIC_TOKEN"
></script>
```

`defer` downloads in parallel and runs after the HTML is parsed, so it doesn't block rendering. `async` works too, but runs at an unpredictable point, which makes `window.supapulse` harder to rely on from your own scripts.

To shave the connection setup, add a preconnect for the CDN and the API:

```html
<link rel="preconnect" href="https://cdn.jsdelivr.net" />
<link rel="preconnect" href="https://supapulse.app" />
```

## Loading from npm [#loading-from-npm]

`@supapulse/core` is small enough to bundle with your app. If you'd rather keep it out of the initial chunk, import it after the page is interactive:

```ts
window.addEventListener('load', async () => {
  const { Supapulse } = await import('@supapulse/core');
  const supapulse = new Supapulse();
  supapulse.init({ apiToken: 'pub_your_token' });
});
```

Late initialization still records the first pageview, because `init()` sends one. Events sent before then are lost, so don't do this if you track events during load.

## Content Security Policy [#content-security-policy]

```text
script-src 'self' https://cdn.jsdelivr.net;
connect-src 'self' https://supapulse.app;
```

`script-src` is only needed for the script tag.

## Server-side revenue [#server-side-revenue]

A revenue call is one HTTPS request from your webhook handler. The API queues it and answers without waiting for it to be stored. Await it before returning, so a failure can be retried.

## Measuring [#measuring]

- **Lighthouse or WebPageTest:** compare runs with and without the script.
- **Network tab:** filter by `ingest` to see each send and its size; filter by `browser.js` for the script.
- **Bundle analyzers** (`webpack-bundle-analyzer`, `@next/bundle-analyzer`, `rollup-plugin-visualizer`): check that only `@supapulse/core` and `@supapulse/react` show up.
