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!

We use cookies to enhance your experience and analyze site usage. Learn more