takt

Svelte

Svelte has a dedicated wrapper: @vskstudio/takt-svelte (component, action, web component), built on the @vskstudio/takt-core core.

The core is a peer dependency: install both packages. Since 0.8.0, the wrapper requires @vskstudio/takt-core >=0.10.0.

pnpm add @vskstudio/takt-svelte @vskstudio/takt-core
# or: npm install @vskstudio/takt-svelte @vskstudio/takt-core
# or: yarn add @vskstudio/takt-svelte @vskstudio/takt-core
# or: bun add @vskstudio/takt-svelte @vskstudio/takt-core

The package exposes three integration styles:

  • @vskstudio/takt-svelte: the <Takt> component + the useTakt() hook, and the optOut() / optIn() / isOptedOut() consent functions re-exported from the core.
  • @vskstudio/takt-svelte/actions: the use:taktEvent action + init/track/pageview/optOut/optIn/isOptedOut functions re-exported from the core.
  • @vskstudio/takt-svelte/element: the <takt-analytics> web component for plain HTML.
Only <Takt> and <takt-analytics> are SSR-safe: they initialize on mount, in the browser. The init() from the /actions subpath touches history as soon as it is called — running it in a SvelteKit component's <script> executes it during server rendering and throws ReferenceError: history is not defined. Call it from onMount.

This page is the option reference. For the end-to-end integration in a SvelteKit app and its main trap — useTakt() called in a component’s <script>, hence before <Takt> mounts — see the guide cookieless analytics in a SvelteKit app.

<Takt> component + useTakt()

Place <Takt> once (typically in +layout.svelte): it initializes the instance on mount, emits the initial pageview and wires up SPA navigation. The component renders no markup — it is a wiring point, not a container, so it has no children. useTakt() retrieves the instance from anywhere in the app (a module store acts as the relay), not only below <Takt>.

<!-- +layout.svelte -->
<script>
  import { Takt } from '@vskstudio/takt-svelte'
</script>

<Takt domain="example.com" outbound files />
<slot />

Call useTakt() inside the handler, not in the <script>: the <script> runs at component init, hence before <Takt>’s onMount. An instance captured there would be the permanent no-op, and every event would go nowhere.

<script>
  import { useTakt } from '@vskstudio/takt-svelte'
</script>

<button onclick={() => useTakt().track('Signup', { props: { plan: 'pro' } })}>
  Sign up
</button>

<button
  onclick={() =>
    useTakt().track('Purchase', {
      props: { plan: 'pro' },
      revenue: { amount: '29', currency: 'EUR' }
    })}
>
  Buy
</button>

<Takt> component props:

PropTypeDefaultRole
domainstringlocation.hostnameSite identifier
endpointstringhttps://taktlytics.com/api/eventIngestion URL. Pass /api/event for a same-origin first-party proxy.
scriptOriginstring—First-party origin to derive the endpoint from (the origin followed by /api/event). endpoint wins over it.
outboundbooleanfalseTracks outbound links
filesboolean \| string[]falseTracks downloads (optional extension list)
spabooleantrueTracks client navigation (auto pageviews)
track404booleanfalseReports a 404 event on error pages ([data-takt-404] / <meta name="takt:404"> marker, or a 404 HTTP status).
taggedbooleanfalseAuto-tracks [data-takt-event] elements
respectDntbooleantrueRespects Do Not Track
excludeLocalhostbooleantrueIgnores localhost / private IPs
enabledbooleantrueMaster switch — false disables all tracking
sampleRatenumber1Fraction of sessions to sample (0–1)
trackQuerybooleanfalsePreserves the query string in page URLs
queryParamsstring[]—Params preserved when trackQuery is false (allowlist)
excludestring[]—Path prefixes never tracked. Segment-bounded: /app matches /app and /app/… but not /application.
scrubUrl(url: string) => string—Transforms each URL before it is sent (page, referrer, url of outbound links and downloads)
debugbooleanfalseLogs each payload to the console before sending
redactRoutesstring[]noneSensitive route patterns sent instead of the real path, e.g. ['/verify/[token]'] (see Route redaction)
routeTemplatesbooleanfalseSends every page as its route template; needs routeTemplate
routeTemplate() => string \| null \| undefinednoneReturns the current route template, e.g. () => page.route.id in SvelteKit
The component forwards all of these options to the core: the advanced settings (enabled, sampleRate, trackQuery, queryParams, exclude, scrubUrl) are available as props, without going through the /actions subpath, and so is debug since 0.7.0.

useTakt() does not throw: called before <Takt> mounts or during SSR, it returns a no-op instance and warns once in the console (useTakt() called before <Takt /> mounted). That message is the symptom of the trap above. Consent calls are the exception: they really act, see Consent.

Route redaction

The query string is stripped by default, but the path is sent as is: /verify/abc123 leaks the token. redactRoutes lists the sensitive routes, and a matching path is sent as its pattern. Every other path keeps its real value, so per-page stats stay intact.

<Takt domain="example.com" redactRoutes={['/verify/[token]', '/reset/[code]', '/invoices/[id].pdf']} />

Patterns accept SvelteKit syntax ([param], [[optional]], [...rest], (group)) as well as :param, :param?, * and **. The rule covers the page URL, same-site referrers, file download destinations and 404 paths.

For a fully private app, routeTemplates sends every page as its route template: /blog/hello becomes /blog/[slug]. The package does not depend on SvelteKit, so you pass the resolver yourself. In SvelteKit, read it from page.route.id:

<!-- +layout.svelte -->
<script>
  import { page } from '$app/state'
  import { Takt } from '@vskstudio/takt-svelte'
</script>

<Takt domain="example.com" routeTemplates routeTemplate={() => page.route.id} />

Route groups such as (marketing) are removed by the core: /(marketing)/blog/[slug] is sent as /blog/[slug]. When the resolver returns null (no matched route), redactRoutes still applies and the real path is sent otherwise. On a public site this mode merges every article into one row, so prefer redactRoutes. init() from the /actions subpath takes the same options. Details in Privacy.

optOut, optIn and isOptedOut are exported from the package root (and from ./actions). They need no instance: a consent banner can record the visitor’s choice before <Takt> mounts, and the instance created later honours it. On the no-op instance, useTakt().optOut(), useTakt().optIn() and useTakt().isOptedOut() delegate to these functions too.

<script>
  import { optOut, optIn, isOptedOut } from '@vskstudio/takt-svelte'

  let optedOut = $state(isOptedOut())

  function toggle(event) {
    if (event.currentTarget.checked) optIn()
    else optOut()
    optedOut = isOptedOut()
  }
</script>

<label>
  <input type="checkbox" checked={!optedOut} onchange={toggle} />
  Audience measurement
</label>

isOptedOut() reads localStorage: on a server-rendered component, call it in the browser (onMount or an event handler).

use:taktEvent action

Without a component, initialize yourself via init(), then attach taktEvent to a clickable element. init() must be called from onMount: at the <script> top level it would run during server rendering and throw ReferenceError: history is not defined.

<script>
  import { onMount } from 'svelte'
  import { init, taktEvent } from '@vskstudio/takt-svelte/actions'

  onMount(() => init({ domain: 'example.com', sampleRate: 0.5, trackQuery: false }))
</script>

<button use:taktEvent={{ name: 'Signup', props: { plan: 'pro' } }}>
  Sign up
</button>

<button use:taktEvent={{ name: 'Purchase', revenue: { amount: '29', currency: 'EUR' } }}>
  Buy
</button>

The action is reactive: if the parameters change, later clicks use the new values. The listener is removed when the element is destroyed.

<takt-analytics> web component

For plain HTML or a non-Svelte framework, import the (auto-registered) element:

<script type="module">
  import '@vskstudio/takt-svelte/element'
</script>

<takt-analytics domain="example.com" outbound files></takt-analytics>

Attributes mirror the props:

  • Flags (presence = on, value ignored): outbound, files, track-404, tagged.
  • On by default, set ="false" to turn them off: spa, respect-dnt, exclude-localhost. The historical single-word spellings respectdnt and excludelocalhost are still accepted.
  • Values: domain, endpoint, script-origin, enabled, sample-rate, track-query, debug (only read when the attribute is present), plus query-params, exclude and redact-routes which take a comma-separated list (redact-routes="/verify/[token],/reset/:code").

routeTemplates and routeTemplate have no attribute: the element has no router. files takes no extension list here, and scrubUrl has no equivalent: those are functions and arrays the Svelte component receives as props, out of reach of an HTML attribute.