@addilytics/astro
v0.0.1
Published
Astro server analytics and bundled navigation tracking.
Readme
@addilytics/astro
Astro middleware that records pageviews after Astro has produced the final response. It does not read, clone, or replace the response body.
// src/middleware.ts
import { addilytics } from '@addilytics/astro';
export const analytics = addilytics({
endpoint: 'https://addilytics.example',
siteKey: import.meta.env.ADDILYTICS_KEY
});
export const onRequest = analytics;If you compose middleware with sequence(), put authentication or session initialization first,
Addilytics second, and routes or response-mutating middleware after it. Astro runs earlier
middleware as outer layers, so Addilytics still sees the final status and headers from later
middleware.
import { sequence } from 'astro:middleware';
export const onRequest = sequence(sessionMiddleware, analytics, responseMiddleware);This order matters in hybrid and client modes. Addilytics answers relay requests without calling
later middleware. Resolve identity from Astro.locals only when an earlier middleware populates it.
A resolver that reads the raw request can stay inside the Addilytics options.
The adapter ignores Astro's /_astro/ assets along with the core asset filters. It records only
eligible final HTML responses. Redirects, JSON responses, and server errors are skipped by default.
Middleware runs for on-demand rendered routes. A prerendered page served as a static file or directly
from a CDN never reaches Astro's server middleware. Use client mode below when those first views
must count.
Custom events
Export the middleware instance when an endpoint needs to record an event.
import type { APIRoute } from 'astro';
import { analytics } from '../middleware';
export const POST: APIRoute = async (context) => {
await analytics.track(context, 'newsletter_signup', {
props: { plan: 'weekly' }
});
return new Response(null, { status: 204 });
};track() uses the same bot filtering and identity rules as the core client.
Identity and background delivery
Use user when identity depends on Astro.locals or another part of the Astro context.
export const analytics = addilytics({
endpoint: 'https://addilytics.example',
siteKey: import.meta.env.ADDILYTICS_KEY,
user: (context) => context.locals.user?.id
});Register the middleware that populates Astro.locals before analytics in sequence().
On Cloudflare, the adapter uses context.locals.runtime.ctx.waitUntil() when available. On other
platforms it waits for delivery before returning. Supply waitUntil for another adapter:
export const analytics = addilytics({
endpoint: 'https://addilytics.example',
siteKey: import.meta.env.ADDILYTICS_KEY,
waitUntil: (context) => context.locals.waitUntil
});Analytics failures never replace or reject an otherwise successful application response.
Browser navigation modes
Server tracking remains the default. Use hybrid when Astro serves the first document and a client
router handles later navigations. Use client when the browser should own every pageview. Client
mode keeps custom server events enabled but skips automatic server document capture.
Set the same mode and relay path on both sides:
// src/middleware.ts
export const analytics = addilytics({
endpoint: process.env.ADDILYTICS_ENDPOINT!,
mode: 'hybrid',
relayPath: '/internal/analytics',
siteKey: process.env.ADDILYTICS_SITE_KEY!
});
export const onRequest = analytics;<!-- src/layouts/Layout.astro -->
<script>
import { installAddilytics } from '@addilytics/astro/browser';
installAddilytics({
endpoint: '/internal/analytics',
mode: 'hybrid'
});
</script>The browser entry listens for Astro's astro:page-load event, which runs after a navigation commits.
It also records persisted back-forward cache restores. Repeated calls to installAddilytics() reuse
the active tracker instead of attaching duplicate listeners. Call destroy() on the returned
tracker to remove them.
Hash-only changes are ignored by default. Set trackHashChanges: true to count them. The browser
sends only a generated event ID, timestamp, pathname, allowlisted campaign query, and referrer to
the same-origin relay. The server adds request metadata and user identity before delivery. The site
key and ingest endpoint stay in server code, and the adapter does not load a hosted script.
The relay accepts only its exact configured path. It rejects cross-origin requests, invalid JSON, and oversized or malformed payloads before they reach the application route.
