Plausible Server-Side vs Client-Side | OpsBlu Docs

Plausible Server-Side vs Client-Side

Server-side vs client-side tracking approaches for Plausible Analytics. Covers implementation trade-offs, data accuracy, privacy compliance, ad blocker.

Overview

Plausible Analytics supports both client-side (JavaScript) and server-side event tracking, each with distinct advantages and use cases. Understanding when to use each approach is crucial for building a reliable analytics implementation that captures all important user interactions.

Client-side tracking is Plausible's default and most common implementation method, utilizing JavaScript in the browser to capture user interactions. Server-side tracking involves sending events from your backend servers directly to Plausible's API, bypassing the browser entirely.

Key Differences

Client-Side Tracking:

  • Events originate from the user's browser
  • Automatically captures browser metadata (user agent, screen size, referrer)
  • Subject to ad blockers and browser privacy features
  • No server infrastructure required beyond hosting static files
  • Real-time user context available

Server-Side Tracking:

  • Events originate from your backend servers
  • Immune to ad blockers and browser-based blocking
  • Requires manual IP address and user agent forwarding
  • Guaranteed event delivery for critical conversions
  • Better for sensitive operations or high-value transactions

Architecture Considerations

The choice between client-side and server-side tracking (or a hybrid approach) depends on:

  1. Data Completeness Requirements: How critical is capturing 100% of events?
  2. Technical Infrastructure: What server-side capabilities do you have?
  3. Privacy Constraints: Are there consent requirements affecting client-side tracking?
  4. Event Types: Are events user-initiated (clicks) or system-initiated (background processes)?
  5. Reliability Needs: Can you tolerate some data loss from ad blockers?

Most implementations use a hybrid approach: client-side for standard pageviews and interactions, server-side for critical conversions.

When to Use Client-Side Collection

Client-side tracking should be your default choice for most analytics needs. It's simpler to implement, requires no backend changes, and automatically captures important user context.

Ideal Use Cases

  1. Pageview Tracking: The most common analytics use case
  2. User Interaction Events: Clicks, form interactions, video plays
  3. Content Engagement: Scroll depth, time on page, article reads
  4. Navigation Tracking: Site search, menu clicks, filters
  5. Initial Implementation: Starting with Plausible for the first time
  6. Marketing Sites: Brochure sites, blogs, landing pages
  7. Front-End Heavy Applications: SPAs, progressive web apps

Advantages of Client-Side Tracking

Automatic Context Collection:

// All of this is captured automatically:
// - User agent (browser, device, OS)
// - Screen dimensions
// - Referrer URL
// - Current page URL
// - User's country (from IP, not stored)
// - Campaign parameters (UTM tags)

plausible('pageview'); // That's it!

Ease of Implementation:

<!-- Single script tag -->
<script defer data-domain="example.com" src="https://plausible.io/js/script.js"></script>

Real-Time User Context:

// Access to DOM, window, document
document.querySelector('#signup-btn').addEventListener('click', function() {
  plausible('Signup', {
    props: {
      button_text: this.textContent,
      page_section: this.closest('section').id
    }
  });
});

Limitations of Client-Side Tracking

  1. Ad Blocker Impact: 10-30% of users may have ad blockers that prevent tracking
  2. Browser Privacy Features: ITP, ETP, and other privacy features may limit tracking
  3. User Control: Users can disable JavaScript or clear cookies
  4. Network Dependency: Events may fail if user loses connection before event sends
  5. Timing Issues: Critical events during page transitions may be lost

Client-Side Implementation Examples

Basic Setup:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>My Site</title>

  <!-- Plausible client-side tracking -->
  <script defer data-domain="example.com" src="https://plausible.io/js/script.js"></script>
</head>
<body>
  <button id="cta">Sign Up</button>

  <script>
    document.getElementById('cta').addEventListener('click', function() {
      plausible('CTA Click');
    });
  </script>
</body>
</html>

Single-Page Application:

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

export function useAnalytics() {
  const location = useLocation();

  useEffect(() => {
    plausible('pageview');
  }, [location]);
}

// Usage in App component
function App() {
  useAnalytics();
  return <Routes>...</Routes>;
}

E-commerce Client-Side Tracking:

// Product page tracking
function trackProductView(product) {
  plausible('Product View', {
    props: {
      product_id: product.id,
      category: product.category,
      price_range: getPriceRange(product.price)
    }
  });
}

// Add to cart
function trackAddToCart(product) {
  plausible('Add to Cart', {
    props: {
      product_id: product.id,
      quantity: product.quantity.toString()
    }
  });
}

When to Use Server-Side Collection

Server-side tracking should be used when event reliability is critical and you cannot tolerate data loss from client-side blocking mechanisms.

Ideal Use Cases

  1. Critical Conversions: Purchase completions, subscription activations
  2. Financial Transactions: Payment confirmations, refunds, chargebacks
  3. Account Operations: User registrations, account upgrades/downgrades
  4. API-Driven Events: Webhooks, background jobs, scheduled tasks
  5. Server-Side Rendering (SSR): Next.js, Nuxt.js, or other SSR frameworks
  6. High-Value Actions: Actions worth significant revenue or business value
  7. Compliance Requirements: When client-side tracking is restricted by consent laws

Advantages of Server-Side Tracking

Guaranteed Delivery:

// No ad blockers, no browser restrictions
// If your server sends it, Plausible receives it
await sendServerSideEvent('Purchase', { order_id: '12345' });

Authoritative Data:

// Server-side data is the source of truth
// No client-side manipulation possible
const actualRevenue = order.totalAfterRefunds;
await trackPurchase(order.id, actualRevenue);

Background Events:

// Track events that happen outside user sessions
async function processSubscriptionRenewal(subscription) {
  await chargeCustomer(subscription);

  // Track renewal server-side
  await plausibleServerSide('Subscription Renewal', {
    revenue: { currency: 'USD', amount: subscription.price },
    props: { plan: subscription.planName }
  });
}

Limitations of Server-Side Tracking

  1. Manual Context: Must manually forward IP address, user agent, etc.
  2. Development Effort: Requires backend code changes
  3. IP Address Handling: Privacy concerns with IP forwarding
  4. User Agent Forwarding: Must capture and forward browser information
  5. Deduplication Complexity: Risk of double-counting with client-side events

Server-Side Implementation Examples

Node.js Implementation:

const axios = require('axios');

async function trackServerSideEvent(eventName, options = {}) {
  const {
    domain = 'example.com',
    url,
    referrer = null,
    userAgent,
    ip,
    props = {},
    revenue = null
  } = options;

  const payload = {
    name: eventName,
    url: url,
    domain: domain,
    ...(referrer && { referrer }),
    ...(props && Object.keys(props).length > 0 && {
      props: JSON.stringify(props)
    }),
    ...(revenue && {
      revenue: JSON.stringify(revenue)
    })
  };

  const headers = {
    'User-Agent': userAgent,
    'X-Forwarded-For': ip,
    'Content-Type': 'application/json'
  };

  try {
    await axios.post('https://plausible.io/api/event', payload, { headers });
    console.log(`Event tracked: ${eventName}`);
  } catch (error) {
    console.error('Failed to track event:', error.message);
  }
}

// Usage in Express route
app.post('/checkout/complete', async (req, res) => {
  const order = await processOrder(req.body);

  // Track purchase server-side
  await trackServerSideEvent('Purchase', {
    url: `${req.protocol}://${req.get('host')}/checkout/complete`,
    userAgent: req.headers['user-agent'],
    ip: req.ip,
    revenue: {
      currency: order.currency,
      amount: order.total
    },
    props: {
      order_id: order.id,
      payment_method: order.paymentMethod
    }
  });

  res.json({ success: true, orderId: order.id });
});

Python Implementation:

import requests
import json

def track_server_side_event(
    event_name,
    domain='example.com',
    url='',
    user_agent='',
    ip='',
    referrer=None,
    props=None,
    revenue=None
):
    """Track event server-side to Plausible"""

    payload = {
        'name': event_name,
        'url': url,
        'domain': domain
    }

    if referrer:
        payload['referrer'] = referrer

    if props:
        payload['props'] = json.dumps(props)

    if revenue:
        payload['revenue'] = json.dumps(revenue)

    headers = {
        'User-Agent': user_agent,
        'X-Forwarded-For': ip,
        'Content-Type': 'application/json'
    }

    try:
        response = requests.post(
            'https://plausible.io/api/event',
            json=payload,
            headers=headers,
            timeout=5
        )
        response.raise_for_status()
        print(f'Event tracked: {event_name}')
    except requests.exceptions.RequestException as e:
        print(f'Failed to track event: {e}')

# Usage in Django view
from django.http import JsonResponse
from django.views.decorators.http import require_POST

@require_POST
def complete_signup(request):
    user = create_user(request.POST)

    # Track signup server-side
    track_server_side_event(
        event_name='Signup',
        url=request.build_absolute_uri(),
        user_agent=request.META.get('HTTP_USER_AGENT', ''),
        ip=get_client_ip(request),
        props={
            'signup_method': request.POST.get('method'),
            'plan': request.POST.get('plan')
        }
    )

    return JsonResponse({'success': True, 'user_id': user.id})

def get_client_ip(request):
    """Extract client IP from request"""
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

PHP Implementation:

<?php

function trackServerSideEvent($eventName, $options = []) {
    $domain = $options['domain'] ?? 'example.com';
    $url = $options['url'] ?? '';
    $userAgent = $options['userAgent'] ?? '';
    $ip = $options['ip'] ?? '';
    $referrer = $options['referrer'] ?? null;
    $props = $options['props'] ?? [];
    $revenue = $options['revenue'] ?? null;

    $payload = [
        'name' => $eventName,
        'url' => $url,
        'domain' => $domain
    ];

    if ($referrer) {
        $payload['referrer'] = $referrer;
    }

    if (!empty($props)) {
        $payload['props'] = json_encode($props);
    }

    if ($revenue) {
        $payload['revenue'] = json_encode($revenue);
    }

    $headers = [
        'User-Agent: ' . $userAgent,
        'X-Forwarded-For: ' . $ip,
        'Content-Type: application/json'
    ];

    $ch = curl_init('https://plausible.io/api/event');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode >= 200 && $httpCode < 300) {
        error_log("Event tracked: $eventName");
    } else {
        error_log("Failed to track event: $eventName (HTTP $httpCode)");
    }
}

// Usage in checkout completion
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_POST['action'] === 'complete_purchase') {
    $order = processOrder($_POST);

    // Track purchase server-side
    trackServerSideEvent('Purchase', [
        'url' => 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
        'userAgent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
        'ip' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'],
        'revenue' => [
            'currency' => $order['currency'],
            'amount' => $order['total']
        ],
        'props' => [
            'order_id' => $order['id'],
            'payment_method' => $order['payment_method']
        ]
    ]);

    echo json_encode(['success' => true, 'order_id' => $order['id']]);
}
?>

Ruby/Rails Implementation:

require 'net/http'
require 'json'

class PlausibleServerSide
  API_ENDPOINT = 'https://plausible.io/api/event'

  def self.track_event(event_name, options = {})
    domain = options[:domain] || 'example.com'
    url = options[:url] || ''
    user_agent = options[:user_agent] || ''
    ip = options[:ip] || ''
    referrer = options[:referrer]
    props = options[:props] || {}
    revenue = options[:revenue]

    payload = {
      name: event_name,
      url: url,
      domain: domain
    }

    payload[:referrer] = referrer if referrer
    payload[:props] = props.to_json if props.any?
    payload[:revenue] = revenue.to_json if revenue

    uri = URI(API_ENDPOINT)
    request = Net::HTTP::Post.new(uri)
    request['User-Agent'] = user_agent
    request['X-Forwarded-For'] = ip
    request['Content-Type'] = 'application/json'
    request.body = payload.to_json

    begin
      response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
        http.request(request)
      end

      if response.is_a?(Net::HTTPSuccess)
        Rails.logger.info "Event tracked: #{event_name}"
      else
        Rails.logger.error "Failed to track event: #{event_name} (#{response.code})"
      end
    rescue StandardError => e
      Rails.logger.error "Error tracking event: #{e.message}"
    end
  end
end

# Usage in Rails controller
class CheckoutController < ApplicationController
  def complete
    @order = process_order(params)

    # Track purchase server-side
    PlausibleServerSide.track_event('Purchase',
      url: request.original_url,
      user_agent: request.user_agent,
      ip: request.remote_ip,
      revenue: {
        currency: @order.currency,
        amount: @order.total
      },
      props: {
        order_id: @order.id,
        payment_method: @order.payment_method
      }
    )

    render json: { success: true, order_id: @order.id }
  end
end

Configuration

Client-Side Configuration

Basic client-side setup:

<!-- Standard setup -->
<script defer data-domain="example.com" src="https://plausible.io/js/script.js"></script>

<!-- With extensions -->
<script defer
  data-domain="example.com"
  src="https://plausible.io/js/script.outbound-links.js">
</script>

<!-- Custom proxy -->
<script defer
  data-domain="example.com"
  data-api="/stats/api/event"
  src="/stats/js/script.js">
</script>

Server-Side Configuration

API endpoint configuration:

// Production endpoint
const PLAUSIBLE_API = 'https://plausible.io/api/event';

// Self-hosted endpoint
const PLAUSIBLE_API = 'https://analytics.yourdomain.com/api/event';

// Configuration object
const plausibleConfig = {
  apiEndpoint: process.env.PLAUSIBLE_API || 'https://plausible.io/api/event',
  domain: process.env.PLAUSIBLE_DOMAIN || 'example.com',
  timeout: 5000, // 5 second timeout
  retries: 2
};

Environment Variables

# .env file
PLAUSIBLE_API_ENDPOINT=https://plausible.io/api/event
PLAUSIBLE_DOMAIN=example.com
PLAUSIBLE_ENABLED=true

# For self-hosted
PLAUSIBLE_API_ENDPOINT=https://analytics.yourdomain.com/api/event

Coordination & Deduplication

When using both client-side and server-side tracking, careful coordination is required to avoid duplicate events.

Deduplication Strategies

Strategy 1: Separate Event Names

// Client-side: User initiates checkout
plausible('Checkout Started');

// Server-side: Payment confirmed
await trackServerSideEvent('Payment Confirmed', {...});

Strategy 2: Client-Side for Attempts, Server-Side for Success

// Client-side: Track button click
document.querySelector('#purchase-btn').addEventListener('click', () => {
  plausible('Purchase Attempted');
});

// Server-side: Track actual completion
app.post('/api/purchase', async (req, res) => {
  const order = await processPayment(req.body);

  if (order.success) {
    await trackServerSideEvent('Purchase', {...});
  }

  res.json(order);
});

Strategy 3: Conditional Client-Side Tracking

// Only track client-side if server-side tracking will fail
async function checkout() {
  try {
    const response = await fetch('/api/purchase', {
      method: 'POST',
      body: JSON.stringify(orderData)
    });

    // Server will handle tracking
    return await response.json();
  } catch (error) {
    // Fallback: track client-side if server request fails
    plausible('Purchase', {
      props: { fallback: 'true', error: error.message }
    });

    throw error;
  }
}

Strategy 4: Unique Identifiers

// Generate unique event ID on client
const eventId = `evt-${Date.now()}-${Math.random()}`;

// Client-side: Track with ID
plausible('Purchase', {
  props: {
    event_id: eventId,
    source: 'client'
  }
});

// Server-side: Track with same ID (for deduplication in analysis)
await trackServerSideEvent('Purchase', {
  props: {
    event_id: eventId, // Same ID
    source: 'server'
  }
});

// Later: Filter by source in analysis or deduplicate by event_id

Event Name Consistency

Keep event names identical between client and server to maintain consistent reporting:

// Define event names in shared constants
const EVENTS = {
  SIGNUP: 'Signup',
  PURCHASE: 'Purchase',
  SUBSCRIPTION: 'Subscription',
  DOWNLOAD: 'Download'
};

// Client-side
plausible(EVENTS.SIGNUP, {props: {method: 'email'}});

// Server-side
await trackServerSideEvent(EVENTS.SIGNUP, {props: {method: 'email'}});

Hybrid Implementation Pattern

// Shared event tracker that decides client vs server
async function trackEvent(eventName, options = {}, forceServerSide = false) {
  const isServerEnvironment = typeof window === 'undefined';

  if (isServerEnvironment || forceServerSide) {
    // Server-side tracking
    return await trackServerSideEvent(eventName, options);
  } else {
    // Client-side tracking
    return plausible(eventName, options);
  }
}

// Usage works in both environments
// In browser:
await trackEvent('Button Click', {props: {location: 'hero'}});

// On server:
await trackEvent('Order Processed', {
  revenue: {currency: 'USD', amount: 99.99}
});

// Force server-side from client (critical event):
await trackEvent('Purchase', {
  revenue: {currency: 'USD', amount: 99.99}
}, true); // forceServerSide = true

Validation Steps

Client-Side Validation

  1. Browser Console Check:
// Verify plausible function exists
console.log(typeof window.plausible); // Should be 'function'

// Test event
plausible('Test Event', {props: {test: 'true'}});

// Check network tab for POST to /api/event
  1. Real-Time Dashboard:
  • Open Plausible dashboard
  • Trigger client-side event
  • Verify event appears within 1-2 seconds
  1. Ad Blocker Test:
  • Enable ad blocker (uBlock Origin, AdBlock Plus)
  • Trigger event
  • Verify if event is blocked
  • If blocked, implement proxy workaround

Server-Side Validation

  1. Server Logs:
// Add logging to server-side tracking
async function trackServerSideEvent(eventName, options) {
  console.log(`Tracking server-side event: ${eventName}`, options);

  try {
    const response = await axios.post(PLAUSIBLE_API, payload, {headers});
    console.log(`Event tracked successfully: ${response.status}`);
  } catch (error) {
    console.error(`Failed to track event: ${error.message}`);
  }
}
  1. API Response Validation:
// Check Plausible API response
const response = await trackServerSideEvent('Test', {...});

// Successful response: HTTP 202 Accepted
// Failed response: HTTP 400 Bad Request (check payload format)
  1. IP and User Agent Verification:
// Verify IP and User Agent are being forwarded
console.log('IP:', req.ip);
console.log('User Agent:', req.headers['user-agent']);

// Check Plausible dashboard for correct location data

End-to-End Testing

// Test complete flow
describe('Analytics Tracking', () => {
  it('should track purchase both client and server side', async () => {
    // Simulate purchase flow
    const orderId = await initiatePurchase();

    // Verify client-side event fired
    expect(mockPlausible).toHaveBeenCalledWith('Checkout Started');

    // Complete purchase (triggers server-side event)
    const order = await completePurchase(orderId);

    // Verify server-side event was sent
    expect(plausibleApiMock).toHaveBeenCalledWith(
      expect.objectContaining({
        name: 'Purchase',
        revenue: expect.any(Object)
      })
    );
  });
});

Troubleshooting

Issue Possible Cause Solution
Server-side events not appearing Incorrect API endpoint Verify endpoint is https://plausible.io/api/event
Location data missing from server events IP not forwarded Include X-Forwarded-For header with client IP
Device/browser data missing User agent not sent Include User-Agent header from client request
Duplicate events appearing Both client and server tracking same event Use different event names or conditional tracking
Server-side API returns 400 Invalid payload format Check JSON structure matches Plausible API spec
Events tracked but no revenue data Revenue format incorrect Ensure revenue object has currency and amount
High latency from server tracking Synchronous tracking blocks request Use async/background jobs for event tracking
Server-side events show wrong country IP from proxy/load balancer Forward original client IP, not proxy IP
Self-hosted endpoint not working CORS or network issue Check firewall rules and CORS configuration
Client-side blocked by ad blocker Default Plausible domain blocked Implement custom proxy for first-party tracking
Server tracking fails silently No error handling Add try-catch and logging to track failures
Events sent but not counted Event name doesn't match goal Verify goal is configured in dashboard
Timestamp issues Server timezone misconfiguration Plausible uses request time; verify server time is correct
Rate limiting errors Too many requests Implement request batching or queuing
Authentication errors on self-hosted Missing API key Include API key header for self-hosted instances

Best Practices

1. Use Hybrid Approach

Combine client-side and server-side for optimal coverage:

// Client-side: Most events, user interactions
plausible('Product View', {...});
plausible('Add to Cart', {...});

// Server-side: Critical conversions only
await trackServerSideEvent('Purchase', {...});
await trackServerSideEvent('Subscription Created', {...});

2. Async Server-Side Tracking

Don't block user requests waiting for analytics:

// Bad: Blocking
app.post('/purchase', async (req, res) => {
  const order = await processOrder(req.body);
  await trackServerSideEvent('Purchase', {...}); // Blocks response!
  res.json(order);
});

// Good: Non-blocking
app.post('/purchase', async (req, res) => {
  const order = await processOrder(req.body);

  // Track in background
  trackServerSideEvent('Purchase', {...}).catch(err => {
    console.error('Analytics error:', err);
  });

  res.json(order); // Don't wait for analytics
});

// Better: Queue-based
app.post('/purchase', async (req, res) => {
  const order = await processOrder(req.body);

  // Add to queue
  analyticsQueue.add({
    event: 'Purchase',
    options: {...}
  });

  res.json(order);
});

3. Privacy-Compliant IP Forwarding

Be careful with IP addresses:

// Hash or anonymize if required
function getAnonymizedIp(fullIp) {
  // Remove last octet for IPv4
  const parts = fullIp.split('.');
  parts[3] = '0';
  return parts.join('.');
}

await trackServerSideEvent('Event', {
  ip: getAnonymizedIp(req.ip)
});

4. Consistent Event Naming

Use constants to ensure consistency:

// constants/analytics.js
export const EVENTS = {
  SIGNUP: 'Signup',
  PURCHASE: 'Purchase',
  DOWNLOAD: 'Download'
};

// Use everywhere
import { EVENTS } from './constants/analytics';

// Client
plausible(EVENTS.SIGNUP);

// Server
await trackServerSideEvent(EVENTS.SIGNUP);

5. Error Handling

Always handle tracking errors gracefully:

async function trackServerSideEvent(eventName, options) {
  try {
    await axios.post(PLAUSIBLE_API, payload, {
      headers,
      timeout: 5000
    });
  } catch (error) {
    // Log but don't throw
    console.error('Analytics tracking failed:', {
      event: eventName,
      error: error.message
    });

    // Optional: Send to error monitoring
    Sentry.captureException(error);

    // Don't let analytics failures break app functionality
  }
}

6. Testing Strategy

Test both approaches:

// Mock client-side tracking
window.plausible = jest.fn();

// Mock server-side API
const mockAxios = jest.spyOn(axios, 'post');

// Test
test('tracks purchase correctly', async () => {
  await purchase();

  expect(window.plausible).toHaveBeenCalledWith('Checkout Started');
  expect(mockAxios).toHaveBeenCalledWith(
    'https://plausible.io/api/event',
    expect.objectContaining({ name: 'Purchase' })
  );
});

7. Performance Monitoring

Track analytics performance:

async function trackServerSideEvent(eventName, options) {
  const startTime = Date.now();

  try {
    await axios.post(PLAUSIBLE_API, payload, {headers});

    const duration = Date.now() - startTime;
    if (duration > 1000) {
      console.warn(`Slow analytics tracking: ${duration}ms for ${eventName}`);
    }
  } catch (error) {
    console.error('Analytics error:', error);
  }
}

8. Feature Flags

Use feature flags to control tracking:

async function trackEvent(eventName, options) {
  if (!config.analyticsEnabled) {
    return; // Skip in development
  }

  if (config.serverSideTracking) {
    await trackServerSideEvent(eventName, options);
  } else {
    plausible(eventName, options);
  }
}

9. Documentation

Document which events use which method:

/**
 * Analytics Event Catalog
 *
 * Client-Side Events:
 * - Product View (user interaction)
 * - Add to Cart (user interaction)
 * - Search (user interaction)
 *
 * Server-Side Events:
 * - Purchase (critical conversion)
 * - Signup (critical conversion)
 * - Subscription Created (backend process)
 *
 * Hybrid Events (tracked both places):
 * - None (avoided for deduplication)
 */

10. Graceful Degradation

Ensure analytics failures don't break functionality:

function trackSafely(eventName, options = {}) {
  try {
    if (typeof plausible === 'function') {
      plausible(eventName, options);
    }
  } catch (error) {
    // Silently fail
    console.warn('Analytics error:', error.message);
  }
}

// Use wrapper instead of direct calls
trackSafely('Button Click', {props: {location: 'hero'}});