@1mn/react
v0.4.0
Published
React bindings for the 1mn SDK — embedded product analytics, session replay, error tracking, user management, and customer feedback (PostHog/Sentry-like). Self-contained: npm install + <OnemnProvider>, no snippet.
Downloads
13,666
Maintainers
Readme
@1mn/react
React bindings for the 1mn SDK — embedded product analytics, error tracking, and user management (PostHog/Sentry-like) for your React or Next.js app.
Self-contained: it bundles the SDK core, so npm install + one provider is all you need. No
snippet, no script tag.
- 📈 Analytics — automatic pageviews (incl. SPA navigations), sessions, web vitals, and custom events.
- 👥 User management — identify your signed-in users (with email) and watch them in your 1mn dashboard.
- 🐛 Errors — uncaught exceptions and unhandled rejections captured and grouped automatically.
- 💬 Feedback — a drop-in
<FeedbackWidget>(or headless hook) that files customer feedback as tickets. - 🎬 Session replay — opt-in per key from your dashboard; the recorder (rrweb) lazy-loads only for sampled sessions, so it never touches this bundle's size. Off by default; inputs masked.
- 🪶 ~9 KB, zero runtime dependencies, SSR-safe.
Install
npm install @1mn/reactQuickstart
Wrap your app once with OnemnProvider and pass your public key (1mn_pk_…, from the Users tab
in your 1mn dashboard) plus your signed-in user:
import { OnemnProvider } from "@1mn/react";
export default function App({ children, user }) {
return (
<OnemnProvider apiKey="1mn_pk_xxx" user={user}>
{children}
</OnemnProvider>
);
}That's it — pageviews, web vitals, and errors are captured automatically, and user is identified
(with email/name) whenever it's set. When user becomes null (logout) the SDK resets.
Custom events
import { useOnemn } from "@1mn/react";
function CheckoutButton() {
const onemn = useOnemn();
return (
<button onClick={() => onemn.capture("checkout_started", { plan: "pro" })}>
Upgrade
</button>
);
}Customer feedback
Collect in-app feedback with one component. Drop in the widget for a floating launcher + modal:
import { FeedbackWidget } from "@1mn/react";
<FeedbackWidget />;Or drive the modal from your own button (no launcher):
import { useState } from "react";
import { FeedbackWidget } from "@1mn/react";
function Sidebar() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)}>Feedback</button>
<FeedbackWidget launcher={false} open={open} onOpenChange={setOpen} />
</>
);
}Prefer your own UI? Use the headless hook (or useOnemn().feedback):
import { useFeedback } from "@1mn/react";
const { send } = useFeedback();
send("Love the new dashboard!");Each submission emits a $feedback event. In your 1mn dashboard these are filed as feedback
tickets in your backlog for triage — they never trigger an autonomous agent run on their own.
Next.js (App Router)
OnemnProvider is a client component — render it in a "use client" boundary (e.g. a
providers.tsx wrapped around children in your root layout). It's SSR-safe: nothing runs until
the browser hydrates. <FeedbackWidget> is likewise a client component.
React Router 7 / Remix / TanStack Start (SSR)
OnemnProvider is SSR-safe — render it from a normal module in your root route (e.g.
app/root.tsx), wrapped around your <Outlet />:
// app/root.tsx — a normal module, NOT a *.client.tsx file
import { OnemnProvider } from "@1mn/react";
import { useSession } from "./lib/auth-client";
export default function App() {
const { data: session } = useSession();
return (
<OnemnProvider apiKey="1mn_pk_xxx" user={session?.user}>
<Outlet />
</OnemnProvider>
);
}⚠️ Do not put
OnemnProvider(or a wrapper around it) in a*.client.tsxfile and render that in the SSR tree. React Router 7, Remix and Vite empty*.client.*modules in the server build, so the import isundefinedduring SSR and React throws on<undefined>— HTTP 500 on every route.OnemnProviderdoesn't need to be client-only; it's already SSR-safe.
If your user/session hook is client-only (it lives in a *.client.* module, or is undefined on
the server), keep OnemnProvider SSR-safe and move just the identify call into a small child
rendered behind a mounted guard (useOnemn().identify(...) inside a useEffect) — don't make the
whole provider client-only. After wiring it in, confirm the site still returns 200, not 500.
API
<OnemnProvider apiKey host? user? >
| Prop | Type | Notes |
| -------- | ------------------------------------------------- | -------------------------------------------------- |
| apiKey | string | Your public key (1mn_pk_…). Required. |
| host | string | Ingest origin. Defaults to the 1mn collector. |
| user | { id, email?, name? } \| null | Signed-in user. Identified when it has an email. |
useOnemn()
Returns { capture, identify, reset, set, feedback, optOut, optIn }. Stable across renders;
SSR-safe (no-ops on the server and before init).
capture(event, properties?)— a custom event.identify(id, set?, setOnce?)— usually unnecessary if you passuserto the provider.set(properties)— update person properties without re-identifying.feedback(message, properties?)— send an in-app feedback/support message.reset()— clear identity (logout); the provider does this for you whenuserclears.optOut()/optIn()— stop / resume sending.
<FeedbackWidget />
A drop-in feedback launcher + modal. All props optional:
| Prop | Type | Notes |
| ------------------- | ------------------------------------- | ----------------------------------------------------------- |
| launcher | boolean | Show the floating launcher button. Default true. |
| open | boolean | Controlled open state. Omit for uncontrolled. |
| onOpenChange | (open: boolean) => void | Open/close callback. |
| label | string | Launcher text. Default "Feedback". |
| title | string | Modal heading. Default "Send feedback". |
| description | string | Sub-text under the heading. |
| placeholder | string | Textarea placeholder. |
| position | "bottom-right" \| "bottom-left" | Launcher corner. Default "bottom-right". |
| accentColor | string | Primary button / launcher color. |
| properties | Record<string, unknown> | Extra props merged into the $feedback event. |
| onSubmit | (message: string) => void | Called after a successful send. |
useFeedback()
Returns { send(message, properties?) } for building your own feedback UI.
getOnemn()
The live client (or null on the server / before init), for non-component code.
Privacy
By default the SDK captures no DOM content (no autocapture) and no IP-derived geo, and ships
optOut(). Session replay is the one exception and is off unless you enable it for your key
in the 1mn dashboard: when on, it records the DOM/interactions of a sampled fraction of sessions via
rrweb, with text inputs masked by default (add onemn-block / onemn-mask classes to hide regions).
optOut() also stops recording. You are the data controller for your end-users' data — if you enable
replay, tell your visitors (EU/UK/CA consent).
License
MIT — see LICENSE.
