User identification
Nohmo assigns every browser a persistent device ID on first visit — all events are anonymous until you call linkUser(). Once called, every event that device ever fired (including before signup) is retroactively attached to that user on the backend.
At login — the standard pattern
Call await linkUser() immediately after your login API resolves. Always await it — the function is async and needs to complete before the session is fully linked.
import { useNohmo } from 'nohmo'export default function LoginForm() {const { linkUser } = useNohmo()const handleLogin = async () => {const user = await loginAPI()// Always await — linkUser sends a network request to link the deviceawait linkUser(user.id, // required — your internal user ID (string)user.email, // optional but strongly recommended{ plan: user.plan }, // optional — any extra metadata)}}
On page load — restoring a logged-in session
If you store the logged-in user in localStorage or a cookie and want to re-identify on every page load, do not call linkUser() directly inside a useEffect with an empty dependency array.
React runs child useEffect hooks before parent ones. If your auth context is rendered inside NohmoNextProvider, the Nohmo tracker hasn't been set up yet when your effect fires — the call is silently dropped.
Note: Fixed in v0.3.20 — the SDK now queues any linkUser() calls that arrive before the tracker is ready and drains them automatically once it initialises. Upgrade to v0.3.20 and the pattern below works correctly.
// ✅ Correct — works in v0.3.20+'use client'import { useEffect } from 'react'import { useNohmo } from 'nohmo'export function AuthProvider({ children }) {const { linkUser } = useNohmo()useEffect(() => {const user = JSON.parse(localStorage.getItem('user') || 'null')if (user?.id) {// Safe to call — SDK queues this if the tracker isn't ready yetlinkUser(String(user.id), user.email)}}, [])return children}
Placement rule
Your auth context must be rendered inside NohmoNextProvider, not outside or alongside it. The hook useNohmo() only works within the provider tree.
// ✅ Correct<NohmoNextProvider projectId={...} apiKey={...}><AuthProvider> {/* useNohmo() works here */}{children}</AuthProvider></NohmoNextProvider>// ❌ Wrong — useNohmo() returns no-op functions here<AuthProvider><NohmoNextProvider projectId={...} apiKey={...}>{children}</NohmoNextProvider></AuthProvider>
Cross-device recognition
Once a user is linked on any device, Nohmo stores their identity server-side. If the same user opens a new browser or device and calls linkUser() with the same ID, the devices are merged into the same user profile automatically.
USER_LINKED event
Every call to linkUser() fires a USER_LINKED event visible in the Live feed and Device journey — so you can see exactly when a device became identified.
Troubleshooting — identified users not appearing
If you call linkUser() but users show as anonymous in the dashboard, check these in order:
| Check | How to verify |
|---|---|
| SDK version ≥ 0.3.20 | Run npm list nohmo — upgrade if below 0.3.20 |
| await linkUser() | Make sure you await the call — fire-and-forget loses the response |
| NohmoNextProvider wraps auth context | useNohmo() must be called inside the provider tree |
| API key is correct | Check Network tab — look for POST /api/tracker/link-user/ returning 200 |
| userId is a non-empty string | Numbers must be cast with String(user.id) |