live-visitors
v1.0.0
Published
Count page views, unique browsers, and who is on the page right now.
Maintainers
Readme
Live Visitors
Four numbers. They are not the same number.
| Field | What it actually is |
| --- | --- |
| views | How many times this path was loaded. A refresh counts again. A second tab counts again. |
| visitors | Unique browser ids seen by this store. A new browser, or cleared site data, is a new visitor. Not a person. |
| today | Those ids that loaded the page on the current UTC day. |
| live | Ids with a tab that heartbeated inside the live window (default 20 seconds). Two tabs, one id: 1. |
The browser keeps an id in localStorage (live-visitors-id) and a tab id in sessionStorage (live-visitors-tab). It posts view once per path per document load, then beat every 10 seconds. A refresh counts as another view. React strict mode does not double-count: the view is recorded once per document.
stop() clears the heartbeat. It does not send leave. A tab that navigates away drops out of live when the window passes (default 20 seconds). Closing the tab sends leave on pagehide and drops out immediately.
Install
npm install live-visitorsimport { LiveVisitors } from "live-visitors";
const visitors = new LiveVisitors({
url: "/api/live-visitors",
onUpdate(snapshot) {
document.querySelector("#live").textContent = String(snapshot.live);
},
});
visitors.start();Server
The package does not phone home. You host the counter. examples/server.mjs is a single-process server:
npm run build
node examples/server.mjs
# POST http://127.0.0.1:8787/ and GET http://127.0.0.1:8787/?page=/MemoryCounter is accurate on one long-lived process. A serverless deploy with many isolates will each keep their own map, and the numbers will be wrong. Implement CounterStore (apply, snapshot) on Redis or Postgres if you run more than one process. The memory store resets when the process exits.
POST JSON: { "type": "view" | "beat" | "leave", "visitorId", "tabId", "page" }.
Ids are 8–80 characters of [A-Za-z0-9_-]. page is a path starting with /, max 200 characters.
GET ?page=/docs returns the snapshot. Responses are cache-control: no-store.
