addilytics
v0.0.1
Published
Backend-first analytics with optional client navigation tracking.
Readme
addilytics
Server-first analytics with no cookies. The optional browser entry point fills SPA navigation gaps without exposing the site key. It is bundled by your application; Addilytics does not ship a hosted or CDN-loaded script.
import { createAddilytics } from 'addilytics';
const analytics = createAddilytics({
endpoint: 'https://addilytics.example',
siteKey: process.env.ADDILYTICS_KEY!,
deliveryTimeoutMs: 5_000,
onError(error, context) {
console.error('analytics delivery failed', context.operation, error);
}
});
const startedAt = Date.now();
const response = await render(request);
ctx.waitUntil(analytics.capture({ request, response, startedAt }));Framework adapters: @addilytics/sveltekit, @addilytics/wintercg, @addilytics/astro,
@addilytics/hono, @addilytics/react-router, @addilytics/nextjs, @addilytics/nuxt,
@addilytics/express, and @addilytics/fastify.
Collection never throws into the application request. Set onError if you want failed identity
lookups, filters, or ingest requests sent to your own logger. Delivery times out after five seconds
by default. Set trustProxy: true only when your server or hosting provider overwrites forwarded IP
and country headers from the public request.
Identity hashing requires the Web Crypto API. Use Node 20 or newer in Node deployments. If hashing
is unavailable, Addilytics reports an identify error and still records the event anonymously.
The client sends only utm_source, utm_medium, utm_campaign, utm_term, and utm_content from
the page URL. It removes the path, query, fragment, and credentials from the referrer before
delivery.
Server-only counting sees requests that reach application code. Client-side route changes, prerendered files, CDN hits, full-response caches, and browser back-forward cache restores bypass that mode. The browser entry below covers them through a same-origin relay.
Optional browser navigation tracking
Framework adapters can pair their server integration with the side-effect-free addilytics/browser
entry point. Importing it does not register listeners or send requests.
import { createBrowserTracker } from 'addilytics/browser';
const tracker = createBrowserTracker({
mode: 'hybrid',
endpoint: '/__addilytics'
});
await tracker.pageview(location.href);hybrid mode skips the first call because the server adapter owns the initial document request.
client mode sends the first call as well. Calls for the same URL, including hash-only changes, are
deduplicated by default. Pass { force: true, navigationKey: uniqueNavigationId } for a persisted
pageshow event. Reusing a navigation key cannot create another view, even with force.
addEventListener('pageshow', (event) => {
if (event.persisted) {
void tracker.pageview(location.href, {
force: true,
navigationKey: `pageshow:${event.timeStamp}`
});
}
});The browser sends only an event ID, timestamp, path, UTM query fields, and referrer origin. It never
receives or sends the site key, IP address, or user agent. Failed requests retry once by default with
the same event ID and timestamp. Use onError to observe failures without throwing into navigation.
A committed client navigation has no reliable HTTP response status. Relayed pageviews store status
0 to mean unknown instead of pretending the route returned 200. trackStatuses applies only to
server responses and does not filter browser-relayed pageviews.
Server adapters can share the relay built into the core client:
const relay = analytics.createRelay({ mode: 'hybrid', endpoint: '/__addilytics' });
if (relay.matches(request)) {
return relay.handle({ request, ip: trustedAddress, country: trustedCountry });
}The relay requires a same-origin JSON POST, limits the body to 4096 bytes while streaming it, and
validates the event ID, timestamp, and canonical path. It ignores client-supplied IP and user-agent
fields, resolves identity from the real relay request, and forwards a normal authenticated pageview
to the ingest API. Adapter server options should omit browser support for server-only counting, use
hybrid for server-owned initial views plus client navigation, or use client and skip automatic
server document capture.
