takt

Vue

Vue has a dedicated wrapper: @vskstudio/takt-vue (component, composable, directive, plugin, web component), built on the @vskstudio/takt-core core.

The core is a peer dependency: install both packages.

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

vue (^3.3.0) and @vskstudio/takt-core (>=0.10.0 since 0.8.0) are peer dependencies. The package exposes three subpaths:

  • @vskstudio/takt-vue: the <Takt> component, the useTakt() composable, the v-takt-event directive, the TaktPlugin, the <TaktBadge> / <TaktEmbed> widget components, the createStats() stats client and the optOut() / optIn() / isOptedOut() consent functions re-exported from the core.
  • @vskstudio/takt-vue/directives: the v-takt-event directive + init/track/pageview/optOut/optIn/isOptedOut functions re-exported from the core.
  • @vskstudio/takt-vue/element: the <takt-analytics> web component for plain HTML.
Only <Takt>, TaktPlugin and <takt-analytics> are SSR-safe: they initialize on mount, in the browser. The init() from the /directives subpath touches location and history as soon as it is called. Running it at the module level of a universal Nuxt plugin or in a <script setup> executes it during server rendering and throws ReferenceError: history is not defined. Call it from onMounted, or from a .client.ts Nuxt plugin.

<Takt> component + useTakt()

Place <Takt> once (typically in App.vue): it initializes the instance on mount, emits the initial pageview, wires up SPA navigation and publishes the instance application-wide. useTakt() retrieves it from any component (a module store acts as the relay), not only below <Takt>.

<!-- App.vue -->
<script setup>
import { Takt } from '@vskstudio/takt-vue'
</script>

<template>
  <Takt domain="example.com" :outbound="true" :files="['pdf', 'zip']">
    <RouterView />
  </Takt>
</template>

Call useTakt() inside the handler, not at the <script setup> level: setup runs before <Takt>’s onMounted, and in Vue a parent’s mounted fires after its children’s. So even a child onMounted is too early. An instance captured there would be the permanent no-op, and every event would go nowhere. With TaktPlugin given options, the instance exists as soon as the plugin is installed: capturing it at setup level is valid there.

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

function onSignup() {
  useTakt().track('Signup', {
    props: { plan: 'pro' },
    revenue: { amount: '29.00', currency: 'EUR' }
  })
}
</script>

<template>
  <button @click="onSignup">Sign up</button>
</template>

<Takt> component props:

PropTypeDefaultRole
domainstringlocation.hostnameSite identifier
endpointstringhttps://taktlytics.com/api/eventIngestion URL. Pass /api/event for a same-origin first-party proxy.
scriptOriginstringnoneFirst-party origin to derive the endpoint from (the origin followed by /api/event). endpoint wins over it.
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).
outboundbooleanfalseTracks outbound links
filesboolean \| string[]falseTracks downloads (optional extension list)
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 to 1)
trackQuerybooleanfalsePreserves the query string in page URLs
queryParamsstring[]noneParams preserved when trackQuery is false (allowlist)
excludestring[]nonePath prefixes never tracked. Segment-bounded: /app matches /app and /app/… but not /application.
scrubUrl(url: string) => stringnoneTransforms each URL before it is sent (page, referrer, url of outbound links and downloads, path of 404s)
debugbooleanfalseLogs each payload to the console before sending
redactRoutesstring[]noneSensitive route patterns: a matching path is sent as the pattern, e.g. ['/verify/:token'] (see Route redaction)
redactUndecodablePathsbooleantrueNot exposed by this wrapper: set it on the core SDK (init() / createTakt()), see Unreadable paths
routeTemplatesbooleanfalseSends every page as its route template instead of its real path
routeTemplate() => string \| null \| undefinednoneResolver of the current route template, read only when routeTemplates is on
routerRouterLikenoneA Vue Router instance: with routeTemplates and no routeTemplate, vueRouterTemplate(router) is used. Not forwarded to the core
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 /directives 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.

optOut, optIn and isOptedOut are exported from the package root (and from ./directives). They need no instance: a consent banner can call them anywhere, before or after <Takt> mounts, and the instance created later honours the choice. On the no-op instance, useTakt().optOut(), useTakt().optIn() and useTakt().isOptedOut() delegate to these functions too.

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { optOut, optIn, isOptedOut } from '@vskstudio/takt-vue'

const optedOut = ref(false)
onMounted(() => {
  optedOut.value = isOptedOut()
})

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

<template>
  <button @click="toggle">{{ optedOut ? 'Accept analytics' : 'Refuse analytics' }}</button>
</template>

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

v-takt-event directive

For simple click tracking, bind the directive instead of a handler. It is reactive (changing the bound value updates what gets tracked) and the listener is cleaned up on unmount. At click time it tracks through the active instance (the one provided by <Takt> or TaktPlugin), falling back to the core default instance if you drive init() yourself:

<script setup>
import { vTaktEvent } from '@vskstudio/takt-vue'
</script>

<template>
  <button v-takt-event="{ name: 'Signup', props: { plan: 'pro' } }">
    Sign up
  </button>

  <button v-takt-event="{ name: 'Purchase', revenue: { amount: '29.00', currency: 'EUR' } }">
    Buy
  </button>
</template>

The directive and the core functions are also available from the ./directives subpath if you prefer a functional import:

import { vTaktEvent, track, pageview, optOut, optIn, isOptedOut } from '@vskstudio/takt-vue/directives'

Plugin

app.use(TaktPlugin) registers the v-takt-event directive and the global <TaktBadge> / <TaktEmbed> components (no per-component import needed). Passing options also bootstraps a single instance (pageview + autocapture) without a <Takt> component:

import { createApp } from 'vue'
import { TaktPlugin } from '@vskstudio/takt-vue'
import App from './App.vue'

createApp(App)
  .use(TaktPlugin, { domain: 'example.com', outbound: true })
  .mount('#app')

Installing without options performs only those registrations. Use <Takt> for instance lifecycle in that case. Bootstrapping is skipped on the server.

Route redaction

The query string is stripped by default, but the path is sent as is: /verify/abc123 leaks the token. List the sensitive routes in redactRoutes: a matching path is sent as its pattern, while every other page keeps its real path.

createApp(App)
  .use(TaktPlugin, { domain: 'example.com', redactRoutes: ['/verify/:token', '/reset/:code'] })
  .mount('#app')

Patterns accept the Vue Router syntax (:param, :param?, *, **) as well as the bracket syntax ([param], [[optional]], [...rest]). The rule also covers same-site referrers, outbound and download links, and 404 paths.

For a fully private app, routeTemplates: true sends every page as its route template (/users/42 becomes /users/:id). Pass your router and the plugin reads the matched route for you:

import { createApp } from 'vue'
import { TaktPlugin } from '@vskstudio/takt-vue'
import { router } from './router'
import App from './App.vue'

createApp(App)
  .use(router)
  .use(TaktPlugin, { domain: 'example.com', routeTemplates: true, router })
  .mount('#app')

The component works the same way: <Takt domain="example.com" route-templates :router="router">. Under the hood it uses vueRouterTemplate(router), exported from the package root, which returns the path of the deepest matched record (Vue Router already stores it as the full normalized path, e.g. /users/:id). Pass your own routeTemplate to override it. The helper is typed structurally, so the package does not depend on vue-router. On a public site this mode merges every article into one row, so prefer redactRoutes. Details in Privacy.

<takt-analytics> web component

For plain HTML or a non-Vue framework, import the ./element subpath (auto-registered, a native HTMLElement, with no Vue runtime bundled; only the core is, so no build step or import map is needed):

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

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

Attributes mirror the props:

  • Flags (presence = on, value ignored): outbound, files, track404.
  • On by default, set ="false" (or ="0") to turn them off: spa, respect-dnt, exclude-localhost.
  • 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.

tagged turns on when the attribute is present, but honours ="false" and ="0" to stay off. The element has no router, so no routeTemplates mode. files takes no extension list here, and scrubUrl has no equivalent: those are functions and arrays the <Takt> component receives as props, out of reach of an HTML attribute.

<takt-analytics domain="example.com" spa="false" respect-dnt="false"></takt-analytics>

defineTaktElement() is also exported for explicit, idempotent registration. The bundle is SSR-safe: importing it on the server is a no-op until customElements exists.