# Installing the Eurolytics tracking script

Eurolytics is privacy-first, cookie-free web analytics. One small script (~2 KB gzipped), no cookies, no consent banner needed for the tracking itself, GDPR-compliant by design.

This guide is written to be readable by both humans and AI assistants. A machine-readable version is at https://eurolytics.app/docs/install.json.

## Quick start

1. Sign up / log in at https://eurolytics.app and add your site — you get a **site ID** (a UUID).
2. Paste this snippet into the `<head>` of every page, replacing `SITE_ID` with your site ID:

```html
<script defer data-site-id="SITE_ID" src="https://events.eurolytics.app/js/script.js?s=SITE_ID"></script>
```

3. Visit your site in a normal browser tab. Your visit appears on the dashboard within a minute or two.

The exact snippet (with your site ID filled in) is shown in the dashboard under **your site → Settings → Tracking**. It can also be fetched programmatically: `GET /api/v1/sites/{siteId}/snippet` with an API key, or the `get_tracking_snippet` tool on the MCP server at https://eurolytics.app/api/mcp.

Important: use only this one `<head>` snippet. Do not add the script more than once per page.

## Fully automated setup (AI agents / MCP)

An AI coding agent (Claude, ChatGPT, or any MCP-capable client) can add a site and confirm tracking works without a human touching the dashboard.

**Connect to the MCP server** at https://eurolytics.app/api/mcp, one of two ways:

1. **OAuth** (recommended) — add the URL as a connector. The client is redirected to sign in (or sign up) in the browser; no account has to exist beforehand and no key needs to be copied anywhere.
2. **In-chat signup** (for headless environments with no browser) — call the separate, unauthenticated onboarding server at https://eurolytics.app/api/mcp/onboarding: `start_signup { email, name }` emails a 6-digit code, then `complete_signup { email, code }` returns an account API key. Reconnect to https://eurolytics.app/api/mcp with `Authorization: Bearer <key>`.

**Agent workflow** once connected:

1. `create_site { domain }` — registers the site and returns the tracking snippet.
2. Paste the snippet into the site's `<head>` — the agent edits the codebase directly, following the framework-specific instructions below.
3. `verify_installation { site_id }` — fetches the live homepage and checks for events; returns a status (`verified`, `snippet_found_awaiting_traffic`, `wrong_site_id`, `not_installed`, or `site_unreachable`) plus guidance on what to do next.

## What is tracked automatically

- **Pageviews**, including single-page-app navigations (`pushState`/`replaceState`/`popstate` and back/forward-cache restores) — no extra SPA setup needed.
- **Engagement**: visibility-aware time on page and maximum scroll depth.
- **Outbound link clicks** and **file downloads** (both can be turned off).
- **404 pages** (heuristic, based on document title/body classes).
- UTM parameters (`utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`).

Bot and automated traffic is classified at ingestion and excluded from your stats.

## Framework-specific installation

### Plain HTML

Paste the snippet inside the <head> tag of every page (or of your shared template).

```html
<head>
  ...
  <script defer data-site-id="SITE_ID" src="https://events.eurolytics.app/js/script.js?s=SITE_ID"></script>
</head>
```

### Next.js (App Router)

Add the script to the root layout (app/layout.tsx) using next/script so it loads once for the whole app. Client-side navigations are tracked automatically.

```tsx
import Script from 'next/script';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <Script
          defer
          data-site-id="SITE_ID"
          src="https://events.eurolytics.app/js/script.js?s=SITE_ID"
          strategy="afterInteractive"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}
```

### Next.js (Pages Router)

Add the script in pages/_app.tsx (or pages/_document.tsx) using next/script.

```tsx
import Script from 'next/script';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Script
        defer
        data-site-id="SITE_ID"
        src="https://events.eurolytics.app/js/script.js?s=SITE_ID"
        strategy="afterInteractive"
      />
      <Component {...pageProps} />
    </>
  );
}
```

### Nuxt 3

Register the script globally in nuxt.config.ts under app.head.script.

```ts
export default defineNuxtConfig({
  app: {
    head: {
      script: [
        {
          src: 'https://events.eurolytics.app/js/script.js?s=SITE_ID',
          defer: true,
          'data-site-id': 'SITE_ID',
        },
      ],
    },
  },
});
```

### SvelteKit

Paste the snippet into src/app.html, just before %sveltekit.head%.

```html
<head>
  ...
  <script defer data-site-id="SITE_ID" src="https://events.eurolytics.app/js/script.js?s=SITE_ID"></script>
  %sveltekit.head%
</head>
```

### Astro

Add the snippet to the <head> of your shared layout component. Use is:inline so Astro does not bundle or transform it.

```astro
<head>
  ...
  <script is:inline defer data-site-id="SITE_ID" src="https://events.eurolytics.app/js/script.js?s=SITE_ID"></script>
</head>
```

### React / Vue / other Vite SPA

Paste the snippet into the <head> of index.html at the project root. SPA route changes are tracked automatically.

```html
<head>
  ...
  <script defer data-site-id="SITE_ID" src="https://events.eurolytics.app/js/script.js?s=SITE_ID"></script>
</head>
```

### WordPress

Use a header-scripts plugin (e.g. WPCode: Code Snippets → Header & Footer → Header) and paste the snippet, or add it before </head> in your child theme's header.php.

```html
<script defer data-site-id="SITE_ID" src="https://events.eurolytics.app/js/script.js?s=SITE_ID"></script>
```

### Shopify

In the admin go to Online Store → Themes → Edit code, open layout/theme.liquid, and paste the snippet just before </head>.

```html
<script defer data-site-id="SITE_ID" src="https://events.eurolytics.app/js/script.js?s=SITE_ID"></script>
```

### Webflow

Go to Site settings → Custom code → Head code, paste the snippet, save, and publish the site. Requires a plan that allows custom code.

```html
<script defer data-site-id="SITE_ID" src="https://events.eurolytics.app/js/script.js?s=SITE_ID"></script>
```

### Google Tag Manager

Create a Custom HTML tag with the snippet and fire it on the All Pages trigger. A direct <head> install is preferred (fewer blockers, earlier load), but GTM works.

```html
<script defer data-site-id="SITE_ID" src="https://events.eurolytics.app/js/script.js?s=SITE_ID"></script>
```

## Configuration options

Two ways to configure, in order of precedence:

1. **Dashboard toggles** (recommended): your site → Settings → Tracking. Because the snippet loads the script with `?s=SITE_ID`, the dashboard settings are injected at load time — changes apply without touching your site's code, and they override any data-attributes.
2. **Data-attributes** on the script tag, e.g. `<script defer data-site-id="SITE_ID" data-form-submissions="true" src="...">`.

| Attribute | Default | Description |
|-----------|---------|-------------|
| `data-outbound-links` | `true` | Track clicks on links that leave your domain. |
| `data-file-downloads` | `true` | Track clicks on links to common file types (pdf, zip, mp4, …) as "File Download" events. |
| `data-form-submissions` | `false` | Track valid form submissions as "Form: Submission" events. |
| `data-hash` | `false` | Count URL hash changes (#/route) as pageviews, for hash-based SPA routers. |

## Custom events

Call the global `track` function anywhere after the script has loaded:

```js
window.eurolytics.track('Signup', { plan: 'pro' });
// Non-interactive events don't affect bounce rate:
window.eurolytics.track('Banner Seen', {}, { interactive: false });
```

Prop values are converted to strings; nested objects are dropped. Keep the total payload under 4 KB.

Or tag elements with CSS classes — clicks (and form submissions) on tagged elements send a custom event, no JavaScript needed:

```html
<button class="eurolytics-event-name=Signup eurolytics-event-prop-plan=pro">Sign up</button>
```

Use `+` for spaces in values (`eurolytics-event-name=Start+Trial`). `--` also works as the separator instead of `=`. Add `eurolytics-event-interactive=false` to exclude the event from bounce calculations.

## Custom tracking domain (optional)

Serve the script and events from your own subdomain to improve accuracy:

1. Create a CNAME record: `stats.yourdomain.com → events.eurolytics.app`.
2. In the dashboard: your site → Settings → Tracking → custom tracking domain, then verify.
3. The dashboard snippet switches to your domain automatically; update your installed snippet to match.

## Content-Security-Policy

If your site sets a CSP, allow the tracking domain:

```
script-src https://events.eurolytics.app;
connect-src https://events.eurolytics.app;
```

(Use your custom tracking domain instead, if configured.)

## Verifying the install

1. Open your site in a browser and navigate a few pages.
2. In the browser dev tools Network tab you should see the script load from `/js/script.js` and `POST` requests to `/api/event` returning a 2xx status.
3. The visit appears on your Eurolytics dashboard within a minute or two.

## Troubleshooting

- **No data at all**: check the site ID in the snippet matches the dashboard exactly (it must be the full UUID). View the page source to confirm the snippet is present in the rendered HTML.
- **Script doesn't load**: an ad blocker or browser shield may block it in your own browser — test in a private window with blockers off, or set up a custom tracking domain. Check the browser console for CSP errors and extend your policy as above.
- **`POST /api/event` fails**: usually a CSP `connect-src` restriction — allow the tracking domain.
- **Your own visits from automated browsers / CI don't show up**: traffic from headless browsers and hosting-provider IPs is classified as non-human and excluded from stats. This is by design.
- **SPA pageviews missing**: navigations via `history.pushState` are tracked automatically; hash-based routing (`#/page`) needs the hash option enabled (dashboard toggle or `data-hash="true"`).
- **Duplicate pageviews**: make sure the snippet appears only once per page and isn't installed both directly and via a tag manager.

## Privacy notes

- No cookies, no localStorage, no fingerprinting. Session state lives in `sessionStorage` only (30-minute timeout).
- IP addresses are used for coarse geolocation, then immediately discarded — never stored.
- Raw user-agent strings and full referrer URLs are never stored (referrers are reduced to their domain).
- Visitor identifiers are HMAC-hashed from truncated data and rotate daily.

## Programmatic access

- REST API: `https://eurolytics.app/api/v1` (Bearer `el_...` keys, created in the dashboard).
- MCP server for AI agents: `https://eurolytics.app/api/mcp` (tools for listing sites, fetching snippets, and reading stats).
