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.
Technical Guide Expert
• 5 min read
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:
- Test Across Devices: Verify personalization works on mobile, tablet, and desktop
- Monitor Performance: Use browser dev tools to track loading times
- Implement A/B Testing: Test different content variants with real users
- 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!