@edgeplus/sdk
v0.0.19
Published
EdgePlus browser SDK - Prescriptive web analytics
Maintainers
Readme
EdgePlus
Find what's broken on your site, and what to do about it.
Most analytics tools show you charts and let you figure things out. EdgePlus tells you which button broke for which users, and the first thing to check. Like a teammate dropping you a note, not a chart to interpret.
Quick start
npm install @edgeplus/sdkimport { init } from '@edgeplus/sdk';
init({ siteKey: 'ep_site_your_key' });Get your siteKey at dashboard.edgeplus.pro. First data shows up within 5 minutes; first alert (if there's something to fix) within 24 hours.
What you'll get
When users get stuck, you'll see alerts like this:
Rage clicks detected on /checkout.
· Target: "Pay now" (button)
· 12 users clicked an average of 6.4 times in the last hour, max 9
· Likely cause: slow click handler, no visual feedback, or routing failed
What to check:
· DevTools Network tab — is the click response slow?
· Add a loading state to prevent re-clicksAuto-detected: rage clicks, dead clicks (clicks on non-interactive elements), scroll dropoff, JavaScript errors, Google Web Vitals (LCP, CLS, INP), and page views (SPA-aware).
init() runs in the browser, so use a small client component:
// app/edgeplus-init.tsx
'use client';
import { useEffect } from 'react';
import { init } from '@edgeplus/sdk';
export function EdgePlusInit() {
useEffect(() => {
if (process.env.NODE_ENV !== 'production') return;
init({ siteKey: process.env.NEXT_PUBLIC_EDGEPLUS_SITE_KEY! });
}, []);
return null;
}Mount it once in your root layout:
// app/layout.tsx
import { EdgePlusInit } from './edgeplus-init';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<EdgePlusInit />
{children}
</body>
</html>
);
}The NODE_ENV guard keeps the SDK off during local dev and tests.
If you use Tailwind, CSS Modules, or styled-components, your CSS class names change every build:
<!-- Build 1 --> <button class="btn-primary-x9k2_a">Pay now</button>
<!-- Build 2 --> <button class="btn-primary-3m7f_b">Pay now</button>EdgePlus might count these as two different buttons, splitting your data.
Fix it by giving important elements a stable id:
<button data-ep-id="checkout-pay" className="btn-primary-x9k2_a">
Pay now
</button>Where to use it: Checkout buttons, signup CTAs, main nav links, A/B test targets. You don't need it on every element — only the ones that matter for business decisions.
Bonus: Elements with data-ep-id are excluded from dead-click detection. If you mark a <div> as a clickable card, EdgePlus respects that.
Example: SaaS landing nav
const navItems = [
{ href: '/features', label: 'Features', epId: 'nav-features' },
{ href: '/pricing', label: 'Pricing', epId: 'nav-pricing' },
{ href: '/docs', label: 'Docs', epId: 'nav-docs' },
{ href: '/signup', label: 'Sign up', epId: 'nav-signup' },
];
<nav>
{navItems.map(({ href, label, epId }) => (
<Link key={href} href={href} data-ep-id={epId}>
{label}
</Link>
))}
</nav>label may change with marketing copy or i18n, but epId stays the same. EdgePlus aggregates rage clicks correctly across builds and locales.
Auto-detection covers user behavior. For business milestones (signup, purchase, content read), call track():
import { track } from '@edgeplus/sdk';
track('signup_completed', { plan: 'pro', source: 'landing' });Signup funnel
track('signup_started', { source: 'landing-hero' });
track('signup_email_submitted');
track('signup_email_verified');
track('signup_completed', { plan: 'free' });EdgePlus measures conversion at each step and tells you where users drop off.
E-commerce checkout
track('cart_added', { product_id: 'sku-123', price: 29000 });
track('checkout_started', { total: 87000 });
track('checkout_completed', { order_id: 'ord-456' });Search
track('search', { query: 'laptop', results: 42 });Don't send PII — no names, emails, phone numbers. Stick to short strings, numbers, and booleans.
By default EdgePlus tracks anonymous visitors by a stable visitor_id stored in localStorage (13-month lifetime). When a user signs in, call identify() to link that visitor to your user id:
import { identify, reset, optOut, optIn, setConsent } from '@edgeplus/sdk';
// on login
identify('user_12345', { plan: 'pro' });
// on logout — keeps the visitor_id, drops the user binding
reset();All events after identify() include user_id. Events before it (the anonymous session) stay anonymous but share the same visitor_id, so the dashboard can join them.
Opt-out / consent
// user disabled analytics in settings
optOut();
// user re-enabled
optIn();
// integrate with a consent banner
setConsent({ analytics: false }); // stops collection until toggled back onoptOut() clears all stored identifiers. setConsent({ analytics: false }) keeps identifiers but pauses event transmission.
Sessions
A session ends after 30 minutes of inactivity. Reopening the site within 30 minutes continues the same session.
Don't send PII in traits
Stick to short strings, numbers, booleans — plan, country, tier, etc. Not names, emails, phone numbers.
Will it slow down my site?
No. The SDK is ~18 KB gzipped (ESM) / ~21 KB (CDN IIFE), loads with defer, and sends events in async batches. Zero impact on page rendering.
Does it work with my framework? Yes — React, Next.js, Vue, Svelte, Astro, plain HTML. Page navigation is auto-detected for SPAs.
Do I need a cookie consent banner?
No cookies are used. EdgePlus stores a random visitor_id in localStorage for 13 months. GDPR / CCPA compliant out of the box.
Can I disable it in dev/test?
Yes — wrap init() in a NODE_ENV check (see the Next.js example).
What if I don't add data-ep-id?
Everything still works. EdgePlus auto-detects elements. data-ep-id is optional, for tracking key elements with extra precision.
Calling init() twice?
Safe — it's idempotent. The second call is ignored.
TypeScript? Yes. Type definitions included.
What language is the dashboard in? Currently Korean. English support is on the roadmap. The SDK and the data it collects are language-agnostic.
Privacy
- No cookies
- No PII collected automatically (names, emails, phone numbers)
- IP addresses are hashed with a daily-rotating salt and discarded immediately
<input>and<textarea>clicks are excluded automaticallyvisitor_idis a random UUID inlocalStoragewith a 13-month lifetime — not shared across sites or servicessession_idrotates after 30 minutes of inactivityoptOut()/setConsent({ analytics: false })are available for user control
SDK options
// browser mode (default)
init({
siteKey: 'ep_site_your_key', // required in browser mode
endpoint: 'https://...', // optional, defaults to collect.edgeplus.pro
batchSize: 10, // optional, events per batch
batchIntervalMs: 5000, // optional, batch interval
debug: false, // optional, console logging
});
// server-proxy mode (0.0.15+) — siteKey not exposed in the browser
init({ endpoint: '/api/edgeplus.pro' });In server-proxy mode, your server route forwards requests to https://collect.edgeplus.pro/api/collect with an X-Secret-Key header.
Server route — one-liner per framework (0.0.19+)
handleProxy accepts any Web-Request-based framework input and auto-unwraps it. nodeProxy covers Node http (Express / Pages Router / Angular SSR).
// Next.js App Router · Vercel Edge · Bun · Deno · CF Workers
export const POST = (req: Request) =>
handleProxy(req, { secretKey: process.env.EDGEPLUS_SECRET_KEY! });
// SvelteKit · Remix · Astro — pass the event/args/context directly
export const POST = (event) =>
handleProxy(event, { secretKey: process.env.EDGEPLUS_SECRET_KEY! });
// Hono — pass the Context directly
app.post('/api/edgeplus.pro', (c) =>
handleProxy(c, { secretKey: c.env.EDGEPLUS_SECRET_KEY }));
// Nuxt 3 — pass the H3Event directly
export default defineEventHandler((event) =>
handleProxy(event, { secretKey: process.env.EDGEPLUS_SECRET_KEY! }));
// Express · Next.js Pages Router · Angular Universal SSR — Node http
app.post('/api/edgeplus.pro',
nodeProxy({ secretKey: process.env.EDGEPLUS_SECRET_KEY! }));
// Vite dev (SPA) — plugin auto-mounts /api/edgeplus.pro
import { edgeplusVitePlugin } from '@edgeplus/sdk/vite';
export default defineConfig({ plugins: [edgeplusVitePlugin()] });The dashboard's install wizard generates ready-to-paste snippets for 11 frameworks (TS/JS + Python / Go / Java / C# / Rust manual fetch).
License: Proprietary. All rights reserved.
