Overview
Hotjar is fundamentally a client-side behavior analytics tool, meaning it operates primarily in the user's browser to capture visual interactions, mouse movements, clicks, and scrolls. However, certain aspects of Hotjar can be complemented with server-side logic for enhanced tracking and user identification.
This guide explains:
- How Hotjar's client-side tracking works
- When and how to use server-side techniques
- Hybrid approaches for robust implementation
- Limitations and tradeoffs
Client-Side Tracking (Primary Method)
What is Client-Side Tracking?
Client-side tracking happens in the user's browser. JavaScript code executes on the user's device to:
- Capture DOM snapshots for session recordings
- Track mouse movements, clicks, and scrolls
- Generate heatmap data
- Display surveys and feedback widgets
- Send data to Hotjar servers
How Hotjar Uses Client-Side Tracking
Core Functionality:
User visits page
↓
Hotjar JavaScript loads in browser
↓
Captures user interactions (clicks, scrolls, etc.)
↓
Records DOM state and events
↓
Sends data to Hotjar servers
↓
Displayed in Hotjar dashboard
What Gets Tracked:
- Session Recordings: Full visual replay of user sessions
- Heatmaps: Click, move, and scroll maps
- Form Interactions: Field focus, input, abandonment
- Custom Events: Triggered via
hj('event', 'event_name') - User Identification: via
hj('identify', userId, attributes)
Advantages of Client-Side Tracking
Visual Insights
- Session replays show exactly what users see and do
- Heatmaps visualize engagement on actual page elements
- Captures user experience (frustration, confusion, delight)
Easy Implementation
Real-Time Behavior Capture
- No server-side latency
- Captures client-side errors and issues
- Tracks interactions even before page navigation
Rich Context
- Full page context (viewport, scroll position, element visibility)
- Device and browser information
- Mouse movement and rage clicks
Limitations of Client-Side Tracking
Ad Blockers & Privacy Tools
- ~20-30% of users block tracking scripts
- Data is incomplete if Hotjar is blocked
- Privacy-focused browsers (Brave, Firefox with strict settings) may interfere
Performance Impact
- JavaScript adds to page weight (~100KB compressed)
- Recording can consume browser resources
- May slow down low-powered devices
No Tracking Before Page Load
- Can't track server-side logic or pre-rendering decisions
- Misses events that happen before script loads
Data Sampling Required
- Recording 100% of high-traffic sites can exhaust quota
- Must sample sessions to manage costs
Privacy & Compliance Challenges
- Requires user consent in many jurisdictions
- Must suppress sensitive form fields
- Limited control once JavaScript loads
Server-Side Tracking (Supplemental)
What is Server-Side Tracking?
Server-side tracking happens on your backend servers before the page is sent to the user's browser. It involves:
- Logging user attributes and actions on your server
- Conditionally loading Hotjar based on server logic
- Pre-identifying users before page renders
- Enriching data with server-side context
Hotjar Doesn't Have a Traditional Server-Side SDK
Unlike platforms like Segment, Mixpanel, or Google Analytics, Hotjar does not offer a server-side SDK for tracking events or sending session data from your backend.
Why?
- Hotjar's core value is visual behavior tracking
- Session recordings require browser DOM access
- Heatmaps and mouse tracking need client-side execution
When to Use Server-Side Logic with Hotjar
Even though Hotjar is client-side, server-side logic can enhance your implementation:
1. Conditional Script Loading
Use Case: Only load Hotjar for specific user segments or pages
Implementation:
<?php
// PHP example - only load Hotjar for logged-in users
if ($user->isLoggedIn()) {
$hotjarId = '1234567';
} else {
$hotjarId = null; // Don't load Hotjar for guests
}
?>
<?php if ($hotjarId): ?>
<script>
(function(h,o,t,j,a,r){
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
h._hjSettings={hjid:<?php echo $hotjarId; ?>,hjsv:6};
a=o.getElementsByTagName('head')[0];
r=o.createElement('script');r.async=1;
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
a.appendChild(r);
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
</script>
<?php endif; ?>
Benefits:
- Reduce tracking overhead for non-critical segments
- Respect user roles (don't track admins/internal users)
- Control quota usage
2. Pre-Identifying Users
Use Case: Pass user attributes from backend to client-side Hotjar
Implementation:
// Server renders user data into page
<script>
window.currentUser = {
id: '<?php echo $user->id; ?>',
email: '<?php echo $user->email; ?>',
plan: '<?php echo $user->plan; ?>',
signupDate: '<?php echo $user->signupDate; ?>'
};
</script>
// After Hotjar loads, identify user
<script>
if (window.currentUser && window.hj) {
hj('identify', window.currentUser.id, {
email: window.currentUser.email,
plan: window.currentUser.plan,
signup_date: window.currentUser.signupDate
});
}
</script>
Benefits:
- Accurate user attribution from server data
- No client-side API calls needed
- Consistent user ID across sessions
3. Environment-Specific Configuration
Use Case: Use different Hotjar Site IDs for dev, staging, and production
Implementation:
# Python/Django example
from django.conf import settings
hotjar_id = settings.HOTJAR_SITE_ID # Different per environment
context = {
'hotjar_id': hotjar_id
}
return render(request, 'template.html', context)
<!-- In template -->
{% if hotjar_id %}
<script>
(function(h,o,t,j,a,r){
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
h._hjSettings={hjid:{{ hotjar_id }},hjsv:6};
// ... rest of tracking code
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
</script>
{% endif %}
Benefits:
- Keep staging data separate from production
- Easy to disable in development
- Configuration managed in one place
4. Server-Side Event Triggering
Use Case: Trigger client-side events based on server-side logic
Implementation:
<?php
// Server determines if user completed a key action
$userCompletedOnboarding = $user->hasCompletedOnboarding();
?>
<script>
<?php if ($userCompletedOnboarding): ?>
// Fire Hotjar event on page load
if (window.hj) {
hj('event', 'onboarding_completed');
} else {
// Wait for Hotjar to load
window.addEventListener('load', function() {
if (window.hj) {
hj('event', 'onboarding_completed');
}
});
}
<?php endif; ?>
</script>
Benefits:
- Track server-determined states
- Avoid client-side API calls to check status
- Reduce client-side logic complexity
Hybrid Approach (Recommended)
The most robust Hotjar implementation combines client-side tracking with server-side support.
Architecture
Server Side Client Side
───────────── ─────────────
User Authentication Hotjar Script Loads
↓ ↓
Determine User Attributes Captures Interactions
↓ ↓
Render User Data in Page Identifies User with Server Data
↓ ↓
Conditionally Load Hotjar Tracks Custom Events
↓ ↓
Serve Page to Browser Sends Data to Hotjar
Implementation Example
Backend (Node.js/Express):
app.get('/dashboard', async (req, res) => {
const user = await getUserFromSession(req);
// Determine Hotjar configuration
const hotjarConfig = {
siteId: process.env.HOTJAR_SITE_ID,
shouldLoad: user && !user.isAdmin, // Don't track admins
userId: user ? user.id : null,
userAttributes: user ? {
email: user.email,
plan: user.subscription.plan,
accountAge: user.accountAgeDays,
lifetimeValue: user.ltv
} : null
};
res.render('dashboard', {
user,
hotjarConfig
});
});
<!DOCTYPE html>
<html>
<head>
<title>Dashboard</title>
<!-- Conditionally load Hotjar -->
<% if (hotjarConfig.shouldLoad) { %>
<script>
(function(h,o,t,j,a,r){
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
h._hjSettings={hjid:<%= hotjarConfig.siteId %>,hjsv:6};
a=o.getElementsByTagName('head')[0];
r=o.createElement('script');r.async=1;
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
a.appendChild(r);
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
// Identify user once Hotjar loads
<% if (hotjarConfig.userId) { %>
window.addEventListener('load', function() {
if (window.hj) {
hj('identify', '<%= hotjarConfig.userId %>', <%= JSON.stringify(hotjarConfig.userAttributes) %>);
}
});
<% } %>
</script>
<% } %>
</head>
<body>
<!-- Your dashboard content -->
<script>
// Client-side event tracking
document.getElementById('export-button').addEventListener('click', function() {
if (window.hj) {
hj('event', 'export_button_clicked');
}
});
</script>
</body>
</html>
Benefits of Hybrid Approach
Best of Both Worlds
- Visual tracking from client-side
- Reliable user identification from server-side
- Conditional loading based on server logic
Reduced Client-Side Complexity
- User data already available (no client API calls)
- Server handles authentication and authorization
- Cleaner separation of concerns
Better Performance
- Avoid loading Hotjar for admin users
- Reduce unnecessary tracking
- Optimize for critical user segments
Enhanced Security
- Sensitive logic stays on server
- User attributes validated server-side
- Reduced exposure of tracking configuration
Server-Side Alternatives for Hotjar-Like Functionality
While Hotjar doesn't offer server-side tracking, you can achieve some overlapping goals with server-side tools:
1. Server Logs for Event Tracking
Use Case: Track events that happen server-side (API calls, background jobs, email sends)
Tools:
- Custom logging infrastructure
- Data warehouses (Snowflake, BigQuery)
- Analytics platforms with server SDKs (Mixpanel, Amplitude, Segment)
Implementation:
// Server-side event logging
const analytics = require('./analytics');
app.post('/api/checkout', async (req, res) => {
// Process checkout
const result = await processCheckout(req.body);
// Log server-side event
analytics.track({
userId: req.user.id,
event: 'Checkout Completed',
properties: {
orderId: result.orderId,
revenue: result.total,
items: result.items.length
}
});
res.json(result);
});
Why This Doesn't Replace Hotjar:
- No session recordings
- No heatmaps
- No visual behavior insights
2. Server-Side Rendering with Injected Events
Use Case: Pre-render pages with event markers for client-side tracking
Implementation:
// Server determines page state
const userJustCompletedPurchase = await checkRecentPurchase(userId);
// Inject event trigger into page
res.render('thank-you', {
shouldFirePurchaseEvent: userJustCompletedPurchase
});
<!-- In rendered page -->
<script>
<% if (shouldFirePurchaseEvent) { %>
if (window.hj) {
hj('event', 'purchase_completed');
}
<% } %>
</script>
Benefits:
- Server logic drives client-side tracking
- Reliable, no race conditions
- Works with Hotjar's client-side model
Privacy & Compliance Considerations
Client-Side Privacy
Hotjar's Built-In Privacy:
- Automatically suppresses sensitive form fields (password, credit card)
- Respects Do Not Track headers (optional)
- Allows IP anonymization
- Supports consent mode
Your Responsibilities:
- Implement cookie consent banners
- Update privacy policy
- Provide opt-out mechanism
- Delete user data on request
Server-Side Privacy Controls
Use Server Logic to Enforce Privacy:
// Don't load Hotjar if user opted out
const userHasOptedOut = req.cookies.tracking_opt_out === 'true';
res.render('page', {
loadHotjar: !userHasOptedOut
});
GDPR Compliance:
// Only load Hotjar if consent granted
const hasConsent = req.cookies.cookie_consent === 'analytics';
res.render('page', {
loadHotjar: hasConsent
});
Decision Framework: Client vs Server
| Requirement | Client-Side | Server-Side | Hybrid |
|---|---|---|---|
| Session recordings | |||
| Heatmaps | |||
| Custom events | (via injection) | ||
| User identification | (better) | (best) | |
| Conditional loading | |||
| Ad blocker resistant | |||
| Performance | |||
| Privacy controls | |||
| Server event tracking |
Legend:
- Fully supported
- Partially supported or tradeoffs
- Not supported
Best Practices Summary
Client-Side
Do:
- Load Hotjar asynchronously
- Use for visual behavior tracking
- Implement consent management
- Test cross-browser
- Monitor performance impact
Don't:
- Block page rendering on Hotjar
- Track every single interaction
- Ignore ad blocker impact
- Forget about privacy regulations
Server-Side
Do:
- Use for user identification
- Conditionally load based on user role
- Manage environment-specific config
- Inject events based on server state
- Enforce privacy controls
Don't:
- Expect server-side session recordings
- Try to replace client-side tracking entirely
- Hardcode configuration
- Ignore security best practices
Hybrid
Do:
- Combine strengths of both approaches
- Identify users with server data
- Load conditionally from server
- Track events from client
- Maintain separation of concerns
Don't:
- Over-complicate implementation
- Duplicate logic across client/server
- Forget to test end-to-end
Next Steps:
Additional Resources: