# @thisbefine/analytics SDK - Complete Reference > Lightweight analytics, error tracking, and logging for indie SaaS. > Package: @thisbefine/analytics (~4KB gzipped) > Entry points: @thisbefine/analytics, @thisbefine/analytics/react, @thisbefine/analytics/next --- ## Installation ```bash npm install @thisbefine/analytics # or pnpm add @thisbefine/analytics # or yarn add @thisbefine/analytics ``` Prerequisites: Node.js 18+, React 18+ (for React/Next.js) --- ## Environment Variables ```bash # Next.js NEXT_PUBLIC_THISBEFINE_API_KEY=tif_your_key NEXT_PUBLIC_TBF_API_KEY=tif_your_key # alternative # Vite VITE_THISBEFINE_API_KEY=tif_your_key # Create React App REACT_APP_THISBEFINE_API_KEY=tif_your_key ``` --- ## Setup ### Next.js (App Router) ```tsx // app/layout.tsx import { Analytics } from "@thisbefine/analytics/next"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### React SPA (Vite, CRA, etc.) ```tsx // main.tsx or App.tsx import { Analytics } from "@thisbefine/analytics/react"; function App() { return ( <> ); } ``` Note: React SPA requires manual page tracking (see Page Tracking section). --- ## Analytics Component Props ```tsx ``` --- ## Configuration Object ```typescript interface AnalyticsConfig { apiKey: string; // REQUIRED host?: string; // Default: "https://thisbefine.com" flushAt?: number; // Default: 20 (events before auto-send) flushInterval?: number; // Default: 10000 (ms between auto-send) sessionTimeout?: number; // Default: 1800000 (30 min inactivity) cookieDomain?: string; // For cross-subdomain tracking debug?: boolean; // Default: false respectDNT?: boolean; // Default: true maxRetries?: number; // Default: 3 errors?: ErrorCaptureConfig; } interface ErrorCaptureConfig { enabled?: boolean; // Default: true captureConsoleErrors?: boolean; // Default: false captureNetworkErrors?: boolean; // Default: false maxBreadcrumbs?: number; // Default: 25 beforeSend?: (payload: ErrorPayload) => ErrorPayload | null; } ``` ### Example Configuration ```tsx { // Filter out specific errors if (payload.message.includes("ResizeObserver")) return null; return payload; }, }, }} /> ``` --- ## Core Methods ### Event Tracking ```typescript import { analytics } from "@thisbefine/analytics"; // Track custom events analytics.track(eventName: string, properties?: Record); // Examples analytics.track("button_clicked", { buttonId: "signup", page: "landing" }); analytics.track("feature_used", { feature: "export", format: "csv" }); analytics.track("purchase_completed", { orderId: "123", amount: 99.99 }); ``` ### Page Tracking ```typescript // Track page views (automatic in Next.js with trackPageviews={true}) analytics.page(name?: string, properties?: Record); // Examples analytics.page(); // Uses current URL analytics.page("Dashboard"); analytics.page("Product Detail", { productId: "abc123" }); ``` ### User Identification ```typescript // Link anonymous user to known identity analytics.identify(userId: string, traits?: UserTraits); // Examples analytics.identify("user_123"); analytics.identify("user_123", { email: "jane@example.com", name: "Jane Doe", avatar: "https://example.com/avatar.jpg", plan: "pro", createdAt: "2024-01-15", }); ``` ### Account/Team Grouping (B2B) ```typescript // Associate user with an account/team/organization analytics.group(accountId: string, traits?: AccountTraits); // Examples analytics.group("account_456"); analytics.group("account_456", { name: "Acme Inc", plan: "enterprise", mrr: 999, industry: "Technology", employeeCount: 50, createdAt: new Date(), }); ``` ### Reset (Logout) ```typescript // CRITICAL: Call on logout to prevent data leakage analytics.reset(); ``` This clears: userId, user traits, accountId, account traits. Generates new anonymousId and sessionId. ### Get User State ```typescript const user = analytics.getUser(); // Returns: { anonymousId, userId?, traits?, accountId?, accountTraits? } ``` --- ## Error Tracking ### Capture Exceptions ```typescript // Catch and report errors with context analytics.captureException(error: Error, context?: Record); // Example try { await processPayment(order); } catch (error) { analytics.captureException(error as Error, { operation: "processPayment", orderId: order.id, amount: order.total, }); throw error; // Re-throw or handle } ``` ### Capture Messages ```typescript // Report errors without Error object analytics.captureMessage( message: string, level?: "warning" | "error" | "fatal", context?: Record ); // Examples analytics.captureMessage("Payment gateway timeout", "error", { gateway: "stripe" }); analytics.captureMessage("High memory usage detected", "warning", { usage: "95%" }); analytics.captureMessage("Database connection lost", "fatal"); ``` ### Breadcrumbs ```typescript // Leave trail of events before crash analytics.addBreadcrumb({ type: "click" | "navigation" | "network" | "console" | "custom", message: string, timestamp?: string, data?: Record, }); // Example analytics.addBreadcrumb({ type: "click", message: "Clicked checkout button", data: { cartItems: 3 }, }); analytics.addBreadcrumb({ type: "network", message: "POST /api/orders", data: { status: 500 }, }); ``` ### Automatic Error Capture The SDK automatically captures: - Unhandled exceptions (throw new Error) - Unhandled promise rejections - window.onerror events Optional (via config): - console.error() calls (captureConsoleErrors: true) - Failed fetch/XHR requests (captureNetworkErrors: true) ### Error Fingerprinting Errors are grouped by fingerprint (message + top stack frame). Similar errors are deduplicated in the dashboard. --- ## Logging ```typescript // Structured logs with levels analytics.log( message: string, level: "debug" | "info" | "warn" | "error" | "fatal", metadata?: Record ); // Examples analytics.log("User signed up", "info", { source: "google_oauth" }); analytics.log("Export completed", "info", { format: "csv", rows: 1000 }); analytics.log("Rate limit approaching", "warn", { current: 950, limit: 1000 }); analytics.log("Database query failed", "error", { query: "SELECT...", duration: 5000 }); analytics.log("Out of memory", "fatal", { heapUsed: "2GB" }); ``` Log levels (severity order): debug < info < warn < error < fatal Logs automatically include user context if identify() was called. --- ## React Hooks Import from `@thisbefine/analytics/next` (Next.js) or `@thisbefine/analytics/react` (React SPA). ### Core Hooks ```typescript import { useAnalytics, useTrack, useIdentify, useGroup, usePage, useReset, useGetUser, useCaptureException, useLog, } from "@thisbefine/analytics/next"; // Get full analytics instance const analytics = useAnalytics(); // Get memoized track function for specific event const trackClick = useTrack("button_clicked"); trackClick({ buttonId: "signup" }); // Get identify function const identify = useIdentify(); identify("user_123", { email: "jane@example.com" }); // Get group function const group = useGroup(); group("account_456", { name: "Acme Inc" }); // Get page tracking function const page = usePage(); page("Dashboard"); // Get reset function const reset = useReset(); reset(); // Call on logout // Get current user state const getUser = useGetUser(); const user = getUser(); // Get error capture function const captureException = useCaptureException(); captureException(error, { context: "checkout" }); // Get log function const log = useLog(); log("Action completed", "info", { action: "export" }); ``` ### Lifecycle Hooks Pre-built hooks for common events: ```typescript import { // User lifecycle useSignup, useLogin, useLogout, useAccountDeleted, // Subscription lifecycle useSubscriptionStarted, useSubscriptionCancelled, useSubscriptionRenewed, usePlanUpgraded, usePlanDowngraded, // Trial lifecycle useTrialStarted, useTrialEnded, // Engagement useInviteSent, useInviteAccepted, useFeatureActivated, } from "@thisbefine/analytics/next"; // Examples const signup = useSignup(); signup({ source: "landing_page", referrer: "google" }); const subscriptionStarted = useSubscriptionStarted(); subscriptionStarted({ plan: "pro", interval: "monthly", amount: 29 }); const featureActivated = useFeatureActivated(); featureActivated({ feature: "team_collaboration" }); ``` ### Hook Rules 1. Call hooks at the top level of your component 2. Don't call hooks conditionally 3. Requires `` component to be mounted 4. All hooks return memoized functions (safe for useEffect dependencies) --- ## Bug Report Widget ```tsx import { BugReportFAB } from "@thisbefine/analytics/next"; ``` The widget automatically captures: - Current URL - Browser info (user agent) - OS and screen size - User ID and anonymousId - Timestamp --- ## Privacy Controls ### Opt Out/In ```typescript // Stop all tracking (persists in localStorage) analytics.optOut(); // Resume tracking analytics.optIn(); // Check opt-out status const isOptedOut = analytics.isOptedOut(); ``` ### Do Not Track By default, the SDK respects the browser's Do Not Track setting (`respectDNT: true`). When DNT is enabled, no events are tracked. To disable DNT respect (not recommended): ```tsx ``` --- ## Data Flushing Events are automatically batched and sent: - When `flushAt` events are queued (default: 20) - Every `flushInterval` milliseconds (default: 10000) - On page unload (beforeunload event) ### Manual Flush ```typescript // Force send all pending events (returns Promise) await analytics.flush(); ``` Use `flush()` when: - Navigating to external URL - Before critical operations - In development for immediate feedback --- ## Types ### UserTraits ```typescript interface UserTraits { email?: string; name?: string; avatar?: string; [key: string]: unknown; // Custom properties allowed } ``` ### AccountTraits ```typescript interface AccountTraits { name?: string; plan?: string; mrr?: number; industry?: string; employeeCount?: number; createdAt?: string | Date; [key: string]: unknown; // Custom properties allowed } ``` ### UserState ```typescript interface UserState { anonymousId: string; // Always present userId?: string; // After identify() traits?: UserTraits; // After identify() with traits accountId?: string; // After group() accountTraits?: AccountTraits; } ``` ### Event Payload ```typescript interface EventPayload { type: "track" | "identify" | "page" | "group"; timestamp: string; // ISO 8601 anonymousId: string; userId?: string; sessionId?: string; accountId?: string; context?: EventContext; } ``` ### Breadcrumb ```typescript interface Breadcrumb { type: "click" | "navigation" | "network" | "console" | "custom"; message: string; timestamp?: string; data?: Record; } ``` ### LogLevel ```typescript type LogLevel = "debug" | "info" | "warn" | "error" | "fatal"; ``` --- ## Event Naming Conventions ### Pattern: `object_action` Use snake_case consistently. ### Examples ```typescript // User actions "button_clicked" "form_submitted" "file_uploaded" "search_performed" // E-commerce "product_viewed" "cart_item_added" "cart_item_removed" "checkout_started" "purchase_completed" // SaaS "feature_used" "limit_reached" "settings_updated" "integration_connected" // User journey "onboarding_step_completed" "onboarding_completed" "activation_milestone" ``` --- ## Common Patterns ### Login Flow ```typescript const login = useLogin(); const identify = useIdentify(); const group = useGroup(); const handleLogin = async (credentials) => { const user = await authenticateUser(credentials); // Track login event login({ method: "email" }); // Identify user identify(user.id, { email: user.email, name: user.name, plan: user.plan, }); // Group into account (if B2B) if (user.organizationId) { group(user.organizationId, { name: user.organizationName, plan: user.organizationPlan, }); } }; ``` ### Logout Flow ```typescript const logout = useLogout(); const reset = useReset(); const handleLogout = async () => { // Track logout event logout(); // CRITICAL: Reset identity before navigation reset(); // Navigate to login page router.push("/login"); }; ``` ### Form Tracking ```typescript const trackSubmit = useTrack("form_submitted"); const handleSubmit = async (data) => { trackSubmit({ formId: "contact", fields: Object.keys(data).length, hasAttachment: !!data.file, }); await submitForm(data); }; ``` ### Feature Usage ```typescript const trackFeature = useTrack("feature_used"); const handleExport = async (format: string) => { trackFeature({ feature: "export", format, recordCount: data.length, }); await exportData(format); }; ``` ### Error Boundary Integration ```typescript class ErrorBoundary extends React.Component { componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { analytics.captureException(error, { componentStack: errorInfo.componentStack, errorBoundary: this.props.name, }); } } ``` ### Manual Page Tracking (React SPA) ```typescript // Required for non-Next.js apps import { useLocation } from "react-router-dom"; import { usePage } from "@thisbefine/analytics/react"; function PageTracker() { const location = useLocation(); const page = usePage(); useEffect(() => { page(location.pathname, { search: location.search, hash: location.hash, }); }, [location, page]); return null; } // Add to app root ``` ### Workspace/Team Context (B2B) ```typescript const group = useGroup(); useEffect(() => { if (workspace) { group(workspace.id, { name: workspace.name, plan: workspace.plan, memberCount: workspace.members.length, createdAt: workspace.createdAt, }); } }, [workspace, group]); ``` --- ## Local Storage Keys All keys are prefixed with `tif_`: ``` tif_anonymous_id - Random ID for non-logged-in users tif_user_id - User ID after identify() tif_user_traits - User traits from identify() tif_account_id - Account ID from group() tif_account_traits - Account traits from group() tif_session_id - Current session ID tif_last_activity - For session timeout detection tif_opt_out - User opt-out preference ``` --- ## Common Pitfalls ### 1. Forgetting reset() on Logout ```typescript // WRONG - next user's events attributed to previous user const handleLogout = () => { router.push("/login"); }; // CORRECT const handleLogout = () => { analytics.reset(); // Clear identity first router.push("/login"); }; ``` ### 2. Calling identify() Too Late ```typescript // WRONG - events before identify() not linked router.push("/dashboard"); analytics.identify(user.id); // CORRECT - identify immediately after auth const user = await login(); analytics.identify(user.id, { email: user.email }); router.push("/dashboard"); ``` ### 3. SSR/Hydration Issues ```typescript // WRONG - will error on server const user = analytics.getUser(); // CORRECT - check for client-side const user = typeof window !== "undefined" ? analytics.getUser() : null; ``` ### 4. Multiple Analytics Instances ```typescript // WRONG - will cause duplicate events // Don't do this // CORRECT - single instance at app root ``` ### 5. Events Lost on External Navigation ```typescript // WRONG - events may not send before navigation window.location.href = "https://external.com"; // CORRECT - flush before external navigation await analytics.flush(); window.location.href = "https://external.com"; ``` ### 6. Wrong API Key Format ```typescript // WRONG // CORRECT - must start with tif_ ``` --- ## Browser Support - Chrome 80+ - Firefox 78+ - Safari 14+ - Edge 80+ - NOT supported: Internet Explorer --- ## Bundle Size - Core SDK: ~3KB gzipped - React integration: ~1KB gzipped - Total: ~4KB gzipped - Zero external dependencies --- ## API Endpoints The SDK sends data to these endpoints: ``` POST /api/v1/track - Events and page views POST /api/v1/identify - User identification POST /api/v1/group - Account grouping POST /api/v1/error - Error reports POST /api/v1/bug-report - Bug report submissions ``` All endpoints are authenticated via the `tif_` API key in request headers. --- ## Security Notes - API keys are safe to expose in client-side code (they can only send data, not read) - Never log passwords, API keys, credit card numbers, SSNs in events/errors - Use environment variables for API keys anyway (best practice) - User data is isolated by workspace --- ## TypeScript All types are exported from the main package: ```typescript import type { Analytics, AnalyticsConfig, ErrorCaptureConfig, UserTraits, AccountTraits, UserState, TrackEvent, IdentifyEvent, PageEvent, GroupEvent, ErrorPayload, Breadcrumb, LogLevel, } from "@thisbefine/analytics"; ``` --- ## More Information - Dashboard: https://thisbefine.com - Documentation: https://thisbefine.com/docs