Real-time presence tracking for browser apps.
Simple Presence gives you live per-page or per-section counts with a small client SDK, a hosted dashboard, and a realtime backend. It also tracks count history (for sparklines) and peak concurrent users per tag.
@simple-presence/corefor framework-agnostic browser apps@simple-presence/reactfor React apps@simple-presence/solid-jsfor SolidJS apps
- Dashboard: simple-presence.erickr.dev
- Hosted presence endpoint:
wss://simple-presence.erickr.dev/api/presence
Create an app in the dashboard and use its public key as appKey in the SDKs.
npm install @simple-presence/reactimport { usePresenceCount } from "@simple-presence/react";
function OnlineUsers() {
const count = usePresenceCount("landing-page", {
appKey: "your-public-app-key",
});
return <span>{count} users online</span>;
}Need history and peak stats? Use usePresence instead:
import { usePresence } from "@simple-presence/react";
function PresencePanel() {
const { count, history, peak } = usePresence("landing-page", {
appKey: "your-public-app-key",
});
return (
<div>
<span>{count} online</span>
<span>Peak: {peak}</span>
{/* history is CountSnapshot[] — use for sparklines */}
</div>
);
}npm install @simple-presence/solid-jsimport { createPresenceCount } from "@simple-presence/solid-js";
function OnlineUsers() {
const count = createPresenceCount(
() => "landing-page",
() => ({ appKey: "your-public-app-key" }),
);
return <span>{count()} users online</span>;
}For history and peak stats, use createPresence:
import { createPresence } from "@simple-presence/solid-js";
function PresencePanel() {
const { count, history, peak } = createPresence(
() => "landing-page",
() => ({ appKey: "your-public-app-key" }),
);
return (
<div>
<span>{count()} online</span>
<span>Peak: {peak()}</span>
</div>
);
}npm install @simple-presence/coreimport { SimplePresence } from "@simple-presence/core";
const presence = new SimplePresence({
tag: "landing-page",
appKey: "your-public-app-key",
onCountChange: (count) => {
console.log("Users online:", count);
},
});
// Fetch history snapshots (for sparklines) and peak stats
const history = await presence.getHistory();
const stats = await presence.getStats();
console.log("Peak:", stats.peak);
window.addEventListener("beforeunload", () => {
void presence.destroy();
});By default the SDKs connect to the hosted service. For local development or self-hosting, pass your base API URL and the client will connect to the matching websocket presence endpoint.
new SimplePresence({
tag: "landing-page",
appKey: "your-public-app-key",
apiUrl: "http://localhost:3000/api",
});That produces a websocket connection to ws://localhost:3000/api/presence.
- Create an app in the dashboard.
- Copy its public key.
- Track presence with a
tagsuch as"landing-page"or"pricing". - Receive live counts as users join, leave, or go away.
The core client also:
- marks users as
awaywhen the page becomes hidden andonlinewhen it becomes visible again - reuses a single websocket connection for identical
(apiUrl, appKey, tag)pairs - keeps a stable browser client id in local storage, with cookie and in-memory fallbacks
The server tracks:
- count history — snapshots every 10 seconds, retained for 30 minutes (ideal for sparklines)
- peak concurrent users — all-time high-water mark per tag, updated on every presence change
apps/
server/ Cloudflare Worker + Durable Objects backend
web/ Dashboard and marketing site
packages/
config/ Shared configuration
contracts/ Shared contracts and schemas
core/ Framework-agnostic SDK
react/ React hooks
solid-js/ SolidJS primitives
This repo uses Bun workspaces.
bun install
cp apps/server/.env.example apps/server/.env
cp apps/web/.env.example apps/web/.env
bun run devDefaults from the examples:
- web app:
http://localhost:3001 - server API:
http://localhost:3000/api
MIT
