Hotjar Server-Side vs Client-Side Tracking | OpsBlu Docs

Hotjar Server-Side vs Client-Side Tracking

Understanding the differences between server-side and client-side tracking in Hotjar and when to use each approach.

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

  • Single JavaScript snippet
  • No backend changes required
  • Works with any website or web app

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

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
    });
});

Frontend Template:

<!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: