In the browser

Installation

.mdllms.txt

Put the tracker on your site with one script tag and no build step, or with the typed, SSR-safe @invisitdev/web package.

There are two ways to install invisit, and they run the same tracker built from the same source. Pick the script tag when there is no build step: static HTML, WordPress, Shopify, Framer. It works in bundled apps too. Pick the npm package when you want track(), identify() and consent() as typed imports and the tracker bundled with your app.

Both need your site id, from Settings → Installation in the dashboard. It is public and never changes, so it is safe in client code and in PUBLIC_ or NEXT_PUBLIC_ environment variables.

Script tag

index.html
<script
  async
  src="https://api.invisit.dev/invisit.js"
  data-site="<site id>"
  data-collector="https://api.invisit.dev"
></script>

The tracker loads asynchronously and never blocks the page. The tag always serves the current tracker release, so updates reach your site with no change on your side. Where it goes on WordPress, Shopify and Framer is in the platform guides.

npm package

Terminal
npm install @invisitdev/web

Or with pnpm add, yarn add or bun add. Then call init() once, in the browser, as early as your app allows:

src/main.ts
import { init } from '@invisitdev/web'

init({ site: '<site id>' })

That is the whole setup. init() sends the first page view, follows client-side navigation from then on, and returns the running tracker. The web frameworks show where that line goes in each framework.

The package’s optional parts (web vitals, no-code rules, the feedback widget, the heatmap overlay) are dynamic imports, so your bundler splits them into your own files and the page never requests a script from a third-party host.

Calls made before init() are queued and applied once it starts, so an identify() in your auth code can safely run first.

Options

Each option is an init() field, or a data-* attribute on the script tag. Boolean attributes take true or false.

Option Attribute Default What it does
site data-site required The site id.
baseUrl data-collector https://api.invisit.dev, none for the script The collector, or a proxy path such as /_i.
requireConsent data-require-consent false Sends nothing until consent is granted. See Privacy.
storage data-storage auto none keeps nothing at all on the device.
excludePaths data-exclude-paths none Paths never measured. See Excluding traffic.
outboundLinks data-outbound-links true Records clicks on links to other sites.
fileDownloads data-file-downloads true Records clicks on file downloads.
respectGpc data-respect-gpc false Turned on, stops for browsers that send Global Privacy Control.
respectDnt data-respect-dnt false Turned on, stops for browsers that send Do Not Track.
debug data-debug false Logs delivery problems to the console, and lets a browser driven by automation send events, so you can test your setup.

Everything else (allowed domains, redacted query keys, web vitals, heatmaps, no-code event rules, the feedback widget) is set per site in the dashboard and fetched by the tracker when it starts, so changing it needs no redeploy.

Group dynamic paths

Where ids or names sit in the path, such as /orders/8121 or /acme/settings, every one of them is its own row in the Pages report. pageUrl rewrites each address before it is recorded, so one route is one row:

src/main.ts
init({
  site: '<site id>',
  pageUrl: (href) => href.replace(/\/orders\/\d+/, '/orders/:id'),
})

It gets the full URL and returns a full URL; query parameters and the fragment are redacted afterwards as usual. With the script tag, queue the same function on the invisit stub before the script loads:

index.html
<script>
  window.invisit =
    window.invisit ||
    function () {
      ;(invisit.q = invisit.q || []).push(arguments)
    }
  invisit('pageUrl', (href) => href.replace(/\/orders\/\d+/, '/orders/:id'))
</script>
<script
  async
  src="https://api.invisit.dev/invisit.js"
  data-site="<site id>"
  data-collector="https://api.invisit.dev"
></script>

Two addresses that rewrite to the same one count as the same page, so moving between them records no new page view.

A heatmap is drawn over the page opened at its recorded address, and /orders/:id opens no page. So clicks are not collected where pageUrl changes the path, and those pages have no click heatmap. Rewriting only the query keeps it.

Both on one page

If a page has both the script tag and the package, whichever starts first wins and the other reuses it. A visit is never counted twice.

Single-page apps

Nothing to add. The tracker follows client-side navigation in every router and records a page view for each new address. The fragment is not part of an address, so a router that changes only #/path records one page view, not one per route.

Browser support

Chrome 91, Edge 91, Firefox 90, Safari 15 and later.