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

@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.