JavaScript (npm)

@caravela-studio/glancy-web is Hey Glancy for any browser app: React, Vue, Svelte, Angular, or plain TypeScript. It sends events, page views, session replays and heatmaps, batches them, and flushes the queue when the tab is hidden or closed so nothing is lost. It's fully typed.

On Next.js, use the Next.js SDK instead (it wraps this one). For a site without a build step, use the script tag.

Install

npm install @caravela-studio/glancy-web

Set up

Create one client and share it across your app:

// analytics.ts
import { BrowserClient } from "@caravela-studio/glancy-web";

export const glancy = new BrowserClient({
  writeKey: "YOUR_WRITE_KEY",
  host: "https://ingest.heyglancy.com",
});

The write key can only add events to one bucket, never read them, so it's safe to ship in your bundle. To keep it out of your code, read it from your build tool's public environment variables (for example import.meta.env.VITE_GLANCY_WRITE_KEY with Vite).

Page views

Call page() on every route change, from your router's navigation hook:

// React Router
const location = useLocation();
useEffect(() => { glancy.page(); }, [location.pathname]);

// Vue Router
router.afterEach(() => glancy.page());

Each page view is stored as Page Viewed with the path, URL, title, referrer and UTM parameters.

Events and users

glancy.track("Order Completed", { revenue: 42, currency: "EUR" });

// after sign up or sign in
glancy.identify(user.id, { name: user.name, email: user.email, plan: user.plan });

// on sign out
glancy.reset();

See Events, users and sessions for naming tips and how identities are merged.

Options

Option Default
writeKey, host Required
heatmaps false Collect clicks and scroll depth
sessionReplay false true, or { sampleRate, maskAllInputs, maskAllText, maskTextSelector, blockSelector }; see Session replay
redactParams key, token, code, password… URL query parameters replaced by redacted
flushAt / flushInterval 20 / 2000 ms Send a batch after this many events, or this long
manualStart false Don't start replay and heatmaps until startRecording()
debug false Log every event to the console
export const glancy = new BrowserClient({
  writeKey: "YOUR_WRITE_KEY",
  host: "https://ingest.heyglancy.com",
  heatmaps: true,
  sessionReplay: { sampleRate: 0.5 },
});

Methods

Method What it does
track(event, properties?) Sends an event
page(name?, properties?) Sends a page view
identify(userId, traits?) Ties this browser to a user and updates their profile
group(groupId, traits?) Ties the user to a company or team
alias(userId, previousId?) Merges an old id into a new one
reset() Forgets the user; new anonymous id and session
flush() Sends the queue now
getAnonymousId(), getUserId(), getSessionId() The current ids
startRecording(), stopRecording() Start or stop replay and heatmaps, for example on consent changes
consent({ ads }) The visitor's ad consent from your banner; with ads: false their events are never sent to ad platforms (Ad conversions)

If you need consent before recording sessions, configure replay and heatmaps with manualStart: true. Events are sent as usual, and nothing is recorded until you call startRecording():

export const glancy = new BrowserClient({
  writeKey: "YOUR_WRITE_KEY",
  host: "https://ingest.heyglancy.com",
  sessionReplay: true,
  heatmaps: true,
  manualStart: true,
});

onConsentGiven(() => glancy.startRecording());
onConsentWithdrawn(() => glancy.stopRecording());

On your server

@caravela-studio/glancy-web/server sends events from any JavaScript server (Node, Bun, Deno, edge runtimes). It reads GLANCY_HOST and GLANCY_WRITE_KEY. Using Next.js? @caravela-studio/glancy-next/server has the same calls.

import { glancy, getAnonymousId } from "@caravela-studio/glancy-web/server";

await glancy().track({ userId: user.id, anonymousId: getAnonymousId(request.headers), event: "Order Completed", properties: { revenue: 42 } });

To record your API's requests with their status and duration_ms (filter by status ≥ 500 to find server errors), send an API Request event per request:

  • Fetch-style handlers (Hono, Remix, SvelteKit, Bun, Deno, Cloudflare Workers): wrap the handler with withGlancyRoute(handler, { waitUntil }). waitUntil (e.g. ctx.waitUntil on Workers) sends in the background; without it the response waits for the send. A handler that throws counts as 500 and the error goes on.
  • Express / Connect: app.use("/api", trackRequests()). It sends once the response is finished.
import express from "express";
import { trackRequests } from "@caravela-studio/glancy-web/server";

const app = express();
app.use("/api", trackRequests({ userId: (req) => req.headers["x-user-id"] }));

Both take event (default API Request), userId, writeKey, host and onError.