npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@brandkarma/tracker

v0.1.0

Published

Cookieless web analytics and attribution SDK for BrandKarma. Image-beacon transport, no cookies, no device storage. Framework-free core plus React and Next.js App Router bindings.

Readme

@brandkarma/tracker

Cookieless web analytics and attribution SDK for BrandKarma. Image-beacon transport, no cookies, no device storage of any kind. Framework-free core with React and Next.js App Router bindings.

  • @brandkarma/tracker — core, framework-free, valid Node ESM (usable anywhere)
  • @brandkarma/tracker/react — generic React client components (optional peer: react >= 18)
  • @brandkarma/tracker/next — Next.js App Router bindings, everything from /react plus Analytics (optional peers: react, next >= 14)

ESM only.

Install

pnpm add @brandkarma/tracker
# or: npm install @brandkarma/tracker

Quickstart (Next.js App Router)

1. Define your site's event taxonomy (see Event taxonomy below):

// lib/events.ts
export const EVENTS = {
  PAGEVIEW: "pageview", // REQUIRED literal — the server's pageview metrics match it exactly
  CTA_CLICK: "cta_click",
  CONTACT_CLICK: "contact_click",
} as const;

export type EventName = (typeof EVENTS)[keyof typeof EVENTS];

2. Configure the tracker ONCE in a single "use client" module:

// lib/tracker.ts
"use client";
import { createBrandKarma } from "@brandkarma/tracker/next";
import type { EventName } from "./events";

export const { track, withAttribution, getAttribution, Analytics, TrackedOutboundLink, ContactLink, TrackView } =
  createBrandKarma<EventName>({
    projectId: "<your BrandKarma Brand id>",
    canonicalOrigin: "https://www.example.com",
  });

3. Mount Analytics once in your root (or locale) layout, inside your providers, after {children}:

// app/[locale]/layout.tsx (server component)
import { Analytics } from "@/lib/tracker";

// ... inside the provider tree, after {children}:
<Analytics />

Analytics fires a pageview on mount and on every client-side route change. It reads the route via usePathname() and deliberately never calls useSearchParams() — that would opt every page out of static rendering. It also guards against React StrictMode's double effect invocation, so no duplicate pageviews land in your data.

4. Track from anywhere:

// In a SERVER component: render the components (they are client references).
<TrackView event="pricing_view" />
<TrackedOutboundLink href="https://app.example.com/auth/register" event="cta_click" location="hero">
  Start free
</TrackedOutboundLink>
<ContactLink href="mailto:[email protected]" event="contact_click" location="footer">
  [email protected]
</ContactLink>

// In a CLIENT component: call the functions.
track("cta_click", { location: "navbar" });

Server components vs client components

The component exports (Analytics, TrackView, TrackedOutboundLink, ContactLink) are client references — render them directly from server components; they need no wrapper.

The function exports (track, withAttribution, captureAttribution, getAttribution) are import-legal everywhere but callable only inside client components. Calling track() from a server component fails the build with "It's not possible to invoke a client function from the server." If a server page needs to record a view, render <TrackView /> instead of calling track().

Plain React (no Next.js)

Use @brandkarma/tracker/react — the same factory without Analytics (which depends on Next's router). Fire pageviews yourself from your router of choice, with your own "did the path actually change" guard.

No framework

Use the core directly:

import { createTracker } from "@brandkarma/tracker";

const tracker = createTracker({ projectId: "...", canonicalOrigin: "https://www.example.com" });
tracker.captureAttribution(); // on landing
tracker.track("pageview");

Getting a projectId (it's a Brand id)

projectId is your BrandKarma Brand id — the id of the Brand document, shown in the BrandKarma app under Brand Details → Tracking Pixel. It is public by design (it ships in every beacon URL); the read side of the analytics is what's protected.

Warning: unknown or mistyped ids fail SILENTLY. The tracking endpoint accepts any projectId without validation — events for a wrong id are stored but unreadable by anyone. After setup, always verify that events appear in your BrandKarma dashboard before trusting the integration.

Event taxonomy

The SDK is generic over your event names (createBrandKarma<EventName>(...)); the taxonomy itself stays in your site's code, not in the SDK. Conventions that work well:

  • A single lib/events.ts with a const map + derived union type (see Quickstart).
  • pageview must be the literal string "pageview" — server-side pageview metrics compare it exactly.
  • Name events by intent (cta_click, contact_click, tool_use, signup), and pass the placement as a location prop (hero, navbar, pricing:not-sure) so one event can be compared across placements.
  • If you assign monetary values to conversions, keep a CONVERSION_VALUES map next to the taxonomy in the same file.

Privacy posture (cookieless, no device storage)

This SDK never uses cookies, localStorage, sessionStorage, or any other terminal-device storage — reading or writing. All three count as terminal-device storage under §25 TDDDG (and equivalent EU consent regimes) and would require an opt-in banner. An in-memory variable does not. This is a load-bearing product promise, not an implementation detail.

How it works instead:

  • Attribution is captured once per JS context from the landing URL and held in memory. It survives client-side navigation (SPA routers keep one JS context) but is lost on a hard reload — an accepted trade-off, since the realistic ad journey (land with ?gclid → browse → click the CTA) stays within one context.
  • Sessions and unique visitors are stitched server-side by BrandKarma from a daily-salted hash of user agent + IP, held in memory for a 30-minute sliding window. The salt is random per server process and never persisted; IP addresses are never stored. Visitors cannot be joined across days or server restarts.
  • The SDK sends no session IDs, no visitor IDs, no timestamps — such parameters don't exist on the server. Do not batch or delay events either: the server timestamps at receipt, so deferral skews sessionization.

Attribution capture and click-ID semantics

Captured parameters (exactly the server's binding list, exported as ATTRIBUTION_PARAMS):

| Group | Params | Meaning | |---|---|---| | UTM | utm_source, utm_medium, utm_campaign, utm_term, utm_content, utm_id | Campaign labeling. A paid-style utm_medium (cpc, ppc, paid…) classifies traffic as paid. | | Paid click IDs | gclid, gbraid, wbraid, msclkid | Google/Microsoft Ads. Their presence classifies the session as paid and they are the keys for ad-platform conversion import. | | Organic-capable click IDs | li_fat_id, fbclid, ttclid | LinkedIn/Meta/TikTok. Captured and stored, but deliberately not treated as paid evidence (they appear on organic shares too). |

Semantics:

  • Capture-once, first-non-empty-wins. The landing-page capture is kept for the life of the JS context and re-sent with every event, so attribution survives navigation to URLs that no longer carry the params. Explicit beacon params outrank the server's fallback parsing of the page URL. An empty capture does not lock; a later navigation carrying params can still be captured.
  • ?ref= shorthand (common on inbound links) is mapped client-side to utm_source when no utm_source is present; ref itself is never sent.
  • Values are trimmed and truncated to 512 chars (matching the server cap).
  • withAttribution(url) appends the captured params to an outbound URL — use it (or TrackedOutboundLink, which does it for you) to carry click IDs across a domain boundary to your app, where they can be stored server-side against the account and conversions imported back to the ad platform without any ad cookie. Params already on the target URL are never overwritten. During SSR it returns the URL unchanged (TrackedOutboundLink server-renders the clean href and upgrades after hydration).
  • referrer is sent as an explicit query param (from document.referrer, only when non-empty) because the HTTP Referer header of an image beacon identifies the page the pixel is on, not where the visitor came from. Traffic-source classification (paid/search/llm/social/referral/direct) is derived server-side, first-touch per session.

Custom props: contract and limits

The optional second argument to track(event, props):

track("tool_use", { tool: "gtin-validator" });
track("cta_click", { location: "hero", destination: "/auth/register" });
  • Type: Record<string, string | number | boolean | undefined>.
  • On the wire each prop becomes a p_-prefixed query param (p_tool=gtin-validator), which is what the server recognizes as a custom prop. The prefix makes collisions with reserved params (projectId, event, url, referrer, attribution params, …) impossible.
  • Keys must match ^[A-Za-z0-9_-]{1,64}$. Non-conforming keys (dots, $, spaces, empty, over-long) are dropped silently — track never throws.
  • Values are stringified and truncated to 512 chars. undefined, null and "" values are skipped. false and 0 are sent.
  • Max 20 props per event; extras are dropped.
  • Props are the first thing shed if the beacon URL approaches the server's 8 KB request-line limit (see below).

Transport and limits

  • Transport is a 1×1 image GET: immune to CORS, stays in flight across page unload (matters for outbound clicks), can't read a response. Fire-and-forget — there is no delivery confirmation and no retry, and track() is a silent no-op during SSR.
  • A _t cache-buster is appended (the server sets no Cache-Control on the pixel), unique even for identical events fired in the same millisecond.
  • The server silently drops request lines over 8 KB, so the SDK measures the final encoded URL and sheds in order until it fits: custom props → page-URL query string → utm_term/utm_content → referrer path → remaining non-paid attribution params. The paid click IDs (gclid/gbraid/wbraid/msclkid) are last to go and in practice never dropped.

API notes

  • Single tracker per page. Attribution capture state is module-level and shared; configure exactly one tracker per site (the factory-module pattern above enforces this naturally).
  • resetAttribution() (core export) clears the captured attribution. Test-only — it exists because capture-once semantics are otherwise untestable. Never call it in production code.
  • createTracker (core) returns plain closures — destructuring is safe, nothing depends on this.

License

MIT