Tutorial

Implementing CounterAPI in Remix Applications - 2025 Guide

A straightforward guide to adding analytics and tracking to your Remix applications using CounterAPI's streamlined solution.

MA

Tutorial Expert

• 4 min read

CounterAPI Remix Analytics React Web Development

Why CounterAPI Works Well with Remix

Remix has become a go-to framework for developers who value web fundamentals and exceptional user experiences. When it comes to analytics, CounterAPI's lightweight approach complements Remix's performance-first philosophy perfectly.

This guide will show you how to integrate CounterAPI with Remix for both client and server-side tracking in 2025.

Setting Up CounterAPI in Your Remix Project

First, install the CounterAPI package:

npm install counterapi@latest

Create a CounterAPI Utility

Let's create a utility file to manage our CounterAPI instances:

// app/utils/counter.server.js - For server-side tracking
import { Counter } from 'counterapi';

let serverCounter;

export function getCounter() {
  if (!serverCounter) {
    serverCounter = new Counter({
      workspace: process.env.COUNTER_WORKSPACE,
      accessToken: process.env.COUNTER_TOKEN,
    });
  }
  return serverCounter;
}
// app/utils/counter.client.js - For client-side tracking
import { Counter } from 'counterapi';

let clientCounter;

export function getClientCounter() {
  if (typeof window === 'undefined') return null;
  
  if (!clientCounter) {
    clientCounter = new Counter({
      workspace: process.env.COUNTER_PUBLIC_WORKSPACE,
      accessToken: process.env.COUNTER_PUBLIC_TOKEN,
    });
  }
  return clientCounter;
}

Add these environment variables to your .env file:

COUNTER_WORKSPACE=your-workspace
COUNTER_TOKEN=server-side-token
COUNTER_PUBLIC_WORKSPACE=your-workspace
COUNTER_PUBLIC_TOKEN=client-side-token

Server-Side Tracking with Remix Loaders

One of Remix's strengths is its loader pattern. Let's use it to track page views:

// app/routes/_index.jsx
import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { getCounter } from '~/utils/counter.server';

export async function loader() {
  const counter = getCounter();
  
  try {
    // Track page view
    const viewResult = await counter.up('home-page-view');
    
    return json({
      pageViews: viewResult.value,
    });
  } catch (error) {
    // Don't let tracking errors affect the page
    console.error('Analytics error:', error);
    
    return json({
      pageViews: 'unavailable',
    });
  }
}

export default function Index() {
  const { pageViews } = useLoaderData();
  
  return (
    <div>
      <h1>Welcome to my Remix app</h1>
      <p>This page has been viewed {pageViews} times.</p>
      {/* Rest of your component */}
    </div>
  );
}

Client-Side Tracking with useEffect

For client-side events like button clicks:

// app/components/FeatureCard.jsx
import { useState, useEffect } from 'react';
import { getClientCounter } from '~/utils/counter.client';

export default function FeatureCard({ featureId, title, description }) {
  const [expanded, setExpanded] = useState(false);
  
  const handleExpand = () => {
    setExpanded(!expanded);
    
    // Track when users expand a feature card
    const counter = getClientCounter();
    if (counter) {
      counter.up(`feature-${featureId}-expanded`)
        .catch(error => console.error('Tracking error:', error));
    }
  };
  
  return (
    <div className="feature-card">
      <h3>{title}</h3>
      <button onClick={handleExpand}>
        {expanded ? 'Show less' : 'Show more'}
      </button>
      
      {expanded && (
        <div className="feature-details">
          <p>{description}</p>
        </div>
      )}
    </div>
  );
}

Tracking Form Submissions

Remix's form handling is one of its standout features. Let's track form submissions:

// app/routes/contact.jsx
import { redirect, json } from '@remix-run/node';
import { Form, useActionData } from '@remix-run/react';
import { getCounter } from '~/utils/counter.server';

export async function action({ request }) {
  const formData = await request.formData();
  const email = formData.get('email');
  const message = formData.get('message');
  
  // Validate and process form data...
  const validForm = email && message && message.length > 10;
  
  // Track form submission
  const counter = getCounter();
  
  try {
    if (validForm) {
      await counter.up('contact-form-success');
      // Process the valid submission...
      return redirect('/thank-you');
    } else {
      await counter.up('contact-form-error');
      return json({ error: 'Please fill out all fields correctly.' });
    }
  } catch (error) {
    // Log but don't disrupt the user flow
    console.error('Analytics error:', error);
    
    if (validForm) {
      return redirect('/thank-you');
    } else {
      return json({ error: 'Please fill out all fields correctly.' });
    }
  }
}

export default function Contact() {
  const actionData = useActionData();
  
  return (
    <div>
      <h1>Contact Us</h1>
      
      <Form method="post">
        <div>
          <label htmlFor="email">Email</label>
          <input type="email" name="email" id="email" required />
        </div>
        
        <div>
          <label htmlFor="message">Message</label>
          <textarea name="message" id="message" required></textarea>
        </div>
        
        {actionData?.error && (
          <div className="error">{actionData.error}</div>
        )}
        
        <button type="submit">Send Message</button>
      </Form>
    </div>
  );
}

Creating a Root Analytics Tracker

To track all page views automatically, use Remix's root module:

// app/root.jsx
import { useEffect } from 'react';
import { useLocation } from '@remix-run/react';
import { getClientCounter } from '~/utils/counter.client';
import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
} from '@remix-run/react';

export default function App() {
  const location = useLocation();
  
  useEffect(() => {
    const counter = getClientCounter();
    if (!counter) return;
    
    const trackPageView = async () => {
      try {
        // Remove leading slash and replace remaining slashes with dashes
        const path = location.pathname.substring(1).replace(/\//g, '-') || 'home';
        await counter.up(`page-${path}`);
      } catch (error) {
        console.error('Page view tracking error:', error);
      }
    };
    
    trackPageView();
  }, [location.pathname]);

  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

Analytics Dashboard Resource Route

Create a resource route to fetch analytics data:

// app/routes/resources.analytics.jsx
import { json } from '@remix-run/node';
import { getCounter } from '~/utils/counter.server';

export async function loader({ request }) {
  // Check authentication/authorization here
  // This is an admin endpoint
  
  const url = new URL(request.url);
  const counters = url.searchParams.get('counters')?.split(',') || [];
  
  if (counters.length === 0) {
    return json({ error: 'No counters specified' }, { status: 400 });
  }
  
  try {
    const counter = getCounter();
    const results = {};
    
    for (const name of counters) {
      const result = await counter.get(name);
      results[name] = result.value;
    }
    
    return json({ results });
  } catch (error) {
    console.error('Analytics fetch error:', error);
    return json(
      { error: 'Failed to fetch analytics data' },
      { status: 500 }
    );
  }
}

Creating an Admin Dashboard

Use this resource route to build an admin dashboard:

// app/routes/admin.analytics.jsx
import { useEffect, useState } from 'react';
import { useLoaderData } from '@remix-run/react';

// Protect this route with authentication

export default function AnalyticsDashboard() {
  const [metrics, setMetrics] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchMetrics = async () => {
      try {
        // List of important counters to track
        const counters = [
          'home-page-view',
          'contact-form-success',
          'contact-form-error',
          'feature-1-expanded',
          'feature-2-expanded',
          'feature-3-expanded',
        ].join(',');
        
        const response = await fetch(
          `/resources/analytics?counters=${counters}`
        );
        
        if (!response.ok) {
          throw new Error('Failed to fetch analytics');
        }
        
        const data = await response.json();
        setMetrics(data.results);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchMetrics();
  }, []);
  
  if (loading) return <div>Loading analytics data...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return (
    <div className="analytics-dashboard">
      <h1>Analytics Dashboard</h1>
      
      <div className="metrics-grid">
        {Object.entries(metrics).map(([key, value]) => (
          <div key={key} className="metric-card">
            <h3>{key}</h3>
            <div className="metric-value">{value}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

Conclusion

By integrating CounterAPI with Remix, you get a powerful analytics solution that leverages Remix's best features – server loaders, actions, and client-side interactions. The combination gives you comprehensive tracking capabilities without compromising on performance or user experience.

For more information about CounterAPI and its capabilities, visit the official documentation.