> ## Documentation Index
> Fetch the complete documentation index at: https://www.usenotra.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Count visitors with the notra.js script

> Add one script tag to any website to count page views, visitors, sources, AI referrals, engagement, outbound links and Core Web Vitals in Notra, without cookies.

`notra.js` counts the people who read your site: page views, visitors, sessions, where they came from (including ChatGPT, Perplexity, Claude and other AI answers), how long they stayed and how far they scrolled. It also records clicks on links to other sites and Core Web Vitals. It works on any site, including static sites and single-page apps, and is about 1.2 KB compressed.

The script cannot see AI crawlers, because crawlers do not run JavaScript. To log crawlers as well, install the [server tracker](https://www.usenotra.com/docs/analytics/install/nextjs.md) or a [log drain](https://www.usenotra.com/docs/analytics/install/vercel-log-drain.md) alongside it. Notra does not double count, because page views come from the script and AI crawler events come from the server tracker.

**1. Copy the script tag**

Open **Settings → Tracking** and copy the **Script tag** from the install panel. Until the first page view arrives, the **Web** tab under **Analytics** shows the same panel. The tag contains your project's public key and loads the current build of the script, checked against its hash. You never look up or set the hash yourself, because the install panel fills in the current build and its `integrity`. With your key in place of `nwk.…`, today's tag is:

```html theme={"system"}
<script defer src="https://static.usenotra.com/notra.ce3c872ef9c9.js" data-key="nwk.…" data-api="https://ingest.usenotra.com/api/collect" integrity="sha384-LuUp5Hj+rSQ4/mZp1sWBOrJDHIHKGqflwF1GYIreqmWdCaI1otOQealWJGRj6Toj" crossorigin="anonymous"></script>
```

The key is not a secret. It can only record page views on the domains your project tracks, so it is safe in page HTML and keeps working when you rotate your server token. See [Pinned builds](#pinned-builds-and-subresource-integrity) for what `integrity` does and how to update.

`POST /v1/geo/ingest/token` returns the key as `webKey` and the full pinned tag as `webSnippet`.

**2. Add it to every page**

Paste the tag into the `<head>` of your layout or site template. In Webflow, Framer, WordPress or Shopify, use the custom code setting for the site header.

**3. Check your domains**

Notra only accepts page views from the domains in **Tracked domains** in GEO settings, so add every hostname that serves the script, such as `www.example.com` and `docs.example.com`.

## Install with the SDK

In a React, Next.js or Astro app, let [`@usenotra/sdk`](https://www.npmjs.com/package/@usenotra/sdk) write the tag. It loads the build pinned in your SDK version with its `integrity`, so you only pass your public key, and updating the package (for example through Dependabot or Renovate) moves you to the newest build.

```bash theme={"system"}
npm install @usenotra/sdk
```

```tsx app/layout.tsx theme={"system"}
import { NotraAnalytics } from "@usenotra/sdk/react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <NotraAnalytics webKey="nwk.…" disabled={process.env.NODE_ENV !== "production"} />
      </body>
    </html>
  );
}
```

```astro src/layouts/Base.astro theme={"system"}
---
import { notraScriptTag } from "@usenotra/sdk/script";

const notra = notraScriptTag({ webKey: "nwk.…" });
---
<html>
  <head>
    <Fragment set:html={notra} />
  </head>
  <body><slot /></body>
</html>
```

`NotraAnalytics` needs no `"use client"` and works in Server Components. Both take the same options as the tag: `errors` loads `notra.plus.js`, `hash` sets `data-hash` and `api` changes where page views go. The same package also holds the [server tracker](https://www.usenotra.com/docs/analytics/install/nextjs.md) for AI crawlers, so one install covers both.

## Options

| Attribute | Default | Description |
| - | - | - |
| `data-key` | required | Your project's public key. |
| `data-api` | `/api/collect` on the script's origin | Where page views are sent. The snippet from Notra sets it to `https://ingest.usenotra.com/api/collect`. Keep it when the script loads from `static.usenotra.com`, which only serves the script. Change it when you proxy page views through your own domain. |
| `data-hash` | off | Count `#hash` changes as page views, for apps that route with the hash. Routes such as `#/pricing` or `#!/pricing` are reported as pages (`/#/pricing`), without their query, and any other fragment, such as `#access_token=…`, is never stored. |

Single-page apps are tracked automatically: every `history.pushState` navigation, every `history.replaceState` that changes the path (such as a redirect from `/` to `/en`) and every back/forward step counts as a page view. A `replaceState` that only changes the query string, such as a filter or sort order, stays the same page view. Pages restored from the back/forward cache and pages that were prerendered count when the visitor actually sees them.

## Outbound links

The script records left and middle clicks on links to another host on its own, as an `Outbound` event. Notra keeps only the destination's host and path, never its query string, fragment or link text, and shows the clicks in the **Outbound links** table.

## Core Web Vitals

`notra.js` measures Largest Contentful Paint (LCP), First Contentful Paint (FCP), Time to First Byte (TTFB), Cumulative Layout Shift (CLS) and Interaction to Next Paint (INP) with the browser's own performance APIs, without the `web-vitals` library. The values travel on the beacon the script already sends when the page is hidden, so measuring them adds no requests. LCP, FCP and TTFB describe the page load and are reported once per loaded page, while CLS and INP are measured again for every single-page-app view. The **Web vitals** table shows the 75th percentile of each, rated against Google's thresholds. Browsers without these APIs, such as older Safari versions, report only what they support.

## JavaScript errors (opt-in)

To also record uncaught JavaScript errors and unhandled promise rejections, turn on **Report JavaScript errors** in the install panel. The tag then loads `notra.plus.js` instead of `notra.js`. It is the same script with error reporting added, about 1.3 KB compressed, takes the same attributes and is pinned the same way:

```html theme={"system"}
<script defer src="https://static.usenotra.com/notra.plus.d703b9cc5508.js" data-key="nwk.…" data-api="https://ingest.usenotra.com/api/collect" integrity="sha384-MgLdHKufD0WdkO4pxtkT/bbNCKxl6PxOyUJe+M/qwFGfjOdZ5rEnljxTlOVQIzF+" crossorigin="anonymous"></script>
```

Only errors from scripts served over `http` or `https` are reported, which leaves out browser extensions and the opaque `Script error.` of other origins, and at most five per page view. The script never sends a stack trace. Before storing a message, Notra removes query strings from URLs in it, replaces email addresses with `[email]` and replaces runs of four or more digits and long hexadecimal tokens with `#`. Errors appear in the **JavaScript errors** table on the **Web** tab once there are any.

## Pinned builds and Subresource Integrity

The tag from the install panel loads one exact build, such as `notra.ce3c872ef9c9.js`, and carries its hash in `integrity`. The browser hashes the file and refuses to run it if a single byte differs, so nobody, Notra included, can change the script on your pages without you replacing the tag. Every pinned build stays available unchanged, so the tag keeps working after new releases.

> [!WARNING]
>
> **Keep `integrity` on the tag.** Removing it only turns off the hash check, because the tag still loads the same pinned build, so you lose the protection and get no updates. For automatic updates, turn off **Pin this version** in the install panel and copy the new tag. That tag loads whatever build of `/notra.js` (or `/notra.plus.js`) the script host serves at that moment, without a hash check. If the script host or Notra's release process were ever compromised, the changed script would run on your pages right away. We don't recommend it, so use it only if you accept that risk in exchange for getting new builds without changing the tag.

* **Updating.** A pinned script never updates on its own. To move to a newer build, copy the tag from the install panel again and replace both `src` and `integrity`. The tags on this page always show the current build.
* **Keep `crossorigin="anonymous"`.** Browsers only check the integrity of scripts loaded with CORS. Without the attribute the script is blocked.
* **Proxies.** If you serve the script through your own domain, pass the file through unchanged. Minifying or rewriting it breaks the hash, but compression is fine.
* **Cloudflare Rocket Loader.** Rocket Loader rewrites script tags so it can load them itself, which can drop `integrity` and `crossorigin`. Add `data-cfasync="false"` to the tag so Rocket Loader leaves it alone.
* **Content-Security-Policy.** Allow the script host in `script-src` and the ingest in `connect-src` for its beacons: `script-src https://static.usenotra.com; connect-src https://ingest.usenotra.com`. Browsers that implement CSP Level 3 also accept the pinned script's hash in `script-src`, because the tag carries the same `integrity` value: `'sha384-LuUp5Hj+rSQ4/mZp1sWBOrJDHIHKGqflwF1GYIreqmWdCaI1otOQealWJGRj6Toj'` for today's `notra.js`, or the value from your tag. Keep the origin listed for browsers that don't.

The token response also returns `webScript` and `webPlusScript`, each with the auto-updating `url`, the `pinnedUrl` of the current build and its `integrity`.

## What is sent

Each page view sends the page URL, the referrer and a random ID for that view. Each time the page is hidden, the script sends the visible time, the scroll depth and the web vitals for that view. A click on a link to another site sends the link's host and path with the view's ID, and `notra.plus.js` sends an error's message, script URL and line number. The script sets no cookies, writes nothing to storage and does not fingerprint the browser.

Notra stores the path, the referrer's host, the `utm_source`, `utm_medium`, `utm_campaign`, `utm_term` and `utm_content` parameters, country, the browser's primary language (such as `de`), device, browser and operating system. In the path, it replaces any segment that looks like an email address, a UUID, a number of four or more digits or a token with `:id`. Visitors are counted with a hash of the IP address and user agent, salted with a random value that changes every day and is deleted after 48 hours, so nobody, Notra included, can link a visitor to an IP address afterwards or follow them from one day to the next. The IP address itself is never stored.

The country is looked up inside Notra's own servers, so the IP address is not sent to any other service. Country data: [IP Geolocation by DB-IP](https://db-ip.com), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).

The script does not run inside iframes, on `localhost` or in automated browsers, and Notra drops page views, clicks and errors from headless browsers and page-speed tools such as Lighthouse.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.