kilden_
← blog
sdk

A web SDK with zero dependencies

KE
Kilden Engineering 27 May 2026 · 3 min read
on this page ▾

The core of the Kilden web SDK — queue, batching, retries, identity, exit flush — ships as 7.7 KB of gzipped JavaScript with zero runtime dependencies. Many cookie consent banners ship more than that just to ask you about cookies.

We're not bragging about the number; we're bragging about the constraint. Every snippet a customer puts on their page is code they're implicitly trusting, and a dependency tree is a supply chain. The simplest way to keep both small was to write the whole thing in plain TypeScript and treat every kilobyte as a product decision. What's interesting is how much behavior fits inside that budget — because a tracking SDK is much stranger than it looks.

Identity that survives subdomains

Every visitor gets an anonymous id — anon_ plus a UUIDv7 — persisted in localStorage and in a cookie on the registrable domain. The cookie is what makes identity cross subdomains by default: land on www.example.com from an ad, sign up on app.example.com, and it's one visitor, one journey, no configuration. First-touch attribution (utm_source and friends) persists the same way, written once with set-once semantics so a later visit can't overwrite where the user actually came from.

The queue is the product

Events don't go to the network one by one; they land in a queue that flushes by batch size or interval, whichever comes first. Everything hard about the SDK is really about that queue:

Events flow from track calls into an in-memory queue, flush in batches to capture, retry with backoff on failure, and fall back to sendBeacon on page unload. track() queue uuid v7 per event flush batch · interval · gzip capture failed batch → backoff, retry with the same uuids page unload → fetch keepalive, then sendBeacon (token in body)
  • Retries — failed batches back off and retry; every event carries a client-generated UUIDv7, so a retried batch that half-arrived doesn't double-count. The server dedups by id.
  • Exit flush — when the page unloads mid-queue, the SDK flushes via fetch(keepalive) and falls back to sendBeacon. Beacons can't carry headers, which is exactly why the capture protocol accepts the identity token in the batch body too. The last 100 milliseconds of a page's life are where tracking data goes to die; most of the SDK's design pressure comes from there.
  • Compression — batches gzip when the browser supports it. Tracking overhead is mostly upload bytes on bad networks; compressing beats any micro-optimization in the hot path.
  • Never block, never lose — if the identity-token fetch is slow at startup, the queue waits briefly, then releases: events go out unverified rather than piling up. Verified late beats unverified beats stuck.

Extension without bloat

Features that not everyone needs — autocapture, session replay, in-app messages — are plugins over a small before/enrich/after pipeline, loaded only when used, and a beforeSend hook gives integrators a last look at every event (including autocaptured ones) to scrub or veto. The 7.7 KB core is the part everyone pays for; everything else is opt-in weight.

None of this is exotic, and that's the point. A tracking SDK is a queue, a persistence story, and a pile of browser edge cases — and none of those need a framework. The browser already ships everything required; the engineering is deciding what not to include, a thousand times in a row.

$ npm install kilden
Analytics, feature flags, campaigns and session replay — one event pipeline, one SDK.

1M events/month on the free tier. Set up in about two minutes.

new posts, monthly Engineering and product notes. No spam, unsubscribe anytime.