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 or a log drain 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 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 for what
integrity. With your key in place of nwk.…, today’s tag is:integrity does and how to update.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 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.
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 for AI crawlers, so one install covers both.
Options
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 anOutbound 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 loadsnotra.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:
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 asnotra.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.
- 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
srcandintegrity. 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
integrityandcrossorigin. Adddata-cfasync="false"to the tag so Rocket Loader leaves it alone. - Content-Security-Policy. Allow the script host in
script-srcand the ingest inconnect-srcfor 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 inscript-src, because the tag carries the sameintegrityvalue:'sha384-LuUp5Hj+rSQ4/mZp1sWBOrJDHIHKGqflwF1GYIreqmWdCaI1otOQealWJGRj6Toj'for today’snotra.js, or the value from your tag. Keep the origin listed for browsers that don’t.
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, andnotra.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, licensed under CC 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.