tracklane

Installation

Requirements and install commands

Requirements

  • Node.js >=22.14.0
  • An ESM project ("type": "module") or a bundler. The package ships ESM only. CommonJS consumers need a dynamic import().

Install

pnpm add tracklane

Two entry points

The browser and the server are two libraries. They share no code and no state, only the vocabulary, which is what makes purchase mean the same thing on both sides. Import from the one you are in:

Import fromWhere
tracklane/browserIn the browser. Calls the vendor tags already on your page.
tracklane/serverOn your server. Calls each vendor's conversion API.
tracklaneTypes only, for writing your own provider. Has no runtime.

Importing the server entry point into client code would pull Node built-ins into your bundle, which is why they are separate.

Vendor tags go on the page

This library does not install vendor tags. Each tag belongs on your page, put there the way its vendor documents: a snippet or a tag manager. We talk to the tag that is already there.

That is deliberate: injecting a third-party script is a decision about your content-security policy and your page weight, and a vendor's own configuration command emits a page view, so issuing a second one would quietly double your traffic.

Each provider page says exactly what its vendor expects on the page.

Verify

import { createTracking, ga4 } from 'tracklane/browser';

const { track } = createTracking({
  providers: [ga4('G-XXXXXXX')],
  onError: console.error,
});

track('page_view');

If a tag is missing, the call fails and reaches onError rather than disappearing.

Open your browser's network tab and look for a request to google-analytics.com/g/collect. If it is there with a 204, the tag is wired up.

A 204 means Google received the request, not that the event will appear in a report. Google answers 204 to payloads it discards, including anything sent from a headless browser, which it treats as a bot. Confirm in the Realtime report, not in the network tab.

Where to put it

createTracking returns plain functions and holds no state beyond the standing identity in the browser. Create it once, at module scope, and import the functions where you need them:

// lib/tracking.ts
import { createTracking, ga4 } from 'tracklane/browser';

export const { track, identify, consent } = createTracking({
  providers: [ga4('G-XXXXXXX')],
  onError: (error) => console.error(error),
});

On the server, do the same, but never carry user data across calls. A server instance is shared by every request, so identity travels with each track, never in the instance. That is why the server has no identify.

Errors

Nothing throws at the call site. One vendor failing must not break a checkout. Everything arrives through onError:

onError: (error) => {
  // error.provider  which vendor
  // error.event     which event
  // error.severity  'error': it did not arrive
  //                 'warning': it arrived, but something is worth knowing
  // error.message   what happened. Never the payload.
  report(error);
};

The warning level exists because some vendors accept an event and then do nothing useful with it. Those are the cases you want to see; without them, the only symptom is a report that stays empty.

Diagnostics never carry event contents. Events hold personal data by design, so piping onError straight to your logger cannot leak an email address.

On this page