@stacksee/analytics 0.3.4 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,17 +4,423 @@ export declare class ServerAnalytics<TEventMap extends DefaultEventMap = Default
4
4
  private providers;
5
5
  private config;
6
6
  private initialized;
7
+ /**
8
+ * Creates a new ServerAnalytics instance for server-side event tracking.
9
+ *
10
+ * The server analytics instance is designed for Node.js environments including
11
+ * long-running servers, serverless functions, and edge computing environments.
12
+ *
13
+ * @param config Analytics configuration including providers and default context
14
+ * @param config.providers Array of analytics provider instances (e.g., PostHogServerProvider)
15
+ * @param config.defaultContext Optional default context to include with all events
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { ServerAnalytics } from '@stacksee/analytics/server';
20
+ * import { PostHogServerProvider } from '@stacksee/analytics/providers/posthog';
21
+ *
22
+ * const analytics = new ServerAnalytics({
23
+ * providers: [
24
+ * new PostHogServerProvider({
25
+ * apiKey: process.env.POSTHOG_API_KEY,
26
+ * host: process.env.POSTHOG_HOST
27
+ * })
28
+ * ],
29
+ * defaultContext: {
30
+ * app: { version: '1.0.0', environment: 'production' }
31
+ * }
32
+ * });
33
+ *
34
+ * analytics.initialize();
35
+ * ```
36
+ */
7
37
  constructor(config: AnalyticsConfig);
38
+ /**
39
+ * Initializes all analytics providers.
40
+ *
41
+ * This method must be called before tracking events. It initializes all configured
42
+ * providers synchronously. Unlike the browser version, server initialization is
43
+ * typically synchronous as providers don't need to load external scripts.
44
+ *
45
+ * The method is safe to call multiple times and will not re-initialize if already done.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const analytics = new ServerAnalytics({ providers: [] });
50
+ *
51
+ * // Initialize before tracking events
52
+ * analytics.initialize();
53
+ *
54
+ * // Now ready to track events
55
+ * await analytics.track('api_request', { endpoint: '/users' });
56
+ * ```
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * // In a serverless function
61
+ * export async function handler(req, res) {
62
+ * const analytics = new ServerAnalytics({ providers: [] });
63
+ * analytics.initialize(); // Quick synchronous initialization
64
+ *
65
+ * await analytics.track('function_invoked', {
66
+ * path: req.path,
67
+ * method: req.method
68
+ * });
69
+ *
70
+ * await analytics.shutdown(); // Important for serverless
71
+ * }
72
+ * ```
73
+ */
8
74
  initialize(): void;
75
+ /**
76
+ * Identifies a user with optional traits.
77
+ *
78
+ * Associates subsequent events with the specified user ID and optionally
79
+ * sets user properties. This method is typically called when processing
80
+ * authentication or when you have user context available on the server.
81
+ *
82
+ * @param userId Unique identifier for the user (e.g., database ID, email)
83
+ * @param traits Optional user properties and characteristics
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * // Basic user identification
88
+ * analytics.identify('user-123');
89
+ * ```
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * // Identify with user traits from database
94
+ * analytics.identify('user-123', {
95
+ * email: 'john@example.com',
96
+ * name: 'John Doe',
97
+ * plan: 'enterprise',
98
+ * company: 'Acme Corp',
99
+ * createdAt: '2024-01-15T10:00:00Z',
100
+ * lastSeenAt: new Date().toISOString()
101
+ * });
102
+ * ```
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * // In an API authentication middleware
107
+ * async function authMiddleware(req, res, next) {
108
+ * const user = await getUserFromToken(req.headers.authorization);
109
+ *
110
+ * analytics.identify(user.id, {
111
+ * email: user.email,
112
+ * role: user.role,
113
+ * organization: user.organization
114
+ * });
115
+ *
116
+ * req.user = user;
117
+ * next();
118
+ * }
119
+ * ```
120
+ */
9
121
  identify(userId: string, traits?: Record<string, unknown>): void;
122
+ /**
123
+ * Tracks a custom event with properties and optional context.
124
+ *
125
+ * This is the main method for tracking business events on the server side.
126
+ * The method sends the event to all configured providers and waits for completion.
127
+ * Failed providers don't prevent others from succeeding.
128
+ *
129
+ * Server-side tracking typically includes additional context like IP addresses,
130
+ * user agents, and server-specific metadata that isn't available on the client.
131
+ *
132
+ * @param eventName Name of the event to track (must match your event definitions)
133
+ * @param properties Event-specific properties and data
134
+ * @param options Optional configuration including user ID, session ID, and context
135
+ * @param options.userId User ID to associate with this event
136
+ * @param options.sessionId Session ID to associate with this event
137
+ * @param options.context Additional context for this event
138
+ * @returns Promise that resolves when tracking is complete for all providers
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * // Basic event tracking
143
+ * await analytics.track('api_request', {
144
+ * endpoint: '/api/users',
145
+ * method: 'GET',
146
+ * responseTime: 150,
147
+ * statusCode: 200
148
+ * });
149
+ * ```
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * // Track with user context
154
+ * await analytics.track('purchase_completed', {
155
+ * orderId: 'order-123',
156
+ * amount: 99.99,
157
+ * currency: 'USD',
158
+ * itemCount: 3
159
+ * }, {
160
+ * userId: 'user-456',
161
+ * sessionId: 'session-789',
162
+ * context: {
163
+ * page: { path: '/checkout/complete' },
164
+ * device: { userAgent: req.headers['user-agent'] },
165
+ * ip: req.ip
166
+ * }
167
+ * });
168
+ * ```
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * // In an Express.js route handler
173
+ * app.post('/api/users', async (req, res) => {
174
+ * const user = await createUser(req.body);
175
+ *
176
+ * // Track user creation with server context
177
+ * await analytics.track('user_created', {
178
+ * userId: user.id,
179
+ * email: user.email,
180
+ * plan: user.plan
181
+ * }, {
182
+ * userId: user.id,
183
+ * context: {
184
+ * page: { path: req.path },
185
+ * device: { userAgent: req.headers['user-agent'] },
186
+ * ip: req.ip,
187
+ * server: { version: process.env.APP_VERSION }
188
+ * }
189
+ * });
190
+ *
191
+ * res.json(user);
192
+ * });
193
+ * ```
194
+ *
195
+ * @example
196
+ * ```typescript
197
+ * // Error handling in tracking
198
+ * try {
199
+ * await analytics.track('payment_processed', {
200
+ * amount: 100,
201
+ * currency: 'USD'
202
+ * });
203
+ * } catch (error) {
204
+ * // This only catches initialization errors
205
+ * // Individual provider failures are logged but don't throw
206
+ * console.error('Failed to track event:', error);
207
+ * }
208
+ * ```
209
+ */
10
210
  track<TEventName extends keyof TEventMap & string>(eventName: TEventName, properties: TEventMap[TEventName], options?: {
11
211
  userId?: string;
12
212
  sessionId?: string;
13
213
  context?: EventContext;
14
214
  }): Promise<void>;
15
- page(properties?: Record<string, unknown>, options?: {
215
+ /**
216
+ * Tracks a page view event from the server side.
217
+ *
218
+ * Server-side page view tracking is useful for server-rendered applications,
219
+ * SSR frameworks, or when you want to ensure page views are tracked even
220
+ * if client-side JavaScript fails.
221
+ *
222
+ * @param properties Optional properties to include with the page view
223
+ * @param options Optional configuration including context
224
+ * @param options.context Additional context for this page view
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * // Basic server-side page view
229
+ * analytics.pageView();
230
+ * ```
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * // Page view with server context
235
+ * analytics.pageView({
236
+ * loadTime: 250,
237
+ * template: 'product-detail',
238
+ * ssr: true
239
+ * }, {
240
+ * context: {
241
+ * page: {
242
+ * path: '/products/widget-123',
243
+ * title: 'Amazing Widget - Product Details'
244
+ * },
245
+ * device: {
246
+ * userAgent: req.headers['user-agent']
247
+ * },
248
+ * server: {
249
+ * renderTime: 45,
250
+ * cacheHit: false
251
+ * }
252
+ * }
253
+ * });
254
+ * ```
255
+ *
256
+ * @example
257
+ * ```typescript
258
+ * // In a Next.js API route or middleware
259
+ * export async function middleware(req) {
260
+ * if (req.nextUrl.pathname.startsWith('/product/')) {
261
+ * analytics.pageView({
262
+ * category: 'product',
263
+ * productId: req.nextUrl.pathname.split('/').pop()
264
+ * }, {
265
+ * context: {
266
+ * page: { path: req.nextUrl.pathname },
267
+ * device: { userAgent: req.headers.get('user-agent') },
268
+ * referrer: req.headers.get('referer')
269
+ * }
270
+ * });
271
+ * }
272
+ * }
273
+ * ```
274
+ */
275
+ pageView(properties?: Record<string, unknown>, options?: {
16
276
  context?: EventContext;
17
277
  }): void;
278
+ /**
279
+ * Tracks when a user leaves a page from the server side.
280
+ *
281
+ * Server-side page leave tracking is less common than client-side but can be
282
+ * useful in certain scenarios like tracking session timeouts, or when combined
283
+ * with server-side session management.
284
+ *
285
+ * Note: Not all analytics providers support page leave events. The method
286
+ * will only call providers that implement the pageLeave method.
287
+ *
288
+ * @param properties Optional properties to include with the page leave event
289
+ * @param options Optional configuration including context
290
+ * @param options.context Additional context for this page leave
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * // Basic page leave tracking
295
+ * analytics.pageLeave();
296
+ * ```
297
+ *
298
+ * @example
299
+ * ```typescript
300
+ * // Page leave with session context
301
+ * analytics.pageLeave({
302
+ * sessionDuration: 45000, // 45 seconds
303
+ * pagesViewed: 3,
304
+ * exitReason: 'session_timeout'
305
+ * }, {
306
+ * context: {
307
+ * session: {
308
+ * id: 'session-123',
309
+ * startTime: sessionStartTime,
310
+ * endTime: Date.now()
311
+ * },
312
+ * server: {
313
+ * reason: 'inactivity_timeout'
314
+ * }
315
+ * }
316
+ * });
317
+ * ```
318
+ *
319
+ * @example
320
+ * ```typescript
321
+ * // In a session cleanup job
322
+ * async function cleanupExpiredSessions() {
323
+ * const expiredSessions = await getExpiredSessions();
324
+ *
325
+ * for (const session of expiredSessions) {
326
+ * analytics.pageLeave({
327
+ * sessionId: session.id,
328
+ * duration: session.duration,
329
+ * reason: 'expired'
330
+ * });
331
+ *
332
+ * await removeSession(session.id);
333
+ * }
334
+ * }
335
+ * ```
336
+ */
337
+ pageLeave(properties?: Record<string, unknown>, options?: {
338
+ context?: EventContext;
339
+ }): void;
340
+ /**
341
+ * Shuts down all analytics providers and flushes pending events.
342
+ *
343
+ * This method is crucial for server environments, especially serverless functions,
344
+ * as it ensures all events are sent before the process terminates. Some providers
345
+ * batch events and need an explicit flush to send them.
346
+ *
347
+ * Always call this method before your server shuts down or before a serverless
348
+ * function completes execution.
349
+ *
350
+ * @returns Promise that resolves when all providers have been shut down
351
+ *
352
+ * @example
353
+ * ```typescript
354
+ * // Basic shutdown
355
+ * await analytics.shutdown();
356
+ * ```
357
+ *
358
+ * @example
359
+ * ```typescript
360
+ * // In a serverless function
361
+ * export async function handler(event, context) {
362
+ * const analytics = new ServerAnalytics({ providers: [] });
363
+ * analytics.initialize();
364
+ *
365
+ * try {
366
+ * // Process the event
367
+ * await processEvent(event);
368
+ *
369
+ * // Track completion
370
+ * await analytics.track('function_completed', {
371
+ * duration: Date.now() - startTime,
372
+ * success: true
373
+ * });
374
+ * } catch (error) {
375
+ * await analytics.track('function_failed', {
376
+ * error: error.message,
377
+ * duration: Date.now() - startTime
378
+ * });
379
+ * } finally {
380
+ * // Always shutdown to flush events
381
+ * await analytics.shutdown();
382
+ * }
383
+ *
384
+ * return { statusCode: 200 };
385
+ * }
386
+ * ```
387
+ *
388
+ * @example
389
+ * ```typescript
390
+ * // In an Express.js server
391
+ * const server = app.listen(3000);
392
+ *
393
+ * // Graceful shutdown
394
+ * process.on('SIGTERM', async () => {
395
+ * console.log('Shutting down gracefully...');
396
+ *
397
+ * server.close(async () => {
398
+ * // Flush analytics events before exit
399
+ * await analytics.shutdown();
400
+ * process.exit(0);
401
+ * });
402
+ * });
403
+ * ```
404
+ *
405
+ * @example
406
+ * ```typescript
407
+ * // With Vercel's waitUntil
408
+ * import { waitUntil } from '@vercel/functions';
409
+ *
410
+ * export default async function handler(req, res) {
411
+ * // Process request
412
+ * const result = await processRequest(req);
413
+ *
414
+ * // Track in background without blocking response
415
+ * waitUntil(
416
+ * analytics.track('api_request', { endpoint: req.url })
417
+ * .then(() => analytics.shutdown())
418
+ * );
419
+ *
420
+ * return res.json(result);
421
+ * }
422
+ * ```
423
+ */
18
424
  shutdown(): Promise<void>;
19
425
  private getCategoryFromEventName;
20
426
  }
@@ -1,7 +1,7 @@
1
- export { createClientAnalytics, createAnalytics, getAnalytics, track, identify, page, reset, type ClientAnalyticsConfig, } from '../client.js';
1
+ export { createClientAnalytics, createAnalytics, getAnalytics, track, identify, pageView, pageLeave, reset, type ClientAnalyticsConfig, } from '../client.js';
2
2
  export { BrowserAnalytics } from '../adapters/client/browser-analytics.js';
3
3
  export { PostHogClientProvider } from '../providers/posthog/client.js';
4
- export type { PostHogConfig } from '../providers/posthog/types.js';
4
+ export type { PostHogConfig } from 'posthog-js';
5
5
  export { BaseAnalyticsProvider } from '../providers/base.provider.js';
6
6
  export type { EventCategory, BaseEvent, EventContext, AnalyticsProvider, AnalyticsConfig, } from '../core/events/types.js';
7
7
  export type { CreateEventDefinition, ExtractEventNames, ExtractEventPropertiesFromCollection, EventCollection, AnyEventName, AnyEventProperties, } from '../core/events/index.js';
@@ -52,7 +52,11 @@ export declare function identify(userId: string, traits?: Record<string, unknown
52
52
  /**
53
53
  * Convenience function to track page views
54
54
  */
55
- export declare function page(properties?: Record<string, unknown>): void;
55
+ export declare function pageView(properties?: Record<string, unknown>): void;
56
+ /**
57
+ * Convenience function to track page leave events
58
+ */
59
+ export declare function pageLeave(properties?: Record<string, unknown>): void;
56
60
  /**
57
61
  * Convenience function to reset user session
58
62
  */
@@ -30,7 +30,8 @@ export interface AnalyticsProvider {
30
30
  initialize(): Promise<void> | void;
31
31
  identify(userId: string, traits?: Record<string, unknown>): Promise<void> | void;
32
32
  track(event: BaseEvent, context?: EventContext): Promise<void> | void;
33
- page(properties?: Record<string, unknown>, context?: EventContext): Promise<void> | void;
33
+ pageView(properties?: Record<string, unknown>, context?: EventContext): Promise<void> | void;
34
+ pageLeave?(properties?: Record<string, unknown>, context?: EventContext): Promise<void> | void;
34
35
  reset(): Promise<void> | void;
35
36
  }
36
37
  export interface AnalyticsConfig {
@@ -1,6 +1,5 @@
1
1
  export type { EventCategory, BaseEvent, EventContext, AnalyticsProvider, AnalyticsConfig, } from './core/events/types.js';
2
2
  export type { CreateEventDefinition, ExtractEventNames, ExtractEventPropertiesFromCollection, EventCollection, AnyEventName, AnyEventProperties, EventMapFromCollection, } from './core/events/index.js';
3
- export { createAnalytics as createClientAnalytics, getAnalytics, track as trackClient, identify as identifyClient, page as pageClient, reset as resetClient, type ClientAnalyticsConfig, } from './client.js';
4
- export { createServerAnalytics, ServerAnalytics, type ServerAnalyticsConfig, } from './server.js';
5
- export { BaseAnalyticsProvider, PostHogClientProvider, PostHogServerProvider, type PostHogConfig, } from './providers/index.js';
3
+ export { createAnalytics as createClientAnalytics, getAnalytics, track as trackClient, identify as identifyClient, pageView as pageViewClient, pageLeave as pageLeaveClient, reset as resetClient, type ClientAnalyticsConfig, } from './client.js';
4
+ export { BaseAnalyticsProvider, PostHogClientProvider, type PostHogConfig, } from './providers/client.js';
6
5
  export { BrowserAnalytics } from './adapters/client/browser-analytics.js';
@@ -10,7 +10,8 @@ export declare abstract class BaseAnalyticsProvider implements AnalyticsProvider
10
10
  abstract initialize(): Promise<void> | void;
11
11
  abstract identify(userId: string, traits?: Record<string, unknown>): Promise<void> | void;
12
12
  abstract track(event: BaseEvent, context?: EventContext): Promise<void> | void;
13
- abstract page(properties?: Record<string, unknown>, context?: EventContext): Promise<void> | void;
13
+ abstract pageView(properties?: Record<string, unknown>, context?: EventContext): Promise<void> | void;
14
+ pageLeave?(properties?: Record<string, unknown>, context?: EventContext): Promise<void> | void;
14
15
  abstract reset(): Promise<void> | void;
15
16
  protected log(message: string, data?: unknown): void;
16
17
  protected isEnabled(): boolean;
@@ -0,0 +1,3 @@
1
+ export { BaseAnalyticsProvider } from './base.provider.js';
2
+ export { PostHogClientProvider } from './posthog/client.js';
3
+ export type { PostHogConfig } from 'posthog-js';
@@ -1,4 +1,5 @@
1
1
  export { BaseAnalyticsProvider } from './base.provider.js';
2
2
  export { PostHogClientProvider } from './posthog/client.js';
3
3
  export { PostHogServerProvider } from './posthog/server.js';
4
- export type { PostHogConfig } from './posthog/types.js';
4
+ export type { PostHogConfig } from 'posthog-js';
5
+ export type { PostHogOptions } from 'posthog-node';
@@ -1,18 +1,20 @@
1
1
  import { BaseEvent, EventContext } from '../../core/events/types.js';
2
2
  import { BaseAnalyticsProvider } from '../base.provider.js';
3
- import { PostHogConfig } from './types.js';
3
+ import { PostHogConfig } from 'posthog-js';
4
4
  export declare class PostHogClientProvider extends BaseAnalyticsProvider {
5
5
  name: string;
6
6
  private posthog?;
7
7
  private initialized;
8
8
  private config;
9
- constructor(config: PostHogConfig & {
9
+ constructor(config: Partial<PostHogConfig> & {
10
+ token: string;
10
11
  debug?: boolean;
11
12
  enabled?: boolean;
12
13
  });
13
14
  initialize(): Promise<void>;
14
15
  identify(userId: string, traits?: Record<string, unknown>): void;
15
16
  track(event: BaseEvent, context?: EventContext): void;
16
- page(properties?: Record<string, unknown>, context?: EventContext): void;
17
+ pageView(properties?: Record<string, unknown>, context?: EventContext): void;
18
+ pageLeave(properties?: Record<string, unknown>, context?: EventContext): void;
17
19
  reset(): void;
18
20
  }
@@ -1,19 +1,21 @@
1
1
  import { BaseEvent, EventContext } from '../../core/events/types.js';
2
2
  import { BaseAnalyticsProvider } from '../base.provider.js';
3
- import { PostHogConfig } from './types.js';
3
+ import { PostHogOptions } from 'posthog-node';
4
4
  export declare class PostHogServerProvider extends BaseAnalyticsProvider {
5
5
  name: string;
6
6
  private client?;
7
7
  private initialized;
8
8
  private config;
9
- constructor(config: PostHogConfig & {
9
+ constructor(config: {
10
+ apiKey: string;
11
+ } & PostHogOptions & {
10
12
  debug?: boolean;
11
13
  enabled?: boolean;
12
14
  });
13
15
  initialize(): void;
14
16
  identify(userId: string, traits?: Record<string, unknown>): void;
15
17
  track(event: BaseEvent, context?: EventContext): void;
16
- page(properties?: Record<string, unknown>, context?: EventContext): void;
18
+ pageView(properties?: Record<string, unknown>, context?: EventContext): void;
17
19
  reset(): Promise<void>;
18
20
  shutdown(): Promise<void>;
19
21
  }
@@ -1,6 +1,6 @@
1
1
  export { createServerAnalytics, ServerAnalytics, type ServerAnalyticsConfig, } from '../server.js';
2
2
  export { PostHogServerProvider } from '../providers/posthog/server.js';
3
- export type { PostHogConfig } from '../providers/posthog/types.js';
3
+ export type { PostHogOptions } from 'posthog-node';
4
4
  export { BaseAnalyticsProvider } from '../providers/base.provider.js';
5
5
  export type { EventCategory, BaseEvent, EventContext, AnalyticsProvider, AnalyticsConfig, } from '../core/events/types.js';
6
6
  export type { CreateEventDefinition, ExtractEventNames, ExtractEventPropertiesFromCollection, EventCollection, AnyEventName, AnyEventProperties, } from '../core/events/index.js';
@@ -13,7 +13,11 @@ export declare class MockAnalyticsProvider extends BaseAnalyticsProvider {
13
13
  event: BaseEvent;
14
14
  context?: EventContext;
15
15
  }>;
16
- page: Array<{
16
+ pageView: Array<{
17
+ properties?: Record<string, unknown>;
18
+ context?: EventContext;
19
+ }>;
20
+ pageLeave: Array<{
17
21
  properties?: Record<string, unknown>;
18
22
  context?: EventContext;
19
23
  }>;
@@ -22,7 +26,8 @@ export declare class MockAnalyticsProvider extends BaseAnalyticsProvider {
22
26
  initialize(): void;
23
27
  identify(userId: string, traits?: Record<string, unknown>): void;
24
28
  track(event: BaseEvent, context?: EventContext): void;
25
- page(properties?: Record<string, unknown>, context?: EventContext): void;
29
+ pageView(properties?: Record<string, unknown>, context?: EventContext): void;
30
+ pageLeave(properties?: Record<string, unknown>, context?: EventContext): void;
26
31
  reset(): void;
27
32
  clearCalls(): void;
28
33
  isInitialized(): boolean;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stacksee/analytics",
3
- "version": "0.3.4",
3
+ "version": "0.4.3",
4
4
  "description": "A highly typed, provider-agnostic analytics library for TypeScript applications",
5
5
  "type": "module",
6
6
  "exports": {
@@ -57,8 +57,8 @@
57
57
  "vitest": "^2.0.3"
58
58
  },
59
59
  "optionalDependencies": {
60
- "posthog-js": "^1.96.0",
61
- "posthog-node": "^3.2.0"
60
+ "posthog-js": "^1.268.2",
61
+ "posthog-node": "^5.9.0"
62
62
  },
63
63
  "engines": {
64
64
  "pnpm": ">=9.0.0",
package/readme.md CHANGED
@@ -445,6 +445,42 @@ export const POST: RequestHandler = async ({ request }) => {
445
445
  };
446
446
  ```
447
447
 
448
+ #### Note for SvelteKit Users: Navigation Tracking
449
+
450
+ If you're using SvelteKit and want to track page views and page leaves automatically with PostHog (as recommended in their documentation), add this to your root layout:
451
+
452
+ ```typescript
453
+ // src/app.html or src/routes/+layout.svelte
454
+ <script>
455
+ import { pageView, pageLeave } from '@stacksee/analytics/client';
456
+ import { beforeNavigate, afterNavigate } from '$app/navigation';
457
+ import { browser } from '$app/environment';
458
+
459
+ let { children } = $props():
460
+
461
+ // Only set up navigation tracking in the browser
462
+ if (browser) {
463
+ beforeNavigate(() => {
464
+ pageLeave();
465
+ });
466
+
467
+ afterNavigate(() => {
468
+ pageView();
469
+ });
470
+ }
471
+ </script>
472
+
473
+ <main>
474
+ {@render children()}
475
+ </main>
476
+ ```
477
+
478
+ This automatically tracks:
479
+ - **Page leaves** before navigation (`$pageleave` events in PostHog)
480
+ - **Page views** after navigation (`$pageview` events in PostHog)
481
+
482
+ The tracking is framework-agnostic, so you can use similar patterns with Next.js router events, Vue Router hooks, or any other navigation system.
483
+
448
484
  ### Event Categories
449
485
 
450
486
  Event categories help organize your analytics data. The SDK provides predefined categories with TypeScript autocomplete:
@@ -759,7 +795,8 @@ const analytics = createClientAnalytics<typeof AppEvents>({
759
795
  #### `BrowserAnalytics<TEventMap>`
760
796
  - `track(eventName, properties): Promise<void>` - Track an event with type-safe event names and properties
761
797
  - `identify(userId, traits)` - Identify a user
762
- - `page(properties)` - Track a page view
798
+ - `pageView(properties)` - Track a page view
799
+ - `pageLeave(properties)` - Track a page leave event
763
800
  - `reset()` - Reset user session
764
801
  - `updateContext(context)` - Update event context
765
802
 
@@ -784,7 +821,8 @@ const analytics = createServerAnalytics<AppEvents>({
784
821
  #### `ServerAnalytics<TEventMap>`
785
822
  - `track(eventName, properties, options): Promise<void>` - Track an event with type-safe event names and properties
786
823
  - `identify(userId, traits)` - Identify a user
787
- - `page(properties, options)` - Track a page view
824
+ - `pageView(properties, options)` - Track a page view
825
+ - `pageLeave(properties, options)` - Track a page leave event
788
826
  - `shutdown()` - Flush pending events and cleanup
789
827
 
790
828
  ### Type Helpers