@specscreen/analytics
v0.1.3
Published
Framework-agnostic analytics SDK with event tracking, auto-capture, and session/error capture. Works with React, Vue, Svelte, Next.js, Nuxt, SvelteKit, or plain JS.
Maintainers
Readme
@specscreen/analytics
Framework-agnostic analytics SDK for the browser. Event tracking, auto-capture, and session/error capture in one small, tree-shakeable package — with first-class adapters for React, Vue, and Svelte, and zero-dependency use in Next.js, Nuxt, SvelteKit, or plain JavaScript.
- 🌳 ESM + CJS, fully typed,
sideEffects: false - 🧩 Works anywhere; framework adapters are optional subpath imports
- 🖥️ SSR-safe — every API is a no-op during server rendering
- 📦 Batched transport with
sendBeaconon unload (no lost events) - 🔌 Auto-capture clicks / page views / form submits, plus uncaught errors
- 🔒 Privacy-aware:
respectDoNotTrack,beforeSendredaction,data-ss-no-capture
Install
npm install @specscreen/analyticsreact, vue, and svelte are optional peer dependencies — install only the one
your app uses (or none, for the vanilla core).
Quick start (vanilla — any framework)
import { createAnalytics } from "@specscreen/analytics";
export const analytics = createAnalytics({
apiKey: "pk_live_xxx",
autoCapture: true, // clicks, page views, form submits
captureErrors: true, // uncaught errors + promise rejections
});
analytics.identify("user_123", { plan: "pro" });
analytics.track("Signup Completed", { plan: "pro" });React / Next.js
// app/providers.tsx
"use client";
import { AnalyticsProvider } from "@specscreen/analytics/react";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<AnalyticsProvider config={{ apiKey: "pk_live_xxx", autoCapture: true }}>
{children}
</AnalyticsProvider>
);
}// any client component
"use client";
import { useAnalytics, usePageView } from "@specscreen/analytics/react";
import { usePathname } from "next/navigation";
export function CheckoutButton() {
const analytics = useAnalytics();
usePageView(usePathname()); // track SPA navigations
return <button onClick={() => analytics.track("Checkout Started")}>Buy</button>;
}Vue / Nuxt
import { createApp } from "vue";
import { analyticsPlugin } from "@specscreen/analytics/vue";
const app = createApp(App);
app.use(analyticsPlugin, { apiKey: "pk_live_xxx", autoCapture: true });<script setup lang="ts">
import { useAnalytics } from "@specscreen/analytics/vue";
const analytics = useAnalytics();
</script>
<template>
<button @click="analytics.track('Checkout Started')">Buy</button>
</template>For Nuxt, register the plugin in a client plugin file (plugins/analytics.client.ts).
Svelte / SvelteKit
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { setAnalytics } from "@specscreen/analytics/svelte";
setAnalytics({ apiKey: "pk_live_xxx", autoCapture: true });
</script>
<slot /><!-- any child component -->
<script lang="ts">
import { getAnalytics } from "@specscreen/analytics/svelte";
const analytics = getAnalytics();
</script>
<button on:click={() => analytics.track("Checkout Started")}>Buy</button>API
createAnalytics(config) / new Analytics(config)
| Method | Description |
| --- | --- |
| track(event, properties?) | Track a named event. |
| page(name?, properties?) | Record a page view (automatic when autoCapture is on). |
| identify(userId, traits?) | Associate the device with a known user. |
| register(properties) | Attach properties to every subsequent event. |
| reset() | Clear identity, rotate the anonymous id (call on logout). |
| flush() | Force-send buffered events. |
| enable() / disable() | Toggle event sending at runtime. |
| shutdown() | Flush and detach all listeners. |
| getAnonymousId() / getUserId() | Read current identity. |
Config
| Option | Default | Description |
| --- | --- | --- |
| apiKey | — | Required project write key. |
| endpoint | SpecScreen ingest | Custom ingest URL. |
| autoCapture | false | true, or { click, pageview, submit, maskSelector }. |
| captureErrors | false | Capture uncaught errors + rejections. |
| sessions | true | Attach a session id; emit $session_start. |
| sessionTimeout | 1800000 | Inactivity (ms) before a new session. |
| flushAt / flushInterval | 20 / 5000 | Batch size / max buffer time (ms). |
| storage | "localStorage" | "localStorage" | "cookie" | "memory". |
| respectDoNotTrack | false | Disable when the browser DNT signal is set. |
| disabled | false | Start disabled (call enable() after consent). |
| filter | — | Drop events by name or path: { events, paths }. |
| beforeSend | — | Mutate or drop (return null) each event. |
| redact | — | Strip PII keys before send. string[] or { keys, mask, emails }. |
| transport | HTTP | Provide a custom { send(events) } transport. |
| debug | false | Verbose console logging. |
Filtering events
Drop events you don't want to count — by event name or by URL path —
without writing beforeSend logic:
createAnalytics({
apiKey: "pk_live_xxx",
autoCapture: true,
filter: {
events: ["$click"], // never send click events
paths: ["/dashboard/**", "/admin/*"], // capture nothing on these paths
},
});* matches within a path segment, ** matches across segments. Filtered events
are dropped before any network or session work, so they're free.
Tip: to stop capturing a whole category at the source (no listeners attached at all), prefer the granular auto-capture switch —
autoCapture: { click: false }— overfilter.events: ["$click"]. Usefilterfor paths and for custom events.
Privacy
- Strip PII in the browser with
redact— denied keys never leave the device:
Applied recursively to properties and context, and runs last (aftercreateAnalytics({ apiKey: "pk_live_xxx", redact: ["email", "password", "ssn"], // shorthand for { keys: [...] } // or: redact: { keys: ["email"], mask: "***", emails: true } });beforeSend) so denied keys can't escape. Use an opaqueuserId, not an email —redactcan't scrub the identifier itself. - Add
data-ss-no-captureto any element/subtree to exclude it from auto-capture. - Form field values are never captured — only ids/names/actions.
- Use
beforeSendfor custom logic, ordisabled: true+enable()to gate on consent.
Development
npm install
npm run build # tsup → dist/ (ESM + CJS + .d.ts)
npm run typecheck # tsc --noEmitLicense
MIT © SpecScreen
