AI-Driven Personalization Engines for Web Apps

Personalization has evolved from a nice-to-have feature to a business imperative. Studies show that e-commerce sites implementing AI-driven personalization see conversion increases of 35% or more, while users increasingly expect experiences tailored to their preferences. Netflix attributes $1 billion in annual savings to their recommendation system by reducing subscriber churn.

In this comprehensive guide, we'll explore how to build sophisticated personalization engines using modern web technologies. From recommendation algorithms and collaborative filtering to real-time content adaptation with TensorFlow.js, you'll learn practical techniques that transform generic web applications into intelligent, user-aware experiences.

Understanding AI Personalization

AI-driven personalization goes beyond simple rule-based systems ("show product A to users who bought product B"). Modern personalization engines analyze user behavior patterns, predict preferences, and adapt content in real-time using machine learning models. According to McKinsey research, companies that excel at personalization generate 40% more revenue from those activities than average players.

Types of Personalization

There are several approaches to personalization, each suited for different scenarios:

// personalization-types.ts

// 1. Content-Based Filtering
// Recommends items similar to what user has liked before
interface ContentBasedPersonalization {
    userProfile: UserPreferences;
    itemFeatures: ItemFeature[];
    similarity: 'cosine' | 'euclidean' | 'jaccard';
}

// 2. Collaborative Filtering
// Recommends based on similar users' preferences
interface CollaborativeFiltering {
    type: 'user-based' | 'item-based' | 'matrix-factorization';
    userItemMatrix: number[][];
    latentFactors?: number;
}

// 3. Hybrid Systems
// Combines multiple approaches for better accuracy
interface HybridPersonalization {
    contentWeight: number;
    collaborativeWeight: number;
    contextualWeight: number;
    fallbackStrategy: 'popularity' | 'random' | 'editorial';
}

// 4. Contextual Personalization
// Adapts based on real-time context
interface ContextualPersonalization {
    timeOfDay: 'morning' | 'afternoon' | 'evening' | 'night';
    device: 'mobile' | 'tablet' | 'desktop';
    location?: GeoLocation;
    sessionBehavior: SessionData;
}

Building the User Behavior Tracking System

Effective personalization requires understanding user behavior. Here's a comprehensive tracking system that captures interactions while respecting privacy:

// behavior-tracker.ts
import { v4 as uuidv4 } from 'uuid';

interface UserEvent {
    eventId: string;
    userId: string;
    sessionId: string;
    eventType: EventType;
    timestamp: number;
    data: EventData;
    context: EventContext;
}

type EventType =
    | 'page_view'
    | 'product_view'
    | 'add_to_cart'
    | 'purchase'
    | 'search'
    | 'click'
    | 'scroll_depth'
    | 'time_on_page'
    | 'hover'
    | 'wishlist_add';

interface EventData {
    itemId?: string;
    category?: string;
    price?: number;
    query?: string;
    position?: number;
    scrollPercentage?: number;
    dwellTime?: number;
}

interface EventContext {
    pageUrl: string;
    referrer: string;
    device: DeviceInfo;
    viewport: { width: number; height: number };
}

class BehaviorTracker {
    private userId: string;
    private sessionId: string;
    private eventQueue: UserEvent[] = [];
    private flushInterval: number = 5000;
    private batchSize: number = 10;

    constructor(userId?: string) {
        this.userId = userId || this.getOrCreateAnonymousId();
        this.sessionId = this.getOrCreateSessionId();
        this.initializeTracking();
    }

    private getOrCreateAnonymousId(): string {
        let id = localStorage.getItem('anon_user_id');
        if (!id) {
            id = uuidv4();
            localStorage.setItem('anon_user_id', id);
        }
        return id;
    }

    private getOrCreateSessionId(): string {
        let sessionId = sessionStorage.getItem('session_id');
        if (!sessionId) {
            sessionId = uuidv4();
            sessionStorage.setItem('session_id', sessionId);
        }
        return sessionId;
    }

    private initializeTracking(): void {
        // Auto-track page views
        this.trackPageView();

        // Track scroll depth
        this.initScrollTracking();

        // Track time on page
        this.initDwellTimeTracking();

        // Flush events periodically
        setInterval(() => this.flushEvents(), this.flushInterval);

        // Flush on page unload
        window.addEventListener('beforeunload', () => this.flushEvents(true));
    }

    public track(eventType: EventType, data: EventData = {}): void {
        const event: UserEvent = {
            eventId: uuidv4(),
            userId: this.userId,
            sessionId: this.sessionId,
            eventType,
            timestamp: Date.now(),
            data,
            context: this.getContext()
        };

        this.eventQueue.push(event);

        // Immediate flush for critical events
        if (['purchase', 'add_to_cart'].includes(eventType)) {
            this.flushEvents();
        } else if (this.eventQueue.length >= this.batchSize) {
            this.flushEvents();
        }
    }

    private getContext(): EventContext {
        return {
            pageUrl: window.location.href,
            referrer: document.referrer,
            device: this.getDeviceInfo(),
            viewport: {
                width: window.innerWidth,
                height: window.innerHeight
            }
        };
    }

    private getDeviceInfo(): DeviceInfo {
        const ua = navigator.userAgent;
        return {
            type: /Mobile|Android|iPhone/.test(ua) ? 'mobile' :
                  /Tablet|iPad/.test(ua) ? 'tablet' : 'desktop',
            browser: this.detectBrowser(ua),
            os: this.detectOS(ua)
        };
    }

    private initScrollTracking(): void {
        let maxScroll = 0;
        const milestones = [25, 50, 75, 90, 100];
        const tracked = new Set();

        window.addEventListener('scroll', () => {
            const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
            const scrollPercent = Math.round((window.scrollY / scrollHeight) * 100);

            if (scrollPercent > maxScroll) {
                maxScroll = scrollPercent;

                milestones.forEach(milestone => {
                    if (scrollPercent >= milestone && !tracked.has(milestone)) {
                        tracked.add(milestone);
                        this.track('scroll_depth', { scrollPercentage: milestone });
                    }
                });
            }
        }, { passive: true });
    }

    private initDwellTimeTracking(): void {
        const startTime = Date.now();
        let isVisible = true;

        document.addEventListener('visibilitychange', () => {
            isVisible = document.visibilityState === 'visible';
        });

        window.addEventListener('beforeunload', () => {
            if (isVisible) {
                const dwellTime = Date.now() - startTime;
                this.track('time_on_page', { dwellTime });
            }
        });
    }

    private async flushEvents(sync: boolean = false): Promise {
        if (this.eventQueue.length === 0) return;

        const events = [...this.eventQueue];
        this.eventQueue = [];

        const payload = JSON.stringify({ events });

        if (sync && navigator.sendBeacon) {
            navigator.sendBeacon('/api/analytics/events', payload);
        } else {
            try {
                await fetch('/api/analytics/events', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: payload
                });
            } catch (error) {
                // Re-queue events on failure
                this.eventQueue = [...events, ...this.eventQueue];
            }
        }
    }
}

// React hook for tracking
export function useTracker() {
    const tracker = useMemo(() => new BehaviorTracker(), []);

    const trackProductView = useCallback((productId: string, category: string, price: number) => {
        tracker.track('product_view', { itemId: productId, category, price });
    }, [tracker]);

    const trackAddToCart = useCallback((productId: string, price: number) => {
        tracker.track('add_to_cart', { itemId: productId, price });
    }, [tracker]);

    const trackSearch = useCallback((query: string) => {
        tracker.track('search', { query });
    }, [tracker]);

    return { trackProductView, trackAddToCart, trackSearch };
}

Implementing Collaborative Filtering

Collaborative filtering is the foundation of most recommendation systems, pioneered by researchers at Amazon and Spotify. Here's a complete implementation including both user-based and item-based approaches:

// collaborative-filter.ts

interface UserItemInteraction {
    userId: string;
    itemId: string;
    rating: number; // Can be explicit (1-5) or implicit (view=1, cart=3, purchase=5)
    timestamp: number;
}

interface SimilarityScore {
    id: string;
    similarity: number;
}

class CollaborativeFilteringEngine {
    private userItemMatrix: Map> = new Map();
    private itemUserMatrix: Map> = new Map();
    private userSimilarityCache: Map = new Map();
    private itemSimilarityCache: Map = new Map();

    constructor(interactions: UserItemInteraction[]) {
        this.buildMatrices(interactions);
    }

    private buildMatrices(interactions: UserItemInteraction[]): void {
        interactions.forEach(({ userId, itemId, rating }) => {
            // User-Item matrix
            if (!this.userItemMatrix.has(userId)) {
                this.userItemMatrix.set(userId, new Map());
            }
            this.userItemMatrix.get(userId)!.set(itemId, rating);

            // Item-User matrix (transposed)
            if (!this.itemUserMatrix.has(itemId)) {
                this.itemUserMatrix.set(itemId, new Map());
            }
            this.itemUserMatrix.get(itemId)!.set(userId, rating);
        });
    }

    // Cosine similarity between two users
    private cosineSimilarity(
        vectorA: Map,
        vectorB: Map
    ): number {
        const commonItems = [...vectorA.keys()].filter(key => vectorB.has(key));

        if (commonItems.length === 0) return 0;

        let dotProduct = 0;
        let normA = 0;
        let normB = 0;

        commonItems.forEach(item => {
            const a = vectorA.get(item)!;
            const b = vectorB.get(item)!;
            dotProduct += a * b;
        });

        vectorA.forEach(val => normA += val * val);
        vectorB.forEach(val => normB += val * val);

        return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
    }

    // Find similar users
    public findSimilarUsers(userId: string, k: number = 10): SimilarityScore[] {
        if (this.userSimilarityCache.has(userId)) {
            return this.userSimilarityCache.get(userId)!.slice(0, k);
        }

        const targetUserRatings = this.userItemMatrix.get(userId);
        if (!targetUserRatings) return [];

        const similarities: SimilarityScore[] = [];

        this.userItemMatrix.forEach((ratings, otherUserId) => {
            if (otherUserId !== userId) {
                const similarity = this.cosineSimilarity(targetUserRatings, ratings);
                if (similarity > 0) {
                    similarities.push({ id: otherUserId, similarity });
                }
            }
        });

        similarities.sort((a, b) => b.similarity - a.similarity);
        this.userSimilarityCache.set(userId, similarities);

        return similarities.slice(0, k);
    }

    // Find similar items
    public findSimilarItems(itemId: string, k: number = 10): SimilarityScore[] {
        if (this.itemSimilarityCache.has(itemId)) {
            return this.itemSimilarityCache.get(itemId)!.slice(0, k);
        }

        const targetItemRatings = this.itemUserMatrix.get(itemId);
        if (!targetItemRatings) return [];

        const similarities: SimilarityScore[] = [];

        this.itemUserMatrix.forEach((ratings, otherItemId) => {
            if (otherItemId !== itemId) {
                const similarity = this.cosineSimilarity(targetItemRatings, ratings);
                if (similarity > 0) {
                    similarities.push({ id: otherItemId, similarity });
                }
            }
        });

        similarities.sort((a, b) => b.similarity - a.similarity);
        this.itemSimilarityCache.set(itemId, similarities);

        return similarities.slice(0, k);
    }

    // User-based recommendation
    public recommendForUser(userId: string, n: number = 10): string[] {
        const userRatings = this.userItemMatrix.get(userId) || new Map();
        const ratedItems = new Set(userRatings.keys());
        const similarUsers = this.findSimilarUsers(userId, 20);

        const itemScores: Map = new Map();

        similarUsers.forEach(({ id: similarUserId, similarity }) => {
            const similarUserRatings = this.userItemMatrix.get(similarUserId)!;

            similarUserRatings.forEach((rating, itemId) => {
                if (!ratedItems.has(itemId)) {
                    const current = itemScores.get(itemId) || { score: 0, count: 0 };
                    current.score += similarity * rating;
                    current.count += 1;
                    itemScores.set(itemId, current);
                }
            });
        });

        // Calculate weighted average scores
        const recommendations: Array<{ itemId: string; score: number }> = [];

        itemScores.forEach(({ score, count }, itemId) => {
            recommendations.push({ itemId, score: score / count });
        });

        recommendations.sort((a, b) => b.score - a.score);
        return recommendations.slice(0, n).map(r => r.itemId);
    }

    // Item-based recommendation ("Users who liked this also liked...")
    public recommendSimilarItems(itemIds: string[], n: number = 10): string[] {
        const excludeItems = new Set(itemIds);
        const itemScores: Map = new Map();

        itemIds.forEach(itemId => {
            const similarItems = this.findSimilarItems(itemId, 20);

            similarItems.forEach(({ id: similarItemId, similarity }) => {
                if (!excludeItems.has(similarItemId)) {
                    const currentScore = itemScores.get(similarItemId) || 0;
                    itemScores.set(similarItemId, currentScore + similarity);
                }
            });
        });

        const recommendations = [...itemScores.entries()]
            .sort((a, b) => b[1] - a[1])
            .slice(0, n)
            .map(([itemId]) => itemId);

        return recommendations;
    }
}

Real-Time Personalization with TensorFlow.js

TensorFlow.js enables client-side machine learning for personalization. This approach improves privacy, reduces latency, and allows for real-time adaptation without server round-trips. The library supports both training models directly in the browser and running pre-trained models from TensorFlow Hub:

// tensorflow-personalization.ts
import * as tf from '@tensorflow/tfjs';

interface UserEmbedding {
    userId: string;
    embedding: Float32Array;
}

interface ItemEmbedding {
    itemId: string;
    embedding: Float32Array;
    features: ItemFeatures;
}

interface ItemFeatures {
    category: string;
    price: number;
    tags: string[];
    popularity: number;
}

class TensorFlowPersonalizationEngine {
    private model: tf.LayersModel | null = null;
    private userEmbeddingSize: number = 32;
    private itemEmbeddingSize: number = 32;
    private itemEmbeddings: Map = new Map();

    async initialize(): Promise {
        // Try to load pre-trained model, or create new one
        try {
            this.model = await tf.loadLayersModel('/models/personalization/model.json');
            console.log('Loaded pre-trained personalization model');
        } catch {
            console.log('Creating new personalization model');
            this.model = this.createModel();
        }
    }

    private createModel(): tf.LayersModel {
        // Neural Collaborative Filtering model
        const userInput = tf.input({ shape: [1], name: 'user_input' });
        const itemInput = tf.input({ shape: [1], name: 'item_input' });
        const contextInput = tf.input({ shape: [10], name: 'context_input' });

        // User embedding
        const userEmbedding = tf.layers.embedding({
            inputDim: 10000, // Max users
            outputDim: this.userEmbeddingSize,
            name: 'user_embedding'
        }).apply(userInput) as tf.SymbolicTensor;

        const userFlat = tf.layers.flatten().apply(userEmbedding) as tf.SymbolicTensor;

        // Item embedding
        const itemEmbedding = tf.layers.embedding({
            inputDim: 50000, // Max items
            outputDim: this.itemEmbeddingSize,
            name: 'item_embedding'
        }).apply(itemInput) as tf.SymbolicTensor;

        const itemFlat = tf.layers.flatten().apply(itemEmbedding) as tf.SymbolicTensor;

        // Combine user, item, and context
        const concatenated = tf.layers.concatenate().apply([
            userFlat,
            itemFlat,
            contextInput
        ]) as tf.SymbolicTensor;

        // Deep neural network layers
        let x = tf.layers.dense({
            units: 128,
            activation: 'relu',
            kernelRegularizer: tf.regularizers.l2({ l2: 0.01 })
        }).apply(concatenated) as tf.SymbolicTensor;

        x = tf.layers.dropout({ rate: 0.3 }).apply(x) as tf.SymbolicTensor;

        x = tf.layers.dense({
            units: 64,
            activation: 'relu'
        }).apply(x) as tf.SymbolicTensor;

        x = tf.layers.dropout({ rate: 0.2 }).apply(x) as tf.SymbolicTensor;

        x = tf.layers.dense({
            units: 32,
            activation: 'relu'
        }).apply(x) as tf.SymbolicTensor;

        // Output: probability of interaction
        const output = tf.layers.dense({
            units: 1,
            activation: 'sigmoid',
            name: 'output'
        }).apply(x) as tf.SymbolicTensor;

        const model = tf.model({
            inputs: [userInput, itemInput, contextInput],
            outputs: output
        });

        model.compile({
            optimizer: tf.train.adam(0.001),
            loss: 'binaryCrossentropy',
            metrics: ['accuracy']
        });

        return model;
    }

    // Encode context features
    private encodeContext(context: UserContext): tf.Tensor {
        return tf.tidy(() => {
            const features = [
                context.hourOfDay / 24,
                context.dayOfWeek / 7,
                context.isMobile ? 1 : 0,
                context.sessionDuration / 3600,
                context.pageViewCount / 50,
                context.cartValue / 1000,
                context.previousPurchases / 100,
                context.daysSinceLastPurchase / 365,
                context.isReturningUser ? 1 : 0,
                context.engagementScore
            ];
            return tf.tensor2d([features]);
        });
    }

    // Predict user interest in items
    async predictInterest(
        userId: number,
        itemIds: number[],
        context: UserContext
    ): Promise> {
        if (!this.model) {
            throw new Error('Model not initialized');
        }

        const predictions = await tf.tidy(() => {
            const userTensor = tf.tensor2d([[userId]].concat(
                Array(itemIds.length - 1).fill([userId])
            ));
            const itemTensor = tf.tensor2d(itemIds.map(id => [id]));
            const contextTensor = this.encodeContext(context).tile([itemIds.length, 1]);

            return this.model!.predict([
                userTensor,
                itemTensor,
                contextTensor
            ]) as tf.Tensor;
        });

        const scores = await predictions.data();
        predictions.dispose();

        return itemIds.map((itemId, index) => ({
            itemId,
            score: scores[index]
        })).sort((a, b) => b.score - a.score);
    }

    // Online learning: update model with new interaction
    async updateWithInteraction(
        userId: number,
        itemId: number,
        interacted: boolean,
        context: UserContext
    ): Promise {
        if (!this.model) return;

        await tf.tidy(() => {
            const userTensor = tf.tensor2d([[userId]]);
            const itemTensor = tf.tensor2d([[itemId]]);
            const contextTensor = this.encodeContext(context);
            const labelTensor = tf.tensor2d([[interacted ? 1 : 0]]);

            // Single step of gradient descent
            this.model!.fit(
                [userTensor, itemTensor, contextTensor],
                labelTensor,
                {
                    epochs: 1,
                    verbose: 0
                }
            );
        });
    }

    // Save model for persistence
    async saveModel(): Promise {
        if (!this.model) return;
        await this.model.save('localstorage://personalization-model');
    }

    // Get user embedding for visualization/clustering
    async getUserEmbedding(userId: number): Promise {
        if (!this.model) {
            throw new Error('Model not initialized');
        }

        const embeddingLayer = this.model.getLayer('user_embedding');
        const weights = embeddingLayer.getWeights()[0];
        const embedding = weights.slice([userId, 0], [1, this.userEmbeddingSize]);
        const data = await embedding.data() as Float32Array;
        embedding.dispose();
        return data;
    }
}

// React hook for TensorFlow.js personalization
export function usePersonalization() {
    const [engine, setEngine] = useState(null);
    const [isLoading, setIsLoading] = useState(true);

    useEffect(() => {
        const initEngine = async () => {
            const eng = new TensorFlowPersonalizationEngine();
            await eng.initialize();
            setEngine(eng);
            setIsLoading(false);
        };
        initEngine();
    }, []);

    const getRecommendations = useCallback(async (
        userId: number,
        candidateItems: number[],
        context: UserContext
    ) => {
        if (!engine) return [];
        return engine.predictInterest(userId, candidateItems, context);
    }, [engine]);

    const recordInteraction = useCallback(async (
        userId: number,
        itemId: number,
        interacted: boolean,
        context: UserContext
    ) => {
        if (!engine) return;
        await engine.updateWithInteraction(userId, itemId, interacted, context);
    }, [engine]);

    return { isLoading, getRecommendations, recordInteraction };
}

Dynamic Content Adaptation

Beyond product recommendations, personalization can adapt entire page layouts, messaging, and user interfaces based on user segments and behavior:

// dynamic-content.tsx
import React, { createContext, useContext, useEffect, useState } from 'react';

interface PersonalizationConfig {
    heroVariant: 'minimal' | 'featured' | 'carousel';
    ctaText: string;
    ctaColor: string;
    showSocialProof: boolean;
    pricingDisplay: 'monthly' | 'annual' | 'both';
    contentOrder: string[];
    urgencyMessages: boolean;
}

interface UserSegment {
    id: string;
    name: string;
    conditions: SegmentCondition[];
    config: Partial;
    priority: number;
}

interface SegmentCondition {
    field: string;
    operator: 'equals' | 'contains' | 'gt' | 'lt' | 'between';
    value: any;
}

const defaultConfig: PersonalizationConfig = {
    heroVariant: 'featured',
    ctaText: 'Get Started',
    ctaColor: '#007bff',
    showSocialProof: true,
    pricingDisplay: 'both',
    contentOrder: ['hero', 'features', 'pricing', 'testimonials'],
    urgencyMessages: false
};

// User segments with personalization rules
const userSegments: UserSegment[] = [
    {
        id: 'high-intent-buyer',
        name: 'High Intent Buyer',
        conditions: [
            { field: 'cartValue', operator: 'gt', value: 100 },
            { field: 'sessionCount', operator: 'gt', value: 3 }
        ],
        config: {
            ctaText: 'Complete Your Purchase',
            ctaColor: '#28a745',
            urgencyMessages: true,
            showSocialProof: true
        },
        priority: 10
    },
    {
        id: 'price-sensitive',
        name: 'Price Sensitive',
        conditions: [
            { field: 'viewedPricingPage', operator: 'gt', value: 2 },
            { field: 'addedCouponCode', operator: 'equals', value: true }
        ],
        config: {
            pricingDisplay: 'annual', // Show savings
            ctaText: 'See Our Best Deals',
            heroVariant: 'minimal'
        },
        priority: 8
    },
    {
        id: 'returning-customer',
        name: 'Returning Customer',
        conditions: [
            { field: 'previousPurchases', operator: 'gt', value: 0 }
        ],
        config: {
            heroVariant: 'carousel', // Show new products
            ctaText: 'See What\'s New',
            contentOrder: ['hero', 'new-arrivals', 'recommendations', 'features']
        },
        priority: 7
    },
    {
        id: 'mobile-user',
        name: 'Mobile User',
        conditions: [
            { field: 'device', operator: 'equals', value: 'mobile' }
        ],
        config: {
            heroVariant: 'minimal',
            showSocialProof: false // Reduce clutter on mobile
        },
        priority: 5
    }
];

class PersonalizationEngine {
    private userData: Record = {};

    setUserData(data: Record): void {
        this.userData = { ...this.userData, ...data };
    }

    private evaluateCondition(condition: SegmentCondition): boolean {
        const value = this.userData[condition.field];

        switch (condition.operator) {
            case 'equals':
                return value === condition.value;
            case 'contains':
                return Array.isArray(value)
                    ? value.includes(condition.value)
                    : String(value).includes(condition.value);
            case 'gt':
                return Number(value) > condition.value;
            case 'lt':
                return Number(value) < condition.value;
            case 'between':
                return Number(value) >= condition.value[0] &&
                       Number(value) <= condition.value[1];
            default:
                return false;
        }
    }

    private matchesSegment(segment: UserSegment): boolean {
        return segment.conditions.every(condition =>
            this.evaluateCondition(condition)
        );
    }

    getConfig(): PersonalizationConfig {
        // Find all matching segments
        const matchingSegments = userSegments
            .filter(segment => this.matchesSegment(segment))
            .sort((a, b) => b.priority - a.priority);

        // Merge configs with priority
        let config = { ...defaultConfig };

        matchingSegments.reverse().forEach(segment => {
            config = { ...config, ...segment.config };
        });

        return config;
    }

    getMatchingSegments(): UserSegment[] {
        return userSegments.filter(segment => this.matchesSegment(segment));
    }
}

// React Context for personalization
const PersonalizationContext = createContext<{
    config: PersonalizationConfig;
    segments: UserSegment[];
    updateUserData: (data: Record) => void;
}>({
    config: defaultConfig,
    segments: [],
    updateUserData: () => {}
});

export function PersonalizationProvider({ children }: { children: React.ReactNode }) {
    const [engine] = useState(() => new PersonalizationEngine());
    const [config, setConfig] = useState(defaultConfig);
    const [segments, setSegments] = useState([]);

    const updateUserData = useCallback((data: Record) => {
        engine.setUserData(data);
        setConfig(engine.getConfig());
        setSegments(engine.getMatchingSegments());
    }, [engine]);

    // Initialize with basic data
    useEffect(() => {
        const isMobile = /Mobile|Android|iPhone/.test(navigator.userAgent);
        updateUserData({
            device: isMobile ? 'mobile' : 'desktop',
            viewport: window.innerWidth
        });
    }, [updateUserData]);

    return (
        
            {children}
        
    );
}

export function usePersonalizationConfig() {
    return useContext(PersonalizationContext);
}

// Personalized component wrapper
export function PersonalizedContent({
    children,
    segment
}: {
    children: React.ReactNode;
    segment?: string;
}) {
    const { segments } = usePersonalizationConfig();

    if (segment && !segments.some(s => s.id === segment)) {
        return null;
    }

    return <>{children};
}

// Example usage: Personalized Hero
function PersonalizedHero() {
    const { config } = usePersonalizationConfig();

    const heroComponents = {
        minimal: MinimalHero,
        featured: FeaturedHero,
        carousel: CarouselHero
    };

    const HeroComponent = heroComponents[config.heroVariant];

    return (
        
{config.showSocialProof && } {config.urgencyMessages && }
); }

A/B Testing Integration

Personalization should be continuously optimized through A/B testing. According to Harvard Business Review, online experiments are essential for validating personalization hypotheses. Here's an integrated testing framework:

// ab-testing.ts

interface Experiment {
    id: string;
    name: string;
    variants: Variant[];
    allocation: number; // % of users in experiment
    status: 'draft' | 'running' | 'paused' | 'completed';
    startDate: Date;
    endDate?: Date;
    targetSegments?: string[];
}

interface Variant {
    id: string;
    name: string;
    weight: number; // Percentage of experiment traffic
    config: Partial;
}

interface ExperimentResult {
    variantId: string;
    sampleSize: number;
    conversions: number;
    conversionRate: number;
    revenue: number;
    confidence: number;
}

class ABTestingEngine {
    private experiments: Map = new Map();
    private assignments: Map> = new Map();

    addExperiment(experiment: Experiment): void {
        this.experiments.set(experiment.id, experiment);
    }

    private hashUserId(userId: string, experimentId: string): number {
        // Consistent hashing for deterministic assignment
        const str = `${userId}-${experimentId}`;
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            const char = str.charCodeAt(i);
            hash = ((hash << 5) - hash) + char;
            hash = hash & hash;
        }
        return Math.abs(hash) % 100;
    }

    getVariant(userId: string, experimentId: string): Variant | null {
        const experiment = this.experiments.get(experimentId);
        if (!experiment || experiment.status !== 'running') {
            return null;
        }

        // Check if user already has assignment
        const userAssignments = this.assignments.get(userId);
        if (userAssignments?.has(experimentId)) {
            const variantId = userAssignments.get(experimentId)!;
            return experiment.variants.find(v => v.id === variantId) || null;
        }

        // Determine if user is in experiment
        const userHash = this.hashUserId(userId, experimentId);
        if (userHash >= experiment.allocation) {
            return null; // User not in experiment
        }

        // Assign variant based on weights
        const variantHash = this.hashUserId(userId, `${experimentId}-variant`);
        let cumulative = 0;

        for (const variant of experiment.variants) {
            cumulative += variant.weight;
            if (variantHash < cumulative) {
                // Store assignment
                if (!this.assignments.has(userId)) {
                    this.assignments.set(userId, new Map());
                }
                this.assignments.get(userId)!.set(experimentId, variant.id);

                return variant;
            }
        }

        return experiment.variants[0]; // Fallback to first variant
    }

    // Statistical significance calculation
    calculateSignificance(
        controlConversions: number,
        controlSampleSize: number,
        treatmentConversions: number,
        treatmentSampleSize: number
    ): number {
        const controlRate = controlConversions / controlSampleSize;
        const treatmentRate = treatmentConversions / treatmentSampleSize;

        const pooledRate = (controlConversions + treatmentConversions) /
                          (controlSampleSize + treatmentSampleSize);

        const standardError = Math.sqrt(
            pooledRate * (1 - pooledRate) *
            (1/controlSampleSize + 1/treatmentSampleSize)
        );

        const zScore = (treatmentRate - controlRate) / standardError;

        // Convert z-score to confidence level (two-tailed)
        const confidence = this.normalCDF(Math.abs(zScore)) * 2 - 1;

        return confidence;
    }

    private normalCDF(x: number): number {
        const a1 =  0.254829592;
        const a2 = -0.284496736;
        const a3 =  1.421413741;
        const a4 = -1.453152027;
        const a5 =  1.061405429;
        const p  =  0.3275911;

        const sign = x < 0 ? -1 : 1;
        x = Math.abs(x) / Math.sqrt(2);

        const t = 1.0 / (1.0 + p * x);
        const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);

        return 0.5 * (1.0 + sign * y);
    }
}

// React hook for A/B testing
export function useExperiment(experimentId: string) {
    const [variant, setVariant] = useState(null);
    const engine = useABTestingEngine();
    const userId = useUserId();

    useEffect(() => {
        const assignedVariant = engine.getVariant(userId, experimentId);
        setVariant(assignedVariant);

        if (assignedVariant) {
            // Track exposure
            trackEvent('experiment_exposure', {
                experimentId,
                variantId: assignedVariant.id
            });
        }
    }, [experimentId, userId, engine]);

    const trackConversion = useCallback((value?: number) => {
        if (variant) {
            trackEvent('experiment_conversion', {
                experimentId,
                variantId: variant.id,
                value
            });
        }
    }, [experimentId, variant]);

    return { variant, trackConversion, isInExperiment: variant !== null };
}

Measuring Personalization Impact

Track the effectiveness of your personalization engine with comprehensive metrics:

// personalization-metrics.ts

interface PersonalizationMetrics {
    recommendations: RecommendationMetrics;
    segmentation: SegmentationMetrics;
    experiments: ExperimentMetrics;
    business: BusinessMetrics;
}

interface RecommendationMetrics {
    clickThroughRate: number;
    conversionRate: number;
    coverageRate: number; // % of catalog recommended
    diversityScore: number;
    noveltyScore: number;
    averagePosition: number;
}

class PersonalizationAnalytics {
    async calculateMetrics(
        dateRange: { start: Date; end: Date }
    ): Promise {
        const [
            recommendations,
            segmentation,
            experiments,
            business
        ] = await Promise.all([
            this.calculateRecommendationMetrics(dateRange),
            this.calculateSegmentationMetrics(dateRange),
            this.calculateExperimentMetrics(dateRange),
            this.calculateBusinessMetrics(dateRange)
        ]);

        return { recommendations, segmentation, experiments, business };
    }

    private async calculateRecommendationMetrics(
        dateRange: { start: Date; end: Date }
    ): Promise {
        const events = await this.fetchEvents(dateRange);

        const impressions = events.filter(e => e.type === 'recommendation_impression');
        const clicks = events.filter(e => e.type === 'recommendation_click');
        const conversions = events.filter(e => e.type === 'recommendation_conversion');

        const ctr = clicks.length / impressions.length;
        const conversionRate = conversions.length / clicks.length;

        // Calculate diversity (how varied are recommendations)
        const recommendedItems = new Set(
            impressions.map(e => e.data.itemId)
        );
        const totalItems = await this.getCatalogSize();
        const coverageRate = recommendedItems.size / totalItems;

        // Calculate novelty (are we recommending non-obvious items)
        const popularItems = await this.getPopularItems(100);
        const novelItems = [...recommendedItems].filter(
            item => !popularItems.includes(item)
        );
        const noveltyScore = novelItems.length / recommendedItems.size;

        return {
            clickThroughRate: ctr,
            conversionRate,
            coverageRate,
            diversityScore: this.calculateDiversity(impressions),
            noveltyScore,
            averagePosition: this.calculateAveragePosition(clicks)
        };
    }

    private calculateDiversity(impressions: Event[]): number {
        // Intra-list diversity using category distribution
        const sessionRecommendations = this.groupBySession(impressions);
        let totalDiversity = 0;

        sessionRecommendations.forEach(session => {
            const categories = session.map(e => e.data.category);
            const uniqueCategories = new Set(categories);
            totalDiversity += uniqueCategories.size / categories.length;
        });

        return totalDiversity / sessionRecommendations.size;
    }

    generateReport(metrics: PersonalizationMetrics): string {
        return `
# Personalization Performance Report

## Recommendation Engine
- **Click-Through Rate**: ${(metrics.recommendations.clickThroughRate * 100).toFixed(2)}%
- **Conversion Rate**: ${(metrics.recommendations.conversionRate * 100).toFixed(2)}%
- **Catalog Coverage**: ${(metrics.recommendations.coverageRate * 100).toFixed(1)}%
- **Novelty Score**: ${(metrics.recommendations.noveltyScore * 100).toFixed(1)}%

## Business Impact
- **Revenue from Recommendations**: $${metrics.business.recommendationRevenue.toLocaleString()}
- **Revenue Lift vs Control**: ${metrics.business.revenueLift}%
- **Average Order Value**: $${metrics.business.averageOrderValue.toFixed(2)}
- **Customer Lifetime Value Impact**: +${metrics.business.clvImpact}%

## Segment Performance
${metrics.segmentation.segments.map(s => `
### ${s.name}
- Users: ${s.userCount.toLocaleString()}
- Conversion Rate: ${(s.conversionRate * 100).toFixed(2)}%
- Revenue: $${s.revenue.toLocaleString()}
`).join('')}

## Active Experiments
${metrics.experiments.active.map(e => `
### ${e.name}
- Status: ${e.status}
- Leading Variant: ${e.leadingVariant}
- Confidence: ${(e.confidence * 100).toFixed(1)}%
- Estimated Impact: ${e.estimatedImpact}%
`).join('')}
        `;
    }
}

Key Takeaways

Remember These Points

  • Start with data collection: Comprehensive behavior tracking is the foundation of effective personalization
  • Combine approaches: Hybrid systems using collaborative filtering, content-based, and contextual signals outperform single approaches
  • Use TensorFlow.js for privacy: Client-side models protect user data while enabling real-time personalization
  • Segment thoughtfully: Create meaningful user segments with clear rules and priority ordering
  • Always A/B test: Personalization hypotheses must be validated with statistical rigor
  • Measure holistically: Track both engagement metrics (CTR) and business outcomes (revenue, CLV)
  • Balance relevance and discovery: Avoid filter bubbles by maintaining diversity in recommendations

Conclusion

AI-driven personalization transforms web applications from static experiences into intelligent systems that understand and adapt to each user. The implementation patterns covered in this guide provide a foundation for building personalization engines that can realistically achieve the 35%+ conversion improvements seen in industry leaders.

Start with solid behavior tracking, implement collaborative filtering for quick wins, then graduate to neural approaches with TensorFlow.js for sophisticated real-time personalization. For deeper learning, explore Google's Recommendation Systems course and the Neural Collaborative Filtering paper.

The key to success isn't just the algorithms; it's building a culture of continuous experimentation and optimization around your personalization stack. Tools like Segment for data collection and LaunchDarkly for feature flags can accelerate your personalization journey.