Vue (Vue 3, Vite)
For a Vue 3 app scaffolded with Vite, the recommended install is a static <script> in index.html. Vite treats index.html as the entry point at the project root, so the tag loads once on first paint, before the app mounts. No plugin, no composable, no extra dependency.
If you cannot edit index.html (micro-frontends, library-rendered shells), use useHead() from @unhead/vue to declare the tag from inside a component.
For server-rendered Vue apps, see Nuxt.
Install
Approach 1: index.html (recommended)
Open index.html in the project root and add the script inside <head>:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My Vue App</title>
<script src="https://statable.com/js/YOUR_SITE_ID/s.js" defer></script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Replace YOUR_SITE_ID with the value from Site settings → Tracking Code in your dashboard. To inject it from an env var at build time, use %VITE_STATABLE_ID% in the template and set VITE_STATABLE_ID in .env.
s.js is the single tracker bundle (~2.5 KB gzip) — pageviews, engagement time, scroll depth, outbound clicks, file downloads, and the window.statable API. See Install the tracking script for the full reference.
Approach 2: useHead() from @unhead/vue
Install the package:
Register the head client in main.ts:
import { createApp } from 'vue'
import { createHead } from '@unhead/vue/client'
import App from './App.vue'
const app = createApp(App)
app.use(createHead())
app.mount('#app')
Declare the script in App.vue:
<script setup lang="ts">
import { useHead } from '@unhead/vue'
useHead({
script: [
{
src: `https://statable.com/js/${import.meta.env.VITE_STATABLE_ID}/s.js`,
defer: true,
},
],
})
</script>
<template>
<RouterView />
</template>
useHead() is the canonical Vue 3 head manager since @vueuse/head was sunset in favor of Unhead. Place the call once, near the root.
Tracking custom events
Add a TypeScript declaration for the global:
// src/types/statable.d.ts
declare global {
interface Window {
statable: {
t: (event: string, props?: Record<string, unknown>) => void
}
}
}
export {}
Wrap calls in a composable so every component gets the same typed entry point:
// src/composables/useStatable.ts
export function useStatable() {
function track(event: string, props?: Record<string, unknown>) {
if (typeof window === 'undefined') return
window.statable?.t?.(event, props)
}
return { track }
}
Use it from any component:
<script setup lang="ts">
import { useStatable } from '@/composables/useStatable'
const { track } = useStatable()
function submit() {
track('Form Submitted', { form: 'contact' })
}
</script>
<template>
<button @click="submit">Send</button>
</template>
The optional chaining window.statable?.t?.(...) inside the composable silently no-ops when the deferred script hasn't loaded yet, so early calls don't throw.
Tracking page views in SPA
Vue Router's HTML5 mode (createWebHistory, the Vite scaffold default) calls history.pushState on every navigation. Statable hooks into that and counts pageviews automatically. No router subscription needed.
Verify it's working
- Open Statable Realtime, navigate around your Vue app.
- Each route change should produce a fresh pageview.
- See Verify installation for the full checklist.
Common pitfalls
- Vue Router hash mode. Hash routing (
/#/about) does not callhistory.pushState, so Statable cannot auto-detect navigation. Switch tocreateWebHistory(recommended) or callwindow.statable.t('pageview')manually on each route change. - Calling
window.statablebefore the script loads. Thedeferattribute means the script executes after HTML parsing completes. Use the queue stub above so early calls are buffered. - Mounting
useHead()inside a frequently re-rendering component. Place it once at the app root, never in a route-level component, or you'll inject the tag multiple times. - Forgetting to expose the env var to the browser. Vite needs the
VITE_prefix. Without it,import.meta.env.VITE_STATABLE_IDis undefined on the client.
See also: Install the tracking script, Custom events, JavaScript API.
Ready to take control of your web analytics? Try Statable free for 30 days — no credit card required, full feature access, GDPR-compliant by default. Start your free trial or view a live demo.