Simple Event Tracking for Websites: A Developer's Guide

You shipped your product. Traffic is coming in. But you have no idea what users actually do once they land on the page. So you reach for Google Tag Manager, spend two hours wrestling with triggers and variables, accidentally fire the same event three times, and give up.

There is a better way. Simple event tracking for websites does not require an enterprise analytics stack. It requires knowing which events matter, a lightweight SDK, and five minutes.

This guide covers exactly that.

The Problem with "Enterprise" Event Tracking

Most analytics tooling was designed for companies with dedicated data teams. The setup reflects it.

Google Tag Manager gives you a visual interface for managing tracking scripts. In theory, it is a no-code solution. In practice, you end up debugging data layer pushes, fighting tag sequencing, dealing with container versioning, and wondering why your click trigger fires on hover. GTM is a deployment mechanism for tags, not an analytics solution. For a solo developer with one site and ten things worth tracking, it is massive overkill.

Segment solves a real problem: routing event data to multiple destinations. If you need the same click event in Amplitude, Mixpanel, your data warehouse, and your CRM, Segment is the right call. If you are an indie hacker with a SaaS and zero downstream integrations, Segment's $120/month starting price (and the overhead of maintaining a tracking plan) is hard to justify.

Plausible and Fathom are excellent for privacy-focused pageview analytics. They are simple, beautiful, and GDPR-friendly. But they were built primarily for aggregate traffic stats. Custom event tracking in Plausible requires manual plausible() calls and has a limited property model. Funnel analysis is not part of the core product.

What developers actually need is something in between: custom event tracking that is trivially easy to set up, gives you funnel visibility, and costs less than your domain name. Simple event tracking for websites should mean writing one line of code and getting back actionable data.

What Events Actually Matter

Before writing any tracking code, you need to decide what to track. The instinct is to track everything. Resist it. More events means more noise, more storage, and more time staring at dashboards without learning anything.

For most websites and SaaS products, the events that matter fall into three categories:

Acquisition Events

How did users arrive and what did they see first?

  • page_view -- Which pages get traffic. This should be automatic; you should not have to write code for it.
  • utm_arrival -- If you run campaigns, capture the UTM parameters on first visit.

Activation Events

Did users do the thing you want them to do?

  • signup_click -- They clicked the signup button.
  • signup_complete -- They actually finished the signup flow.
  • first_project_created -- They completed the first meaningful action in your product.

Revenue Events

Did it convert?

  • pricing_view -- They looked at the pricing page.
  • checkout_start -- They initiated payment.
  • subscription_activated -- Money moved.

That is nine events. For most indie products, nine events give you a complete picture of the funnel from landing to revenue. You can always add more later, but starting with a focused set means you actually look at the data instead of drowning in it.

The Minimum Viable Tracking Setup

Here is the simplest possible event tracking implementation using vanilla JavaScript. No libraries, no build tools, no dependencies.

function track(eventName, properties = {}) {
  fetch("https://your-analytics-endpoint.com/collect", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      event: eventName,
      properties: {
        url: window.location.pathname,
        referrer: document.referrer,
        timestamp: new Date().toISOString(),
        ...properties,
      },
    }),
    keepalive: true,
  });
}

// Track a button click
document.querySelector("#signup-btn").addEventListener("click", () => {
  track("signup_click", { plan: "indie" });
});

This works. It sends events to an endpoint. But it has real problems:

  • No batching. Every event fires a network request immediately.
  • No session management. You cannot tie multiple events to the same user session.
  • No identity stitching. If an anonymous user signs up, you lose the connection between their pre-signup and post-signup activity.
  • No sendBeacon fallback. Events fired on page unload get dropped.
  • No SPA support. Client-side route changes do not trigger page views.

You could solve each of these problems yourself. Or you could use a library that already did.

Implementing with EasyFunnel SDK

EasyFunnel is a funnel analytics platform built for developers. The SDK handles session management, event batching, sendBeacon transport, SPA navigation tracking, and identity stitching out of the box. It is under 11KB.

Option 1: Script Tag (Zero Config)

The fastest path. One line of HTML in your <head>:

<script
  src="https://easyfunnel.co/sdk.js"
  data-api-key="ef_your_project_key"
></script>

That is it. With this single tag, you get:

  • Automatic page_view events on every navigation (including SPA route changes)
  • Session tracking with a 30-minute inactivity timeout
  • data-ef-track attribute support for declarative click tracking (more on this below)
  • UTM attribution captured automatically on first visit
  • Event batching (10 events or 5 seconds, whichever comes first)
  • sendBeacon transport so events survive page unloads

You can enable additional modules with data attributes:

<script
  src="https://easyfunnel.co/sdk.js"
  data-api-key="ef_your_project_key"
  data-web-vitals
  data-form-tracking
  data-error-tracking
></script>

After loading the script tag, you can fire custom events from anywhere using the global window.easyfunnel object:

window.easyfunnel.track("checkout_start", { plan: "indie" });
window.easyfunnel.identify("user_123");

Option 2: NPM Package

For bundled applications, install the SDK:

npm install @easyfunnel/sdk

Then initialize it once at your application entry point:

import { EasyFunnel } from "@easyfunnel/sdk";

const tracker = EasyFunnel.init({
  apiKey: process.env.NEXT_PUBLIC_EASYFUNNEL_KEY,
  debug: process.env.NODE_ENV === "development",
  formTracking: true,
  errorTracking: true,
});

// Track custom events anywhere in your app
tracker.track("signup_click", { plan: "indie" });

// Identify users after authentication
tracker.identify("user_123");

// Set persistent properties that attach to every subsequent event
tracker.setProperties({ plan: "indie", company_size: "solo" });

The debug: true option logs every event to the console during development so you can verify your tracking without opening a network tab.

Option 3: React Bindings

If you are building with React (or Next.js, Remix, etc.), the @easyfunnel/react package provides a context provider and hooks:

npm install @easyfunnel/react

Wrap your app with the provider:

// app/layout.tsx (Next.js) or App.tsx
import { EasyFunnelProvider } from "@easyfunnel/react";

export default function RootLayout({ children }) {
  return (
    <EasyFunnelProvider apiKey={process.env.NEXT_PUBLIC_EASYFUNNEL_KEY}>
      {children}
    </EasyFunnelProvider>
  );
}

Then use the useTrack hook in any component:

import { useTrack, useIdentify } from "@easyfunnel/react";

function PricingCard({ plan }) {
  const track = useTrack();
  const identify = useIdentify();

  const handleCheckout = () => {
    track("checkout_start", { plan: plan.name, price: plan.price });
    // proceed to checkout...
  };

  return (
    <div>
      <h3>{plan.name}</h3>
      <button onClick={handleCheckout}>Subscribe</button>
    </div>
  );
}

The hooks return stable callback references, so they will not cause unnecessary re-renders. If you call useTrack() outside of an EasyFunnelProvider, it logs a warning in development and silently no-ops in production. No crashes.

For a full walkthrough of integrating EasyFunnel with a Next.js project, see our guide on adding analytics to Next.js.

Advanced: Data Attributes for Zero-Code Tracking

The most underused feature of simple event tracking for websites is declarative tracking via HTML attributes. Instead of writing addEventListener callbacks for every button, you annotate your markup:

<button data-ef-track="signup_click">Start Free Trial</button>

<a href="/pricing" data-ef-track="pricing_link_click">View Pricing</a>

<form data-ef-track="contact_form_submit">
  <!-- form fields -->
  <button type="submit">Send</button>
</form>

The EasyFunnel SDK listens for clicks anywhere on the page and walks up to three ancestor elements looking for a data-ef-track attribute. When it finds one, it fires the event with the element's text content and the current URL as properties.

This approach has real advantages:

  • No JavaScript required per element. Add tracking by editing HTML, not wiring up event listeners.
  • Works with server-rendered content. No need to wait for a framework to hydrate.
  • Survives refactors. Moving a button from one component to another does not break tracking, because the tracking is on the element itself.
  • Readable. Anyone scanning the HTML can see exactly what gets tracked.

In a React or JSX context, it works the same way:

function Hero() {
  return (
    <section>
      <h1>Ship faster with funnel analytics</h1>
      <a href="/signup" data-ef-track="hero_cta_click">
        Get Started Free
      </a>
    </section>
  );
}

No hook import needed. No callback wiring. The SDK handles it globally.

AI-Powered Auto-Instrumentation

Here is where things get interesting. EasyFunnel ships an MCP server that lets AI assistants like Claude interact with your analytics directly. But it also works in reverse: you can tell Claude to set up tracking for you.

Point Claude at your codebase and say: "Add EasyFunnel tracking to every button, form, and CTA in this project." It will scan your components, identify every interactive element worth tracking, and add data-ef-track attributes with sensible event names. A task that would take an hour of manual work becomes a two-minute conversation.

This is not theoretical. The @easyfunnel/mcp package exposes tools for listing projects, querying events, creating funnels, and more. AI assistants can not only set up your tracking but also analyze the data and suggest funnel optimizations. For a deeper look at the AI-first approach, check out our post on analytics for indie hackers.

Comparison: What You Get vs. the Alternatives

| Feature | EasyFunnel | Google Tag Manager | Segment | Plausible | |---|---|---|---|---| | Setup time | 1 minute | 30+ minutes | 15+ minutes | 5 minutes | | Custom events | Yes | Yes (via data layer) | Yes | Limited | | Funnel analysis | Built-in | Needs GA4 | Needs destination | No | | Session tracking | Automatic | Manual config | Automatic | No | | Identity stitching | Automatic | No | Yes | No | | SPA support | Automatic | Plugin required | Automatic | Manual | | Data attributes | data-ef-track | No | No | No | | AI integration (MCP) | Yes | No | No | No | | Price for indie use | $5/month | Free (with GA4 lock-in) | $120+/month | $9/month | | Privacy-friendly | Yes | No (Google tracking) | Depends on destinations | Yes |

Putting It All Together

A complete, production-ready tracking setup for a typical SaaS marketing site looks like this:

<!-- In your <head> -->
<script
  src="https://easyfunnel.co/sdk.js"
  data-api-key="ef_your_project_key"
></script>
<!-- In your markup -->
<nav>
  <a href="/pricing" data-ef-track="nav_pricing_click">Pricing</a>
  <a href="/signup" data-ef-track="nav_signup_click">Sign Up</a>
</nav>

<section id="hero">
  <h1>Your headline here</h1>
  <a href="/signup" data-ef-track="hero_cta_click">Start Free Trial</a>
</section>

<section id="pricing">
  <button data-ef-track="checkout_indie">Get Indie Plan</button>
  <button data-ef-track="checkout_startup">Get Startup Plan</button>
</section>

No build step. No configuration file. No tag manager. No data layer. One script tag and a handful of HTML attributes. Page views are tracked automatically. Clicks on annotated elements are tracked automatically. Sessions, attribution, and batching are handled for you.

You can see it all in your EasyFunnel dashboard, build funnels from your events, and let the AI insights tell you where users drop off.

That is what simple event tracking for websites should look like.

Start Tracking in Under 5 Minutes

EasyFunnel gives you custom event tracking, funnel analysis, session replay of user journeys, and AI-powered insights -- all for $5/month with a free trial. No credit card required to start.

Read the full documentation to see everything the SDK can do, or drop the script tag into your site and start seeing events in real time. If you are building a SaaS, a landing page, or anything in between, you do not need an enterprise analytics stack. You need something that works.

Get started at easyfunnel.co

Ready to track your funnels?

EasyFunnel gives you funnel analytics + AI chat for $5/mo. 3-day free trial.

Start Free