Implementing CounterAPI in Remix Applications - 2025 Guide
A straightforward guide to adding analytics and tracking to your Remix applications using CounterAPI's streamlined solution.
Tutorial Expert
• 4 min read
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.