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'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.tsxis a small storefront instrumented withanalytik/react:useTrackPageviews,TrackClick,useAnalytikand checkout metadata fromattribution(). Read it next to the React guide and the payments guide.DashboardExample.tsxmounts<Dashboard api siteId />against the seeded demo site. Read it next to Embedding the dashboard.