Technical Guide

Loading Personalized Content in the Browser with Usertune

Learn how to implement client-side personalization with Usertune's JavaScript library. Complete guide to CDN setup, dynamic content loading, and real-time user experiences.

RI

Technical Guide Expert

• 5 min read

JavaScript Browser Frontend Personalization CDN

Creating personalized user experiences directly in the browser has never been easier. Usertune's JavaScript library provides a lightweight, fast solution for client-side personalization that works with any website or web application.

Why Browser-Side Personalization?

  • ⚡ Instant Loading - Content personalizes without server round-trips
  • 🎯 Real-Time Adaptation - Responds to user interactions immediately
  • 🌐 Universal Compatibility - Works with any website or framework
  • 📱 Device-Aware - Automatically detects device type and capabilities
  • 🔒 Privacy-Friendly - No server-side user data storage required

Quick Start with CDN

The fastest way to get started is using our CDN. No build process required:

<!DOCTYPE html>
<html>
<head>
    <title>My Personalized Site</title>
</head>
<body>
    <div id="hero-banner">Loading...</div>
    
    <!-- Include Usertune from CDN -->
    <script src="https://cdn.jsdelivr.net/npm/usertune.js@latest/dist/usertune.browser.min.js"></script>
    
    <script>
        // Initialize and load personalized content
        const client = new Usertune({
            workspace: 'your-workspace-id'
        });
        
        // Load personalized hero banner
        client.content('hero-banner').then(content => {
            document.getElementById('hero-banner').innerHTML = `
                <h1>${content.data.title}</h1>
                <p>${content.data.description}</p>
                <button onclick="trackClick()">${content.data.cta_text}</button>
            `;
        });
        
        // Track when user clicks CTA
        function trackClick() {
            client.track('cta_click');
        }
    </script>
</body>
</html>

Client Configuration

Basic Configuration

const client = new Usertune({
  workspace: 'your-workspace-id',    // Required
  timeout: 5000,                    // Faster timeout for browser use
  debug: true                       // Enable for development
});

Advanced Configuration with User Context

// Detect user context automatically
const client = new Usertune({
  workspace: 'your-workspace-id',
  timeout: 3000,
  debug: process.env.NODE_ENV === 'development'
});

// Helper function to get user context
function getUserContext() {
  return {
    device_type: window.innerWidth > 768 ? 'desktop' : 'mobile',
    viewport_width: window.innerWidth,
    time_of_day: new Date().getHours(),
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    language: navigator.language,
    referrer: document.referrer,
    page_path: window.location.pathname
  };
}

Dynamic Content Loading

Loading Content with Context

async function loadPersonalizedContent() {
  const userContext = getUserContext();
  
  try {
    const content = await client.content('homepage-banner', userContext);
    
    // Update the DOM with personalized content
    updateBanner(content.data);
    
    // Store variant ID for tracking
    window.currentVariant = content.metadata.variant_id;
    
  } catch (error) {
    console.warn('Personalization failed, using fallback:', error);
    loadFallbackContent();
  }
}

function updateBanner(data) {
  const banner = document.getElementById('hero-banner');
  banner.innerHTML = `
    <div class="hero-content">
      <h1 class="hero-title">${data.title}</h1>
      <p class="hero-description">${data.description}</p>
      <button class="cta-button" onclick="handleCTAClick()">
        ${data.cta_text}
      </button>
    </div>
  `;
  
  // Apply any dynamic styling
  if (data.theme) {
    banner.className = `hero-banner theme-${data.theme}`;
  }
}

Multiple Content Pieces

Load multiple personalized content pieces efficiently:

async function loadAllPersonalizedContent() {
  const userContext = getUserContext();
  
  try {
    // Load multiple content pieces in parallel
    const [hero, sidebar, footer] = await Promise.all([
      client.content('hero-banner', userContext),
      client.content('sidebar-promo', userContext),
      client.content('footer-cta', userContext)
    ]);
    
    // Update each section
    updateHero(hero.data);
    updateSidebar(sidebar.data);
    updateFooter(footer.data);
    
    // Store all variant IDs for tracking
    window.variants = {
      hero: hero.metadata.variant_id,
      sidebar: sidebar.metadata.variant_id,
      footer: footer.metadata.variant_id
    };
    
  } catch (error) {
    console.error('Failed to load personalized content:', error);
  }
}

Real-Time Personalization

Responsive Personalization

Update content based on user interactions:

class PersonalizationManager {
  constructor(workspaceId) {
    this.client = new Usertune({ workspace: workspaceId });
    this.initializeEventListeners();
  }
  
  initializeEventListeners() {
    // Re-personalize on window resize
    window.addEventListener('resize', this.debounce(() => {
      this.updateForViewport();
    }, 500));
    
    // Re-personalize based on scroll behavior
    window.addEventListener('scroll', this.debounce(() => {
      this.updateForScrollDepth();
    }, 1000));
  }
  
  async updateForViewport() {
    const newContext = getUserContext();
    const content = await this.client.content('responsive-banner', newContext);
    this.updateBanner(content.data);
  }
  
  async updateForScrollDepth() {
    const scrollPercent = (window.scrollY / document.body.scrollHeight) * 100;
    
    if (scrollPercent > 50) {
      const content = await this.client.content('scroll-cta', {
        ...getUserContext(),
        scroll_depth: 'deep',
        engagement_level: 'high'
      });
      this.showFloatingCTA(content.data);
    }
  }
  
  debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
      const later = () => {
        clearTimeout(timeout);
        func(...args);
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  }
}

// Initialize personalization manager
const personalizer = new PersonalizationManager('your-workspace-id');

Conversion Tracking

Basic Tracking

// Track simple conversions
async function handleCTAClick() {
  // Track the click
  await client.track('cta_click');
  
  // Proceed with your action
  window.location.href = '/signup';
}

// Track with value
async function handlePurchase(orderValue) {
  await client.track('purchase', orderValue);
  showThankYouMessage();
}

Advanced Tracking with Context

class ConversionTracker {
  constructor(client) {
    this.client = client;
    this.setupAutoTracking();
  }
  
  setupAutoTracking() {
    // Track button clicks automatically
    document.addEventListener('click', (event) => {
      if (event.target.matches('[data-track]')) {
        const trackingType = event.target.dataset.track;
        const trackingValue = event.target.dataset.value || 0;
        this.track(trackingType, parseFloat(trackingValue));
      }
    });
    
    // Track form submissions
    document.addEventListener('submit', (event) => {
      if (event.target.matches('[data-track-form]')) {
        const trackingType = event.target.dataset.trackForm;
        this.track(trackingType);
      }
    });
  }
  
  async track(type, value = 0) {
    try {
      await this.client.track(type, value);
      console.log(`Tracked: ${type}${value ? ` ($${value})` : ''}`);
    } catch (error) {
      console.error('Tracking failed:', error);
    }
  }
}

// HTML usage:
// <button data-track="newsletter_signup">Subscribe</button>
// <button data-track="purchase" data-value="29.99">Buy Now</button>
// <form data-track-form="contact_form">...</form>

Error Handling

async function loadContentSafely(contentId, attributes = {}) {
  try {
    // Try to get cached content first
    const cacheKey = `${contentId}_${JSON.stringify(attributes)}`;
    const cached = contentCache.get(cacheKey);
    if (cached) return cached;
    
    // Load fresh content
    const content = await client.content(contentId, attributes);
    contentCache.set(cacheKey, content);
    
    return content;
    
  } catch (error) {
    console.warn(`Failed to load ${contentId}:`, error);
    
    // Return fallback content
    return {
      data: getFallbackContent(contentId),
      metadata: { variant_id: null }
    };
  }
}

function getFallbackContent(contentId) {
  const fallbacks = {
    'hero-banner': {
      title: 'Welcome to Our Site',
      description: 'Discover amazing products and services',
      cta_text: 'Get Started'
    },
    'sidebar-promo': {
      title: 'Special Offer',
      description: 'Limited time deal',
      cta_text: 'Learn More'
    }
  };
  
  return fallbacks[contentId] || { title: 'Content Loading...', description: '', cta_text: 'Continue' };
}

Next Steps

Now that you have browser-side personalization set up:

  1. Test Across Devices: Verify personalization works on mobile, tablet, and desktop
  2. Monitor Performance: Use browser dev tools to track loading times
  3. Implement A/B Testing: Test different content variants with real users
  4. Add More Touchpoints: Personalize navigation, forms, and checkout flows

For server-side implementation, check out our companion guide: "Setting Up Usertune's JavaScript Library in Node.js Applications".

Ready to personalize your website? Sign up for Usertune and start delivering tailored experiences today!