From 46a3c1d6d4986eb2dffc1925d93e54f1a177419b Mon Sep 17 00:00:00 2001 From: Edwin Tantawi Date: Sun, 25 Jan 2026 14:51:35 +0700 Subject: [PATCH 1/3] feat(analytic): add posthog wrapper for analytic capture --- src/lib/analytic/events.ts | 19 +++++++++++++++++++ src/lib/analytic/index.ts | 25 +++++++++++++++++++++++++ src/lib/analytic/properties.ts | 27 +++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 src/lib/analytic/events.ts create mode 100644 src/lib/analytic/index.ts create mode 100644 src/lib/analytic/properties.ts diff --git a/src/lib/analytic/events.ts b/src/lib/analytic/events.ts new file mode 100644 index 0000000..5d6ca78 --- /dev/null +++ b/src/lib/analytic/events.ts @@ -0,0 +1,19 @@ +/** + * Analytics events + * + * Uses the `category:object_action` naming convention to ensure consistent and descriptive event naming. + * - **category**: The context or domain where the event occurred (e.g., `auth`, `account_settings`, `signup_flow`) + * - **object**: The component, feature, or location involved (e.g., `signup_button`, `pricing_page`) + * - **action**: The user action or system event that occurred (e.g., `click`, `submit`, `view`, `cancel`) + * + * @examples + * - `account_settings:forgot_password_button_click` + * - `signup_flow:pricing_page_view` + * - `registration:sign_up_button_click` + * - `registration_v2:sign_up_button_click` - version your events + * + * @see {@link https://posthog.com/docs/product-analytics/best-practices#2-implement-a-naming-convention} + */ +export enum AnalyticEvent { + EXAMPLE = 'example:view_example', +} diff --git a/src/lib/analytic/index.ts b/src/lib/analytic/index.ts new file mode 100644 index 0000000..26b3a33 --- /dev/null +++ b/src/lib/analytic/index.ts @@ -0,0 +1,25 @@ +import { usePostHog } from '@posthog/react'; +import type { CaptureOptions } from 'posthog-js'; + +import { AnalyticEvent } from '~/lib/analytic/events'; +import type { AnalyticProperty } from '~/lib/analytic/properties'; + +/** + * Hook for capturing analytics events using PostHog. + * + * Provides a typed interface for tracking analytics events with associated properties. + * This hook wraps the PostHog client to ensure type-safe event capturing. + */ +export function useAnalytic() { + const posthog = usePostHog(); + + function capture( + event: TEvent, + properties?: AnalyticProperty[TEvent] | null, + options?: CaptureOptions, + ) { + posthog.capture(event, properties, options); + } + + return { ...posthog, capture }; +} diff --git a/src/lib/analytic/properties.ts b/src/lib/analytic/properties.ts new file mode 100644 index 0000000..9a43744 --- /dev/null +++ b/src/lib/analytic/properties.ts @@ -0,0 +1,27 @@ +import type { AnalyticEvent } from '~/lib/analytic/events'; + +/** + * Analytics properties - contextual data attached to events + * + * Uses naming conventions to ensure consistent and descriptive property naming. + * - **object_adjective pattern**: Use descriptive property names (e.g., `user_id`, `item_price`, `member_count`) + * - **boolean prefixes**: Use `is_` or `has_` for boolean properties (e.g., `is_subscribed`, `has_seen_upsell`) + * - **temporal suffixes**: For dates/timestamps, include `_date` or `_timestamp` (e.g., `user_creation_date`, `last_login_timestamp`) + * + * @examples + * - `user_id` + * - `item_price` + * - `member_count` + * - `is_subscribed` + * - `has_seen_upsell` + * - `last_login_timestamp` + * - `user_creation_date` + * + * @see {@link https://posthog.com/docs/product-analytics/best-practices#2-implement-a-naming-convention} + */ +export interface AnalyticProperty { + [AnalyticEvent.EXAMPLE]: { + example_id: string; + example_timestamp: number; + }; +} From 16ee09066fd8cc5de729ae180f08b6ca8a9c6fbf Mon Sep 17 00:00:00 2001 From: Edwin Tantawi Date: Sun, 25 Jan 2026 15:17:53 +0700 Subject: [PATCH 2/3] feat(analytic): add server analytic with posthog-node --- src/lib/analytic/index.ts | 37 +++++++++++++++++++++++++++++++++++-- src/lib/analytic/types.ts | 9 +++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/lib/analytic/types.ts diff --git a/src/lib/analytic/index.ts b/src/lib/analytic/index.ts index 26b3a33..7174bc5 100644 --- a/src/lib/analytic/index.ts +++ b/src/lib/analytic/index.ts @@ -3,6 +3,8 @@ import type { CaptureOptions } from 'posthog-js'; import { AnalyticEvent } from '~/lib/analytic/events'; import type { AnalyticProperty } from '~/lib/analytic/properties'; +import type { AnalyticEventMessage } from '~/lib/analytic/types'; +import { createPosthogClient } from '~/lib/posthog/server'; /** * Hook for capturing analytics events using PostHog. @@ -15,11 +17,42 @@ export function useAnalytic() { function capture( event: TEvent, - properties?: AnalyticProperty[TEvent] | null, + properties: AnalyticProperty[TEvent], options?: CaptureOptions, ) { - posthog.capture(event, properties, options); + return posthog.capture(event, properties, options); } return { ...posthog, capture }; } + +/** + * Creates a server-side analytics client using PostHog. + * + * This function initializes a PostHog client for server-side analytics tracking, + * providing a type-safe interface for capturing analytics events immediately. + * Useful for tracking events that occur during server-side operations. + */ +export function createAnalyticClient() { + const posthog = createPosthogClient(); + + function captureImmediate( + props: AnalyticEventMessage, + ) { + return posthog.captureImmediate({ + event: props.event, + properties: props.properties || undefined, + }); + } + + function capture( + props: AnalyticEventMessage, + ) { + return posthog.capture({ + event: props.event, + properties: props.properties || undefined, + }); + } + + return { ...posthog, capture, captureImmediate }; +} diff --git a/src/lib/analytic/types.ts b/src/lib/analytic/types.ts new file mode 100644 index 0000000..0fc5f68 --- /dev/null +++ b/src/lib/analytic/types.ts @@ -0,0 +1,9 @@ +import type { EventMessage } from 'posthog-node'; + +import type { AnalyticEvent } from '~/lib/analytic/events'; +import type { AnalyticProperty } from '~/lib/analytic/properties'; + +export type AnalyticEventMessage = { + event: TEvent; + properties: AnalyticProperty[TEvent]; +} & Omit; From 82cfb54bb5207234a281f5cb0f243c04ffdb09e2 Mon Sep 17 00:00:00 2001 From: Edwin Tantawi Date: Mon, 26 Jan 2026 08:00:16 +0700 Subject: [PATCH 3/3] feat: remove example event and property --- src/lib/analytic/events.ts | 4 +--- src/lib/analytic/index.ts | 10 +++++----- src/lib/analytic/properties.ts | 9 +-------- src/lib/analytic/types.ts | 4 +++- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/lib/analytic/events.ts b/src/lib/analytic/events.ts index 5d6ca78..e70c780 100644 --- a/src/lib/analytic/events.ts +++ b/src/lib/analytic/events.ts @@ -14,6 +14,4 @@ * * @see {@link https://posthog.com/docs/product-analytics/best-practices#2-implement-a-naming-convention} */ -export enum AnalyticEvent { - EXAMPLE = 'example:view_example', -} +export enum AnalyticEvent {} diff --git a/src/lib/analytic/index.ts b/src/lib/analytic/index.ts index 7174bc5..8cad68a 100644 --- a/src/lib/analytic/index.ts +++ b/src/lib/analytic/index.ts @@ -15,7 +15,7 @@ import { createPosthogClient } from '~/lib/posthog/server'; export function useAnalytic() { const posthog = usePostHog(); - function capture( + function capture( event: TEvent, properties: AnalyticProperty[TEvent], options?: CaptureOptions, @@ -36,16 +36,16 @@ export function useAnalytic() { export function createAnalyticClient() { const posthog = createPosthogClient(); - function captureImmediate( - props: AnalyticEventMessage, - ) { + function captureImmediate< + TEvent extends AnalyticEvent & keyof AnalyticProperty, + >(props: AnalyticEventMessage) { return posthog.captureImmediate({ event: props.event, properties: props.properties || undefined, }); } - function capture( + function capture( props: AnalyticEventMessage, ) { return posthog.capture({ diff --git a/src/lib/analytic/properties.ts b/src/lib/analytic/properties.ts index 9a43744..940abd9 100644 --- a/src/lib/analytic/properties.ts +++ b/src/lib/analytic/properties.ts @@ -1,5 +1,3 @@ -import type { AnalyticEvent } from '~/lib/analytic/events'; - /** * Analytics properties - contextual data attached to events * @@ -19,9 +17,4 @@ import type { AnalyticEvent } from '~/lib/analytic/events'; * * @see {@link https://posthog.com/docs/product-analytics/best-practices#2-implement-a-naming-convention} */ -export interface AnalyticProperty { - [AnalyticEvent.EXAMPLE]: { - example_id: string; - example_timestamp: number; - }; -} +export interface AnalyticProperty {} diff --git a/src/lib/analytic/types.ts b/src/lib/analytic/types.ts index 0fc5f68..ec0996e 100644 --- a/src/lib/analytic/types.ts +++ b/src/lib/analytic/types.ts @@ -3,7 +3,9 @@ import type { EventMessage } from 'posthog-node'; import type { AnalyticEvent } from '~/lib/analytic/events'; import type { AnalyticProperty } from '~/lib/analytic/properties'; -export type AnalyticEventMessage = { +export type AnalyticEventMessage< + TEvent extends AnalyticEvent & keyof AnalyticProperty, +> = { event: TEvent; properties: AnalyticProperty[TEvent]; } & Omit;