From d5696a9807ffce0be45b64953665fca63a98529c Mon Sep 17 00:00:00 2001 From: Valentin Chmara Date: Thu, 11 Sep 2025 18:01:37 +0200 Subject: [PATCH] feat: add databuddy analytics --- .../scripts/analytics/databuddy-analytics.md | 169 ++++++++++++++++++ src/registry.ts | 9 + src/runtime/registry/databuddy-analytics.ts | 160 +++++++++++++++++ 3 files changed, 338 insertions(+) create mode 100644 docs/content/scripts/analytics/databuddy-analytics.md create mode 100644 src/runtime/registry/databuddy-analytics.ts diff --git a/docs/content/scripts/analytics/databuddy-analytics.md b/docs/content/scripts/analytics/databuddy-analytics.md new file mode 100644 index 00000000..f8662781 --- /dev/null +++ b/docs/content/scripts/analytics/databuddy-analytics.md @@ -0,0 +1,169 @@ +--- +title: Databuddy Analytics +description: Use Databuddy Analytics in your Nuxt app. +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/databuddy-analytics.ts + size: xs +--- + +[Databuddy](https://www.databuddy.cc/) is a privacy-first analytics platform focused on performance and minimal data collection. + +Use the registry to easily inject the Databuddy CDN script with sensible defaults, or call the composable for fine-grain control. + +## Loading Globally + +The simplest way to enable Databuddy globally is via `nuxt.config` (or module config). You can use environment overrides to only enable in production. + +::code-group + +```ts [Always enabled] +export default defineNuxtConfig({ + scripts: { + registry: { + databuddyAnalytics: { + clientId: 'YOUR_CLIENT_ID' + } + } + } +}) +``` + +```ts [Production only] +export default defineNuxtConfig({ + $production: { + scripts: { + registry: { + databuddyAnalytics: { + clientId: 'YOUR_CLIENT_ID' + } + } + } + } +}) +``` + +```ts [Environment Variables] +export default defineNuxtConfig({ + scripts: { + registry: { + databuddyAnalytics: true, + } + }, + runtimeConfig: { + public: { + scripts: { + databuddyAnalytics: { + // .env + // NUXT_PUBLIC_SCRIPTS_DATABUDDY_ANALYTICS_CLIENT_ID= + clientId: '' + }, + }, + }, + }, +}) +``` + +:: + +## useScriptDatabuddyAnalytics + +The `useScriptDatabuddyAnalytics` composable gives you control over when and how Databuddy is loaded. + +```ts +const db = useScriptDatabuddyAnalytics({ + clientId: 'YOUR_CLIENT_ID', + trackWebVitals: true, + trackErrors: true, + enableBatching: true, +}) +``` + +The composable returns the script proxy (when available). You can interact with the global API via `db` or `window.db` / `window.databuddy`. + +### CDN / Self-hosted + +By default the registry injects `https://cdn.databuddy.cc/databuddy.js`. If you host the script yourself, pass `scriptUrl` in options to override the `src`. + +```ts +useScriptDatabuddyAnalytics({ + scriptInput: { src: 'https://my-host/databuddy.js' }, + clientId: 'YOUR_CLIENT_ID' +}) +``` + +### DatabuddyAnalyticsApi + +```ts +export interface DatabuddyAnalyticsApi { + track: (eventName: string, properties?: Record) => Promise | any | void + screenView: (path?: string, properties?: Record) => void + setGlobalProperties: (properties: Record) => void + trackCustomEvent: (eventName: string, properties?: Record) => void + clear: () => void + flush: () => void +} +``` + +### Config Schema + +You must provide a `clientId` when configuring the registry for the first time. The registry supports a large set of Databuddy options which are passed to the script via `data-` attributes. + +```ts +export const DatabuddyAnalyticsOptions = object({ + clientId: string(), + scriptUrl: optional(string()), + apiUrl: optional(string()), + disabled: optional(boolean()), + trackScreenViews: optional(boolean()), + trackPerformance: optional(boolean()), + trackSessions: optional(boolean()), + trackWebVitals: optional(boolean()), + trackErrors: optional(boolean()), + trackOutgoingLinks: optional(boolean()), + trackScrollDepth: optional(boolean()), + trackEngagement: optional(boolean()), + trackInteractions: optional(boolean()), + trackAttributes: optional(boolean()), + trackHashChanges: optional(boolean()), + trackExitIntent: optional(boolean()), + trackBounceRate: optional(boolean()), + enableBatching: optional(boolean()), + batchSize: optional(number()), + batchTimeout: optional(number()), + enableRetries: optional(boolean()), + maxRetries: optional(number()), + initialRetryDelay: optional(number()), + samplingRate: optional(number()), + sdk: optional(string()), + sdkVersion: optional(string()), + enableObservability: optional(boolean()), + observabilityService: optional(string()), + observabilityEnvironment: optional(string()), + observabilityVersion: optional(string()), + enableLogging: optional(boolean()), + enableTracing: optional(boolean()), + enableErrorTracking: optional(boolean()), +}) +``` + +## Example + +Track a custom event using the composable proxy (noop in SSR/development): + +::code-group + +```vue [EventButton.vue] + + + +``` diff --git a/src/registry.ts b/src/registry.ts index c4615b2f..e9ca6678 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -67,6 +67,15 @@ export async function registry(resolve?: (path: string, opts?: ResolvePathOption from: await resolve('./runtime/registry/rybbit-analytics'), }, }, + { + label: 'Databuddy Analytics', + category: 'analytics', + logo: ``, + import: { + name: 'useScriptDatabuddyAnalytics', + from: await resolve('./runtime/registry/databuddy-analytics'), + }, + }, { label: 'Segment', scriptBundling: (options?: SegmentInput) => { diff --git a/src/runtime/registry/databuddy-analytics.ts b/src/runtime/registry/databuddy-analytics.ts new file mode 100644 index 00000000..2fa50212 --- /dev/null +++ b/src/runtime/registry/databuddy-analytics.ts @@ -0,0 +1,160 @@ +import { useRegistryScript } from '../utils' +import { object, optional, string, boolean, number } from '#nuxt-scripts-validator' +import type { RegistryScriptInput } from '#nuxt-scripts/types' + +// Options schema based on https://www.databuddy.cc/docs/sdk +export const DatabuddyAnalyticsOptions = object({ + // Required + clientId: string(), + + // Advanced + scriptUrl: optional(string()), // defaults to https://cdn.databuddy.cc/databuddy.js + apiUrl: optional(string()), // defaults to https://basket.databuddy.cc + disabled: optional(boolean()), + + // Core tracking (enabled by default by SDK) + trackScreenViews: optional(boolean()), + trackPerformance: optional(boolean()), + trackSessions: optional(boolean()), + + // Optional tracking + trackWebVitals: optional(boolean()), + trackErrors: optional(boolean()), + trackOutgoingLinks: optional(boolean()), + trackScrollDepth: optional(boolean()), + trackEngagement: optional(boolean()), + trackInteractions: optional(boolean()), + trackAttributes: optional(boolean()), + trackHashChanges: optional(boolean()), + trackExitIntent: optional(boolean()), + trackBounceRate: optional(boolean()), + + // Performance options + enableBatching: optional(boolean()), + batchSize: optional(number()), + batchTimeout: optional(number()), + enableRetries: optional(boolean()), + maxRetries: optional(number()), + initialRetryDelay: optional(number()), + samplingRate: optional(number()), + + // SDK metadata + sdk: optional(string()), + sdkVersion: optional(string()), + + // Observability & logging (accepted by SDK config) + enableObservability: optional(boolean()), + observabilityService: optional(string()), + observabilityEnvironment: optional(string()), + observabilityVersion: optional(string()), + enableLogging: optional(boolean()), + enableTracing: optional(boolean()), + enableErrorTracking: optional(boolean()), +}) + +export type DatabuddyAnalyticsInput = RegistryScriptInput + +export interface DatabuddyAnalyticsApi { + /** + * Track a custom event. + * @param eventName Name of the event (use snake_case) + * @param properties Optional event properties + */ + track: (eventName: string, properties?: Record) => Promise | any | void + + /** + * Manually record a page / screen view. Useful for SPA route changes. + * @param path Optional path to record (defaults to current location) + * @param properties Optional additional properties for the screen view + */ + screenView: (path?: string, properties?: Record) => void + + /** + * Set properties that will be attached to all future events (e.g. user_id). + * @param properties Key/value map of properties to attach globally + */ + setGlobalProperties: (properties: Record) => void + + /** + * Track a custom event alias (compatibility helper present on the global) + * @param eventName Name of the event + * @param properties Optional event properties + */ + trackCustomEvent: (eventName: string, properties?: Record) => void + + /** + * Clears session and anonymous identifiers (useful on logout). + */ + clear: () => void + + /** + * Force immediate sending of any queued/batched events. + */ + flush: () => void +} + +declare global { + interface Window { + databuddy?: DatabuddyAnalyticsApi + db?: DatabuddyAnalyticsApi + } +} + +export function useScriptDatabuddyAnalytics(_options?: DatabuddyAnalyticsInput) { + return useRegistryScript('databuddyAnalytics', (options) => { + return { + scriptInput: { + // Default CDN script, can be overridden via scriptUrl + 'src': options?.scriptUrl || 'https://cdn.databuddy.cc/databuddy.js', + 'data-client-id': options.clientId, + // Advanced + 'data-api-url': options?.apiUrl, + 'data-disabled': options?.disabled, + // Core + 'data-track-screen-views': options?.trackScreenViews, + 'data-track-performance': options?.trackPerformance, + 'data-track-sessions': options?.trackSessions, + // Optional + 'data-track-web-vitals': options?.trackWebVitals, + 'data-track-errors': options?.trackErrors, + 'data-track-outgoing-links': options?.trackOutgoingLinks, + 'data-track-scroll-depth': options?.trackScrollDepth, + 'data-track-engagement': options?.trackEngagement, + 'data-track-interactions': options?.trackInteractions, + 'data-track-attributes': options?.trackAttributes, + 'data-track-hash-changes': options?.trackHashChanges, + 'data-track-exit-intent': options?.trackExitIntent, + 'data-track-bounce-rate': options?.trackBounceRate, + // Performance tuning + 'data-enable-batching': options?.enableBatching, + 'data-batch-size': options?.batchSize, + 'data-batch-timeout': options?.batchTimeout, + 'data-enable-retries': options?.enableRetries, + 'data-max-retries': options?.maxRetries, + 'data-initial-retry-delay': options?.initialRetryDelay, + 'data-sampling-rate': options?.samplingRate, + // SDK meta + 'data-sdk': options?.sdk, + 'data-sdk-version': options?.sdkVersion, + // Observability & logging + 'data-enable-observability': options?.enableObservability, + 'data-observability-service': options?.observabilityService, + 'data-observability-environment': options?.observabilityEnvironment, + 'data-observability-version': options?.observabilityVersion, + 'data-enable-logging': options?.enableLogging, + 'data-enable-tracing': options?.enableTracing, + 'data-enable-error-tracking': options?.enableErrorTracking, + }, + schema: import.meta.dev ? DatabuddyAnalyticsOptions : undefined, + scriptOptions: { + use() { + if (typeof window === 'undefined') { + return null as unknown as T + } + // Prefer the lightweight proxy (db) if available, else raw tracker instance + return (window.db || window.databuddy || null) as unknown as T + }, + }, + } + }, _options) +}