Nuxt
Nuxt renders pages with Nitro on the server and hydrates on the client. Statable is a client-only script. Load it through Nuxt's head configuration so the tag ends up in the SSR HTML and the script runs once on hydration. Nuxt's router fires history.pushState on every navigation, which Statable picks up automatically.
This guide covers Nuxt 3 and Nuxt 4 (stable since July 2025). For Nuxt 2, use the legacy head option in nuxt.config.js with the same script attributes.
Install
Approach 1: nuxt.config.ts
The simplest option. Register the script globally in app.head.script:
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
script: [
{
src: `https://statable.com/js/${process.env.NUXT_PUBLIC_STATABLE_ID}/s.js`,
defer: true,
},
],
},
},
runtimeConfig: {
public: {
statableId: process.env.NUXT_PUBLIC_STATABLE_ID,
},
},
})
Set the env var in .env:
Nuxt renders the tag inline in <head> during SSR, so the snippet ships with the first byte. defer keeps it from blocking parse.
Approach 2: useHead in app.vue
If you prefer to keep tracking config close to your app entry, use the auto-imported useHead composable:
<!-- app.vue -->
<script setup lang="ts">
const config = useRuntimeConfig()
useHead({
script: [
{
src: `https://statable.com/js/${config.public.statableId}/s.js`,
defer: true,
},
],
})
</script>
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
useHead is reactive and SSR-aware. The tag renders on the server and Unhead reconciles it on the client without re-injecting.
Approach 3: Nuxt Scripts (useScript)
Nuxt Scripts (v1 stable) is the modern way to load third-party tags in Nuxt. It defers execution to onNuxtReady by default, so the script never blocks hydration and never appears in the SSR response. Good when Core Web Vitals matter more than capturing the very first paint.
Install the module:
Then call useScript in app.vue or any layout:
<!-- app.vue -->
<script setup lang="ts">
const config = useRuntimeConfig()
useScript({
src: `https://statable.com/js/${config.public.statableId}/s.js`,
defer: true,
}, {
trigger: 'onNuxtReady',
})
</script>
Trade-off: with onNuxtReady the script loads after Nuxt finishes hydrating, so the very first pageview fires a fraction of a second later than with Approach 1 or 2. For most sites that's invisible. If you want the script live on the SSR response, stay with Approach 1.
Tracking custom events
Wrap window.statable in a typed composable so every component gets autocomplete and SSR-safe calls:
// composables/useStatable.ts
export function useStatable() {
function track(event: string, props?: Record<string, unknown>) {
if (import.meta.server) return
window.statable?.t?.(event, props)
}
return { track }
}
declare global {
interface Window {
statable: {
t: (event: string, props?: Record<string, unknown>) => void
}
}
}
Use it in any component:
<script setup lang="ts">
const { track } = useStatable()
function onSubmit() {
track('Newsletter Signup', { source: 'footer' })
}
</script>
Tracking page views in SPA
<NuxtLink> and programmatic navigateTo both call history.pushState. Statable listens for that and counts the pageview. No router hook required.
Excluding internal traffic
Statable respects a per-browser opt-out flag in localStorage. To exclude your own dev visits, run this once in DevTools on the live site:
Remove the flag with localStorage.removeItem('analytics_ignore'). Full procedure in Verify installation.
Verify it's working
- Open Statable Realtime in your dashboard.
- Navigate between pages on your Nuxt site. Each view should appear within seconds.
- See Verify installation for the full checklist.
Common pitfalls
- Calling
window.statablefromsetup()without an SSR guard. The first render runs on the server, wherewindowis undefined. Wrap calls inimport.meta.clientor use the composable above. - Hardcoding the Site ID. Always read it from
runtimeConfig.public. Then you can swap IDs between staging and production without rebuilding. - Loading the script in
bodyinstead ofhead. Statable needs to register early to capture engagement and scroll. Useapp.head.script,useHead, oruseScript. - Mixing Approach 1 and Approach 2. Both render the same tag. Pick one. Two of them ship the script twice.
ssr: false(SPA mode). No SSR HTML is sent, so all three approaches load the script on the client only. Behavior is the same, the first pageview just waits for the JS bundle.
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.