Umami Tracking Approaches | OpsBlu Docs

Umami Tracking Approaches

Compare server-side and client-side tracking approaches in Umami — performance, accuracy, and implementation trade-offs.

Server-Side vs Client-Side Tracking in Umami

Umami supports both client-side and server-side tracking approaches, each with distinct advantages and use cases. While Umami is primarily designed for client-side tracking through its JavaScript library, server-side tracking via the API enables tracking in environments where JavaScript isn't available or for backend events that have no frontend representation.

Client-Side Tracking

Client-side tracking is the default and recommended approach for most websites and web applications.

Standard Implementation:

<script async src="https://umami.example.com/script.js" data-website-id="YOUR_WEBSITE_ID"></script>

How Client-Side Tracking Works

  1. Script Loading: Umami script loads asynchronously on the page
  2. Automatic Page Views: Page views are captured automatically when pages load
  3. Event Collection: Custom events fire from user interactions
  4. Data Transmission: Data is sent to Umami server via fetch/beacon API
  5. Session Management: Sessions tracked client-side without cookies

Automatic Data Collection:

// These are captured automatically with client-side tracking:
// - Page URL
// - Page title
// - Referrer
// - Screen resolution
// - Browser language
// - Device type (mobile/desktop/tablet)
// - Operating system
// - Browser name

Advantages of Client-Side Tracking

1. Ease of Implementation

<!-- Single script tag, no backend code required -->
<script async src="https://umami.example.com/script.js" data-website-id="abc123"></script>

2. Rich Context Automatically Captured

Client-side tracking captures browser and device information without manual configuration:

// All of this is automatic:
// - User's screen size
// - Browser and OS
// - Referrer URL
// - Page title
// - Current URL

3. Real-Time User Interactions

// Track user actions as they happen
umami.track('button_click', {
  button_name: 'signup',
  location: 'header'
});

4. No Server Load

Analytics tracking happens client-side, reducing load on your application servers.

5. Simple Event Tracking

<!-- Data attributes for no-code tracking -->
<button data-umami-event="cta_click">Sign Up</button>

Limitations of Client-Side Tracking

1. Ad Blockers

Many users have ad blockers that may prevent Umami script from loading:

// Check if Umami loaded
if (!window.umami) {
  // Umami blocked or failed to load
  console.warn('Analytics not available');
}

2. JavaScript Disabled

Small percentage of users browse with JavaScript disabled.

3. Single-Page Applications Require Extra Setup

// Must manually trigger page views on route changes
router.afterEach(() => {
  if (window.umami) {
    umami.pageView();
  }
});

4. Can't Track Server-Only Events

Backend processes like cron jobs, API calls, or server-side business logic can't use client-side tracking.

Client-Side Best Practices

Async Loading:

<!-- Always use async to prevent blocking page load -->
<script async src="https://umami.example.com/script.js" data-website-id="abc123"></script>

Defensive Event Tracking:

// Always check if umami exists
function trackEvent(name, data) {
  if (window.umami) {
    umami.track(name, data);
  }
}

SPA Page View Tracking:

// React Router example
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function App() {
  const location = useLocation();

  useEffect(() => {
    if (window.umami) {
      umami.pageView();
    }
  }, [location.pathname]);

  return <Routes>{/* ... */}</Routes>;
}

Server-Side Tracking

Server-side tracking sends data directly from your backend to Umami's API.

Basic API Request:

curl -X POST "https://umami.example.com/api/send" \
  -H "Content-Type: application/json" \
  -H "User-Agent: Mozilla/5.0" \
  -d '{
    "type": "event",
    "payload": {
      "website": "YOUR_WEBSITE_ID",
      "url": "/api/checkout",
      "name": "purchase_completed",
      "data": {
        "value": 99.99,
        "currency": "USD"
      }
    }
  }'

Server-Side Implementation Examples

Node.js:

const fetch = require('node-fetch');

async function trackServerEvent(eventName, eventData) {
  try {
    const response = await fetch('https://umami.example.com/api/send', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'User-Agent': 'Mozilla/5.0 (compatible; MyApp/1.0)'
      },
      body: JSON.stringify({
        type: 'event',
        payload: {
          website: process.env.UMAMI_WEBSITE_ID,
          url: '/api/purchase',
          name: eventName,
          data: eventData
        }
      })
    });

    if (!response.ok) {
      console.error('Umami tracking failed:', response.statusText);
    }
  } catch (error) {
    console.error('Error tracking event:', error);
  }
}

// Usage
await trackServerEvent('subscription_created', {
  plan: 'enterprise',
  value: 1200,
  billing_cycle: 'annual'
});

Python:

import requests
import json

def track_server_event(event_name, event_data):
    payload = {
        "type": "event",
        "payload": {
            "website": "YOUR_WEBSITE_ID",
            "url": "/api/event",
            "name": event_name,
            "data": event_data
        }
    }

    headers = {
        "Content-Type": "application/json",
        "User-Agent": "Mozilla/5.0 (compatible; MyApp/1.0)"
    }

    try:
        response = requests.post(
            "https://umami.example.com/api/send",
            json=payload,
            headers=headers,
            timeout=5
        )
        response.raise_for_status()
    except requests.exceptions.RequestException as e:
        print(f"Error tracking event: {e}")

# Usage
track_server_event("data_export", {
    "format": "CSV",
    "row_count": 5000
})

PHP:

<?php
function trackServerEvent($eventName, $eventData) {
    $payload = [
        'type' => 'event',
        'payload' => [
            'website' => $_ENV['UMAMI_WEBSITE_ID'],
            'url' => '/api/event',
            'name' => $eventName,
            'data' => $eventData
        ]
    ];

    $ch = curl_init('https://umami.example.com/api/send');
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'User-Agent: Mozilla/5.0 (compatible; MyApp/1.0)'
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $result = curl_exec($ch);
    curl_close($ch);

    return $result;
}

// Usage
trackServerEvent('invoice_generated', [
    'invoice_id' => 'INV-12345',
    'amount' => 499.99
]);
?>

Advantages of Server-Side Tracking

1. Bypass Ad Blockers

Server-to-server communication can't be blocked by browser ad blockers.

2. Track Backend Events

// Track events that happen only on server
trackServerEvent('cron_job_completed', {
  job_name: 'daily_report',
  duration_ms: 1500,
  records_processed: 10000
});

trackServerEvent('api_call_made', {
  endpoint: '/v1/data/export',
  response_time_ms: 250
});

3. Reliable Tracking

Server-side tracking ensures events are captured even if client disconnects or navigates away.

4. Sensitive Operations

Track server-side operations without exposing logic to client:

trackServerEvent('payment_processed', {
  payment_gateway: 'stripe',
  amount: 99.99,
  currency: 'USD',
  status: 'success'
  // Don't send credit card info or other sensitive data
});

5. Accurate Conversion Tracking

Track conversions at the exact moment they're confirmed server-side:

// After successful payment processing
if (paymentSuccessful) {
  await trackServerEvent('purchase_completed', {
    order_id: order.id,
    value: order.total,
    items_count: order.items.length
  });
}

Limitations of Server-Side Tracking

1. No Automatic Browser/Device Info

Must manually provide context that's automatic in client-side tracking.

2. More Complex Implementation

Requires backend code and error handling.

3. Server Load

Analytics requests add (minimal) load to your application servers.

4. No Automatic Page Views

Must manually trigger page views if needed.

Combine both approaches for comprehensive tracking:

Client-Side for User Interactions:

// Track user behavior client-side
umami.track('add_to_cart', {
  product_id: '123',
  quantity: 2
});

Server-Side for Business Events:

// Track business events server-side
await trackServerEvent('order_fulfilled', {
  order_id: orderId,
  fulfillment_time_hours: 24,
  shipping_method: 'express'
});

Hybrid Implementation Example

Client-Side (Frontend):

// Track user initiating checkout
document.getElementById('checkout-button').addEventListener('click', () => {
  umami.track('checkout_initiated', {
    cart_value: getCartTotal()
  });
});

Server-Side (Backend):

// Track actual successful payment
app.post('/api/process-payment', async (req, res) => {
  const payment = await processPayment(req.body);

  if (payment.successful) {
    // Server-side tracking for confirmed transaction
    await trackServerEvent('payment_confirmed', {
      amount: payment.amount,
      payment_method: payment.method
    });
  }

  res.json(payment);
});

Use Case Decision Matrix

Scenario Recommended Approach Why
Page views Client-side Automatic, includes browser context
Button clicks Client-side Immediate user interaction feedback
Form submissions Client-side Track when form is submitted
Successful purchase Server-side Confirm after payment processing
API usage Server-side Backend-only event
Cron jobs Server-side No frontend component
Feature usage Client-side User interaction in UI
Data exports Server-side Backend operation
Video playback Client-side Rich interaction tracking
Email sent Server-side Backend process
File upload Hybrid Start (client), completion (server)

Testing Server-Side Tracking

Test API Endpoint:

# Test with curl
curl -X POST "https://umami.example.com/api/send" \
  -H "Content-Type: application/json" \
  -H "User-Agent: Mozilla/5.0" \
  -d '{
    "type": "event",
    "payload": {
      "website": "YOUR_WEBSITE_ID",
      "url": "/test",
      "name": "test_event",
      "data": {
        "test": true
      }
    }
  }'

Verify in Dashboard:

  1. Send test event from server
  2. Check Umami dashboard
  3. Navigate to Events tab
  4. Look for test_event
  5. Verify event data appears correctly

Best Practices

1. Use Client-Side for User Interactions

Track what users do in the browser with client-side tracking.

2. Use Server-Side for Business Logic

Track backend processes, confirmations, and system events server-side.

3. Don't Duplicate Events

Avoid tracking the same event both client-side and server-side unless intentional.

4. Handle Errors Gracefully

// Server-side tracking should never break your application
try {
  await trackServerEvent('event_name', data);
} catch (error) {
  console.error('Analytics error:', error);
  // Continue with application logic
}

5. Use Environment Variables

const UMAMI_ENDPOINT = process.env.UMAMI_ENDPOINT;
const UMAMI_WEBSITE_ID = process.env.UMAMI_WEBSITE_ID;

6. Implement Timeout

await fetch(UMAMI_ENDPOINT, {
  method: 'POST',
  headers: { /* ... */ },
  body: JSON.stringify(payload),
  signal: AbortSignal.timeout(5000) // 5 second timeout
});

7. Queue for Reliability

// Use queue for critical server-side events
const eventQueue = [];

async function trackWithQueue(eventName, eventData) {
  eventQueue.push({ eventName, eventData });
  await processQueue();
}

async function processQueue() {
  while (eventQueue.length > 0) {
    const event = eventQueue[0];
    try {
      await trackServerEvent(event.eventName, event.eventData);
      eventQueue.shift(); // Remove on success
    } catch (error) {
      console.error('Queue processing failed:', error);
      break; // Retry later
    }
  }
}

Performance Considerations

Client-Side:

  • Minimal impact: Async script loading
  • No blocking of page render
  • Events sent via efficient beacon API when available

Server-Side:

  • Fire-and-forget for non-critical events
  • Use async/background jobs for high-volume tracking
  • Implement circuit breaker pattern for failures

Summary

Choose your tracking approach based on where events originate and what data you need:

  • Client-Side: User interactions, page views, frontend behavior
  • Server-Side: Backend processes, confirmed transactions, system events
  • Hybrid: Combine both for complete picture of user journey and business operations

Both approaches respect Umami's privacy-first principles - neither requires cookies or persistent user identification across sessions.