Skip to main content
Every site comes with built-in analytics, and this page covers how to send visits to your own analytics tool as well, run your own JavaScript and control which origins your pages may load code from.

Built-in analytics

Every site counts its own visitors and their time on page with nothing to install, and you can turn this off under Settings → Danger zone. Open the site’s Analytics page for people and AI agents on the site, or Traffic in GEO for AI crawlers and referrals across all your domains, while previews never count. See AI agents and search.

Analytics integrations

To also send visits to your own analytics tool, add it on the site’s Integrations page or under integrations in blog.json. The Integrations page saves a draft of blog.json that goes live when you publish it from the editor. Notra adds the provider’s script tag to the <head> of every page and allows its hosts in the Content-Security-Policy.
blog.json
You can turn on several providers at once, and Notra checks every ID against the provider’s format. If an ID is wrong or a key is unknown, the build fails with an error that points to the setting. Another tool, such as a chat widget or a tag manager, goes in a custom script.
Previews use the same integrations as your live site, so filter by hostname in your analytics tool to keep previews out of your numbers. Preview hosts look like pr-7--acme.notra.site.

Custom scripts

Every .js file outside the content folders runs on every page, whether it’s script.js, a file in a scripts/ folder or a file like analytics.js at the root. These files work like custom CSS for JavaScript, while a .js file in snippets/, blog/ or changelog/ is a component you import instead.
Notra loads every script on every page with defer from your site’s own address, and the scripts run in the order shown above once the HTML has been parsed.
  • Plain browser JavaScript: these files are not modules, so import and export don’t work in them, and document.createElement("script") loads a library from another host.
  • Syntax check only: the build checks the syntax and never runs your scripts, and a syntax error fails the build and shows the file and line.
  • No imports from MDX: your MDX can’t import these files, so for a component, use a snippet in snippets/ instead.
scripts/external-links.js

Content-Security-Policy

Every page comes with a Content-Security-Policy header that says where scripts may come from and which servers they may connect to, and Notra builds it from your repository.
  • Scripts: they can come from your site, the analytics integrations you turned on and your allowedOrigins. Notra allows inline scripts only if they were on the page at build time and hashes each of them, so a script injected later doesn’t run.
  • Connections: fetch, beacons and WebSockets may connect to your site, your integrations and your allowedOrigins.
  • Images, styles, fonts, videos and embeds: the policy doesn’t limit them, so posts can still show images and videos from anywhere.
If your custom scripts or components load code from another host or call an API, add that origin:
blog.json
Origins must start with https:// or wss:// and have no path, and https://*.example.com covers every subdomain. A wss:// origin only allows connections rather than scripts, and you can list up to 32 origins. To turn the policy off entirely:
blog.json
If a script doesn’t run, open your browser’s developer console, which lists any request the policy blocked with the origin you need to add.
Last modified on October 8, 2026