From 554cb99f2c978fbe0ae89dd2a93f4a9641eae9a0 Mon Sep 17 00:00:00 2001 From: Ronni Baslund Date: Sat, 6 Jun 2026 21:14:42 +0200 Subject: [PATCH] feat(website): Umami analytics (conversion + CTA events) Add the Umami tracker (cookieless, no consent banner) in the document head, limited to the production hostnames via data-domains so dev traffic doesn't pollute the stats. Pageviews are auto-tracked per page and locale. Custom events on the key funnel: - demo-request (demo form submit, with teamSize) - partner-application (partner form submit, with type) - book-demo (every "Book a demo" CTA click) via data-umami-event - login (clicks through to the app) Also fix the mobile nav menu links, which weren't localized (would drop Danish visitors back to English). --- apps/website/components/landing/ComingSoon.vue | 2 +- apps/website/components/landing/FinalCta.vue | 2 +- apps/website/components/landing/Hero.vue | 2 +- apps/website/components/landing/Nav.vue | 9 +++++---- apps/website/components/landing/Pricing.vue | 2 +- apps/website/composables/useLanding.ts | 8 ++++++++ apps/website/nuxt.config.ts | 11 +++++++++++ apps/website/pages/contact.vue | 2 +- apps/website/pages/demo.vue | 3 ++- apps/website/pages/migration.vue | 2 +- apps/website/pages/partners.vue | 5 +++-- 11 files changed, 35 insertions(+), 13 deletions(-) diff --git a/apps/website/components/landing/ComingSoon.vue b/apps/website/components/landing/ComingSoon.vue index f161052..a6b8c3a 100644 --- a/apps/website/components/landing/ComingSoon.vue +++ b/apps/website/components/landing/ComingSoon.vue @@ -13,6 +13,6 @@ const localePath = useLocalePath() diff --git a/apps/website/components/landing/FinalCta.vue b/apps/website/components/landing/FinalCta.vue index 3244836..42332a6 100644 --- a/apps/website/components/landing/FinalCta.vue +++ b/apps/website/components/landing/FinalCta.vue @@ -21,7 +21,7 @@ const localePath = useLocalePath()
{{ copy.finalCta.sub }}
-
diff --git a/apps/website/components/landing/Nav.vue b/apps/website/components/landing/Nav.vue index 6439eb2..2580fbe 100644 --- a/apps/website/components/landing/Nav.vue +++ b/apps/website/components/landing/Nav.vue @@ -97,8 +97,8 @@ function segStyle(code: 'en' | 'da') { - {{ copy.nav.login }} - {{ copy.nav.cta }} → + {{ copy.nav.login }} + {{ copy.nav.cta }} → @@ -147,7 +147,7 @@ function segStyle(code: 'en' | 'da') { }" > {{ copy.nav.login }} - {{ copy.nav.cta }} → + {{ copy.nav.cta }} → diff --git a/apps/website/components/landing/Pricing.vue b/apps/website/components/landing/Pricing.vue index 3476057..75dbc62 100644 --- a/apps/website/components/landing/Pricing.vue +++ b/apps/website/components/landing/Pricing.vue @@ -18,7 +18,7 @@ const localePath = useLocalePath()

{{ copy.pricing.lede }}

- {{ copy.pricing.cta }} → + {{ copy.pricing.cta }} →
diff --git a/apps/website/composables/useLanding.ts b/apps/website/composables/useLanding.ts index 160cf72..ab5ee06 100644 --- a/apps/website/composables/useLanding.ts +++ b/apps/website/composables/useLanding.ts @@ -45,6 +45,14 @@ export function useLocalizeHref() { } } +// Fire a custom Umami event. No-op on the server and when the tracker isn't +// active (e.g. localhost dev, where data-domains limits it to dezky.eu). +export function track(event: string, data?: Record) { + if (!import.meta.client) return + const u = (window as unknown as { umami?: { track: (e: string, d?: unknown) => void } }).umami + u?.track(event, data) +} + // Smooth-scroll to an in-page anchor, accounting for the sticky 72px nav. // Non-anchor / placeholder links (#) are ignored. export function scrollToAnchor(hash: string) { diff --git a/apps/website/nuxt.config.ts b/apps/website/nuxt.config.ts index 94ac96a..06ce2b4 100644 --- a/apps/website/nuxt.config.ts +++ b/apps/website/nuxt.config.ts @@ -49,6 +49,17 @@ export default defineNuxtConfig({ meta: [ { name: 'theme-color', content: '#FAFAF7' }, ], + // Umami — privacy-friendly, cookieless analytics (matches the cookie + // policy; no consent banner needed). data-domains limits tracking to the + // production hostnames so localhost/dev traffic doesn't pollute stats. + script: [ + { + src: 'https://umami.coolify.lastcloud.io/script.js', + defer: true, + 'data-website-id': '639dcd4c-1cb3-4edb-a2a2-6e9cf69348d4', + 'data-domains': 'dezky.eu,www.dezky.eu', + }, + ], }, }, diff --git a/apps/website/pages/contact.vue b/apps/website/pages/contact.vue index 2d93667..715fc52 100644 --- a/apps/website/pages/contact.vue +++ b/apps/website/pages/contact.vue @@ -57,7 +57,7 @@ useHead({ title: () => `${copy.value.pages.contact.label} · dezky` })

{{ c.demoHeading }}

{{ c.demoBody }}

- {{ copy.pages.ctaDemo }} → + {{ copy.pages.ctaDemo }} →
diff --git a/apps/website/pages/demo.vue b/apps/website/pages/demo.vue index d973f3a..2be84d8 100644 --- a/apps/website/pages/demo.vue +++ b/apps/website/pages/demo.vue @@ -3,7 +3,7 @@ // info@dezky.eu (zero backend, no US tools). Swap this for the self-built // embedded scheduler later — the copy + layout can stay. import { ref, computed } from 'vue' -import { useTheme, useCopy } from '~/composables/useLanding' +import { useTheme, useCopy, track } from '~/composables/useLanding' definePageMeta({ layout: 'page' }) @@ -28,6 +28,7 @@ function submit() { '', message.value, ].join('\n') + track('demo-request', { teamSize: team.value }) window.location.href = `mailto:info@dezky.eu?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}` } diff --git a/apps/website/pages/migration.vue b/apps/website/pages/migration.vue index dac9673..9a0fc9f 100644 --- a/apps/website/pages/migration.vue +++ b/apps/website/pages/migration.vue @@ -27,7 +27,7 @@ useHead({ title: () => `${copy.value.pages.migration.label} · dezky` })

{{ c.note }}

- {{ copy.pages.ctaDemo }} → + {{ copy.pages.ctaDemo }} →
diff --git a/apps/website/pages/partners.vue b/apps/website/pages/partners.vue index c8465a6..ebfc721 100644 --- a/apps/website/pages/partners.vue +++ b/apps/website/pages/partners.vue @@ -1,6 +1,6 @@