Skip to content
Analytik
Esc
↑↓navigate↵open⌘Jpreview
On this page

Examples

Live previews of the install snippet generator, the privacy modes, plus pointers to the shop and dashboard demos.

The previews on this page run the demo app’s example files, in apps/example/src/examples, so the docs and the demo cannot drift apart. Switch to the Code tab on a preview to read the file. If a file is missing in your checkout, the preview shows “No example found” and the rest of the page still builds.

Install

Pick a platform and a privacy mode, type your host, and get the script tag, the React provider or the server entry to paste.

import { useId, useState } from 'react'
import type { PrivacyMode } from 'analytik'

import { CodeBlock, Collapsible, CopyButton } from '../components'
import { SERVER_ICONS, SITE_ICONS } from '../lib/icons'
import {
  SERVER_TARGETS,
  SITE_TARGETS,
  serverPrompt,
  sitePrompt,
  type ServerTarget,
  type SiteTarget,
} from '../lib/prompts'
import {
  DEFAULT_OPTIONS,
  hostOf,
  normalizeHost,
  reactSnippet,
  scriptTag,
  serverSnippet,
  type SnippetOptions,
} from '../lib/snippet'
import { PromptPanel } from '../PromptPanel'

type Target = 'script' | 'react' | 'server'

const TARGETS: { id: Target; label: string }[] = [
  { id: 'script', label: 'Script tag' },
  { id: 'react', label: 'React' },
  { id: 'server', label: 'Server' },
]

const MODES: PrivacyMode[] = ['strict', 'enriched', 'persistent']

const dashboardSnippet = (options: SnippetOptions) => `import { Dashboard } from 'analytik/dashboard'
import 'analytik/dashboard/styles.css'

export const Admin = () => <Dashboard api="${hostOf(options)}/api" />`

function Step({
  n,
  title,
  text,
  file,
  code,
  label,
}: {
  n: number
  title: string
  text: string
  file: string
  code: string
  label: string
}) {
  return (
    <li className="step card">
      <div className="step__head">
        <span className="step__number" aria-hidden="true">
          {n}
        </span>
        <h3>{title}</h3>
      </div>
      <p className="muted">{text}</p>
      <div className="step__code">
        <div className="code__bar">
          <span>{file}</span>
          <CopyButton text={code} label={`Copy ${label}`} />
        </div>
        <pre tabIndex={0}>
          <code>{code}</code>
        </pre>
      </div>
    </li>
  )
}

// Install in three steps, a full snippet generator behind "More options", and copy-ready prompts for coding
// assistants. All of them read the same options, so a host typed once shows up everywhere.
export function InstallExample() {
  const [options, setOptions] = useState<SnippetOptions>(DEFAULT_OPTIONS)
  const [target, setTarget] = useState<Target>('script')
  const [server, setServer] = useState<ServerTarget>('cloudflare-d1')
  const [site, setSite] = useState<SiteTarget>('html')
  const ids = { host: useId(), site: useId(), mode: useId() }
  const set = <K extends keyof SnippetOptions>(key: K, value: SnippetOptions[K]) => {
    setOptions((previous) => ({ ...previous, [key]: value }))
  }
  const hostValid = normalizeHost(options.host) !== null

  const code =
    target === 'script' ? scriptTag(options) : target === 'react' ? reactSnippet(options) : serverSnippet(options)

  return (
    <div className="stack stack--loose">
      <ol className="steps-grid">
        <Step
          n={1}
          title="Deploy the server"
          text="Print a starter server for Workers, Vercel, Bun or Node, then deploy it like any small app."
          file="Terminal"
          code="npx analytik init --target worker"
          label="the init command"
        />
        <Step
          n={2}
          title="Add the snippet to your site"
          text="Paste it into the <head> of every page. It sets no cookies."
          file="HTML"
          code={scriptTag(options)}
          label="the script tag"
        />
        <Step
          n={3}
          title="Open the dashboard"
          text="Embed it in any React app and sign in with your admin password."
          file="Admin.tsx"
          code={dashboardSnippet(options)}
          label="the dashboard component"
        />
      </ol>

      <Collapsible title="More options" hint="Your host, site id, privacy mode and the React and server snippets">
        <div className="split">
          <div className="stack">
            <label className="field" htmlFor={ids.host}>
              Your Analytik host
              <input
                id={ids.host}
                className="input"
                value={options.host}
                aria-invalid={!hostValid}
                onChange={(event) => {
                  set('host', event.target.value)
                }}
              />
            </label>
            {!hostValid && <p className="warning">Enter a full URL such as https://analytics.example.com.</p>}
            <label className="field" htmlFor={ids.site}>
              Site id
              <input
                id={ids.site}
                className="input"
                value={options.siteId}
                onChange={(event) => {
                  set('siteId', event.target.value)
                }}
              />
            </label>
            <label className="field" htmlFor={ids.mode}>
              Privacy mode
              <select
                id={ids.mode}
                className="input"
                value={options.mode}
                onChange={(event) => {
                  set('mode', event.target.value as PrivacyMode)
                }}
              >
                {MODES.map((mode) => (
                  <option key={mode} value={mode}>
                    {mode}
                  </option>
                ))}
              </select>
            </label>
            <label className="row">
              <input
                type="checkbox"
                checked={options.requireInteraction}
                onChange={(event) => {
                  set('requireInteraction', event.target.checked)
                }}
              />
              Wait for interaction or 3 s before the first pageview
            </label>
            <label className="row">
              <input
                type="checkbox"
                checked={options.allowLocalhost}
                onChange={(event) => {
                  set('allowLocalhost', event.target.checked)
                }}
              />
              Track localhost (for testing)
            </label>
            <label className="row">
              <input
                type="checkbox"
                checked={options.autoOutbound}
                onChange={(event) => {
                  set('autoOutbound', event.target.checked)
                }}
              />
              Track outbound link clicks
            </label>
            <label className="row">
              <input
                type="checkbox"
                checked={options.vitals}
                onChange={(event) => {
                  set('vitals', event.target.checked)
                }}
              />
              Measure Core Web Vitals
            </label>
          </div>

          <div className="stack">
            <div className="tabs" role="tablist" aria-label="Snippet target">
              {TARGETS.map(({ id, label }) => (
                <button
                  key={id}
                  type="button"
                  role="tab"
                  aria-selected={target === id}
                  onClick={() => {
                    setTarget(id)
                  }}
                >
                  {label}
                </button>
              ))}
            </div>
            <CodeBlock label={TARGETS.find((entry) => entry.id === target)?.label ?? ''} code={code} />
          </div>
        </div>
      </Collapsible>

      <PromptPanel
        title="Server setup prompt"
        description="Let your coding assistant deploy Analytik. Pick where it runs; the prompt uses the host and mode from More options."
        options={SERVER_TARGETS}
        icons={SERVER_ICONS}
        value={server}
        onChange={setServer}
        prompt={serverPrompt(server, options)}
      />
      <PromptPanel
        title="Site integration prompt"
        description="Let your coding assistant add the tracker to your site, in the right file for your framework."
        options={SITE_TARGETS}
        icons={SITE_ICONS}
        value={site}
        onChange={setSite}
        prompt={sitePrompt(site, options)}
      />
    </div>
  )
}

export default InstallExample

The file is InstallExample.tsx.

Privacy modes

Toggle strict, enriched and persistent and see what each one writes to the device, and how the visitor id is derived from a sample IP address, user agent and salt. This is the same derivation described in Privacy modes, run in the browser for illustration.

import type { PrivacyMode } from 'analytik'
import { useCallback, useEffect, useId, useState } from 'react'

import { SITE_ID } from '../config'
import { MODES, deriveVisitorId, randomSalt, readDeviceStorage, type DeviceStorage } from '../lib/privacy'

const SAMPLE = { ip: '203.0.113.7', userAgent: 'Mozilla/5.0 (Macintosh) Firefox/130.0' }

const list = (names: string[]) => (names.length > 0 ? names.join(', ') : 'none')

export function PrivacyExample() {
  const [mode, setMode] = useState<PrivacyMode>('strict')
  const [ip, setIp] = useState(SAMPLE.ip)
  const [salt, setSalt] = useState(randomSalt)
  const [visitorId, setVisitorId] = useState('')
  const [device, setDevice] = useState<DeviceStorage>(readDeviceStorage)
  const ipId = useId()
  const facts = MODES.find((entry) => entry.mode === mode) ?? MODES[0]

  useEffect(() => {
    let current = true
    void deriveVisitorId(salt, SITE_ID, ip, SAMPLE.userAgent).then((id) => {
      if (current) setVisitorId(id)
    })
    return () => {
      current = false
    }
  }, [salt, ip])

  const refresh = useCallback(() => {
    setDevice(readDeviceStorage())
  }, [])

  if (!facts) return null

  return (
    <div className="stack">
      <div>
        <div className="segmented" role="group" aria-label="Privacy mode">
          {MODES.map((entry) => (
            <button
              key={entry.mode}
              type="button"
              aria-pressed={mode === entry.mode}
              onClick={() => {
                setMode(entry.mode)
              }}
            >
              {entry.title}
            </button>
          ))}
        </div>
      </div>

      <table className="table">
        <tbody>
          <tr>
            <th scope="row">Stored on the device</th>
            <td data-testid="fact-storage">{facts.deviceStorage}</td>
          </tr>
          <tr>
            <th scope="row">Visitor id</th>
            <td>{facts.visitorId}</td>
          </tr>
          <tr>
            <th scope="row">Consent banner</th>
            <td>{facts.banner}</td>
          </tr>
          <tr>
            <th scope="row">How long it links visits</th>
            <td>{facts.survives}</td>
          </tr>
        </tbody>
      </table>

      <div className="split">
        <div className="card stack">
          <h3>How a strict visitor id is made</h3>
          <p className="muted">
            The server hashes the site, IP and user agent with this month&apos;s random salt. The IP is then forgotten.
            When the month ends the salt is deleted, and nobody, including you, can link the ids back.
          </p>
          <label className="field" htmlFor={ipId}>
            IP address (documentation range, not yours)
            <input
              id={ipId}
              className="input mono"
              value={ip}
              onChange={(event) => {
                setIp(event.target.value)
              }}
            />
          </label>
          <p>
            Visitor id <code data-testid="visitor-id">{visitorId}</code>
          </p>
          <div className="row">
            <button
              type="button"
              className="btn"
              onClick={() => {
                setSalt(randomSalt())
              }}
            >
              End the month (delete the salt)
            </button>
            <span className="faint">The same visitor now gets a different id.</span>
          </div>
        </div>

        <div className="card stack">
          <h3>On this device right now</h3>
          <table className="table">
            <tbody>
              <tr>
                <th scope="row">Cookies</th>
                <td data-testid="device-cookies">{list(device.cookies)}</td>
              </tr>
              <tr>
                <th scope="row">localStorage</th>
                <td data-testid="device-local">{list(device.localStorage)}</td>
              </tr>
              <tr>
                <th scope="row">sessionStorage</th>
                <td data-testid="device-session">{list(device.sessionStorage)}</td>
              </tr>
            </tbody>
          </table>
          <p className="muted">
            Switch consent on in the Instrumented site tab, come back and refresh: the tab id appears. Withdraw consent
            and it is removed.
          </p>
          <div>
            <button type="button" className="btn" onClick={refresh}>
              Refresh
            </button>
          </div>
        </div>
      </div>
    </div>
  )
}

export default PrivacyExample

The file is PrivacyExample.tsx.

Shop and dashboard

Two more examples need the demo’s server, which serves seeded data on /api, so they run in the demo app and not in these previews:

  • ShopExample.tsx is a small storefront instrumented with analytik/react: useTrackPageviews, TrackClick, useAnalytik and checkout metadata from attribution(). Read it next to the React guide and the payments guide.
  • DashboardExample.tsx mounts <Dashboard api siteId /> against the seeded demo site. Read it next to Embedding the dashboard.

Was this page helpful?