This guide outlines how to integrate the Userorbit SDK into a Next.js application, covering both App Router and Pages Router initialization, user identification post-authentication, and steps to verify the successful setup.
Initialize userorbit-js from one persistent Client Component. Mount that component from the App Router root layout or the Pages Router custom App, then identify the signed-in person after authentication is ready. Pass only the public Account ID and the minimum user and account fields needed for targeting across the client boundary.
Before you begin
You need:
- a Next.js application using App Router, Pages Router, or both
- the public Account ID from Userorbit → Settings → Widget
- a stable application user ID and, for B2B applications, a stable account or workspace ID
- a staging route and test account
- a published tour or checklist if you want to verify delivery
The Account ID is safe to expose in browser code. Do not expose API keys, access tokens, signing secrets, or server-only session data.
1. Install the package
Use the package manager already used by your application:
npm install userorbit-js
Bash
pnpm add userorbit-js
Bash
Add the public Account ID to your client environment:
NEXT_PUBLIC_USERORBIT_ACCOUNT_ID=YOUR_PUBLIC_ACCOUNT_ID
Bash
Next.js includes NEXT_PUBLIC_ variables in the client bundle. That is appropriate for the Userorbit Account ID, but never for a secret.
2. Create one persistent Client Component
Create components/userorbit-client.tsx:
"use client"
import { useEffect } from "react"
import userorbit from "userorbit-js"
const accountId = process.env.NEXT_PUBLIC_USERORBIT_ACCOUNT_ID
let sdkReady: Promise<void> | undefined
export function ensureUserorbit() {
if (!accountId) {
return Promise.reject(
new Error("Missing NEXT_PUBLIC_USERORBIT_ACCOUNT_ID"),
)
}
sdkReady ??= userorbit.init({
accountId,
apiHost: "https://api.userorbit.com",
floatingWidget: true,
})
return sdkReady
}
export type UserorbitIdentity = {
id: string
email?: string
name?: string
role?: string
accountId: string
accountName?: string
accountPlan?: string
}
export function UserorbitClient({ user }: { user?: UserorbitIdentity }) {
useEffect(() => {
void ensureUserorbit()
}, [])
useEffect(() => {
if (!user?.id) return
void ensureUserorbit().then(() =>
userorbit.identify(user.id, {
email: user.email,
name: user.name,
attributes: {
"user.role": user.role,
"account.id": user.accountId,
"account.name": user.accountName,
"account.plan": user.accountPlan,
},
}),
)
}, [
user?.id,
user?.email,
user?.name,
user?.role,
user?.accountId,
user?.accountName,
user?.accountPlan,
])
return null
}
export async function logoutUserorbit() {
await userorbit.logout()
sdkReady = undefined
}
TSX
The module-level promise deduplicates initialization during React Strict Mode's extra development cycle. Userorbit remains entirely behind a browser boundary.
The current web SDK identifies a person. It does not expose a separate company or group identification method. Use stable custom attributes such as account.id, account.name, account.plan, and user.role to describe the active B2B tenant.
Call logoutUserorbit() from your application's real sign-out path. If a person switches tenants without signing out, pass the updated account fields so identify() runs again.
3. Mount it with App Router
Mount the Client Component from a layout that persists across the routes where Userorbit should run. A Server Component can resolve authentication, but only pass the minimum serializable fields needed by the browser component.
import { UserorbitClient } from "@/components/userorbit-client"
import { getCurrentUser } from "@/lib/auth"
export default async function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
const session = await getCurrentUser()
const user = session
? {
id: session.user.id,
email: session.user.email,
name: session.user.name,
role: session.membership.role,
accountId: session.account.id,
accountName: session.account.name,
accountPlan: session.account.plan,
}
: undefined
return (
<html lang="en">
<body>
<UserorbitClient user={user} />
{children}
</body>
</html>
)
}
TSX
The authentication names above are examples. Map them to your own session model. Do not serialize access tokens or server-only authorization state into the component.
4. Mount it with Pages Router
Render the same component from pages/_app.tsx so it survives client-side navigation:
import type { AppProps } from "next/app"
import { UserorbitClient } from "@/components/userorbit-client"
import { useSession } from "@/lib/use-session"
export default function App({ Component, pageProps }: AppProps) {
const session = useSession()
const user = session
? {
id: session.user.id,
email: session.user.email,
name: session.user.name,
role: session.membership.role,
accountId: session.account.id,
accountName: session.account.name,
accountPlan: session.account.plan,
}
: undefined
return (
<>
<UserorbitClient user={user} />
<Component {...pageProps} />
</>
)
}
TSX
Do not call init() from getServerSideProps, a Server Component, or another server-only module.
5. Verify route changes before adding a fallback
The current SDK observes common History API, popstate, and hash changes. App Router and Pages Router client navigation should therefore be tested before adding manual route notifications.
Use Analytics → Live Events and navigate between several real application routes. Each real navigation should produce one $uo_web_page_view with the expected URL.
If a real route is missing, add a narrow fallback.
For App Router:
"use client"
import { useEffect } from "react"
import { usePathname, useSearchParams } from "next/navigation"
import userorbit from "userorbit-js"
export function UserorbitRouteFallback() {
const pathname = usePathname()
const searchParams = useSearchParams().toString()
useEffect(() => {
void userorbit.registerRouteChange()
}, [pathname, searchParams])
return null
}
TSX
For Pages Router:
import { useEffect } from "react"
import { useRouter } from "next/router"
import userorbit from "userorbit-js"
export function UserorbitPagesRouteFallback() {
const router = useRouter()
useEffect(() => {
const handleRoute = () => {
void userorbit.registerRouteChange()
}
router.events.on("routeChangeComplete", handleRoute)
return () => router.events.off("routeChangeComplete", handleRoute)
}, [router.events])
return null
}
TSX
Add only the fallback for the router you actually use. If one navigation then produces two page-view events, remove it because automatic detection already covers your application.
6. Configure targeting and launch an experience
Create and publish the tour or checklist in Userorbit first. Restrict the initial test to a staging URL and a known account attribute such as account.id = acct_test.
To start a published checklist from client code:
"use client"
import userorbit from "userorbit-js"
export function OpenSetupChecklist() {
async function openChecklist() {
await ensureUserorbit()
await userorbit.startChecklist("YOUR_PUBLISHED_CHECKLIST_ID")
}
return <button onClick={openChecklist}>Open setup checklist</button>
}
TSX
Use userorbit.startTour("YOUR_PUBLISHED_TOUR_ID") for a published tour.
These methods do not create or publish an experience. If the experience does not render, confirm its ID, published state, URL rule, audience, frequency, schedule, and target element.
7. Track a configured product event
Create the code event in Userorbit before calling it from the application:
await userorbit.track("workspace_created", {
hiddenFields: {
workspaceId: workspace.id,
template: workspace.templateKey,
},
})
TypeScript
Fire the event only after the underlying product action succeeds. Keep payloads small and do not send credentials or arbitrary customer data.
8. Verify App Router and Pages Router separately
If both routers exist during a migration, test them as separate delivery surfaces.
| Check | App Router | Pages Router |
|---|---|---|
| Initialization | Root layout mounts one Client Component | _app.tsx mounts one Client Component |
| Server boundary | Only serializable user/account fields cross it | Auth hook resolves in the browser |
| Route evidence | App Router URLs appear once in Live Events | Pages Router URLs appear once in Live Events |
| Identity | Contact has the expected account.* values |
Contact has the expected account.* values |
| Delivery | Published experience renders on a target route | Published experience renders on a target route |
| Logout | Userorbit session clears with application auth | Userorbit session clears with application auth |
Also verify the shared browser layers:
userorbit.umd.cjsloads without a CSP or network error.- Settings → Widget reports recent SDK activity.
- One eligible and one ineligible staging user prove that targeting is correct.
- Tour or checklist completion and dismissal are recorded.
- A configured product event responds exactly once.
Troubleshooting
window is not defined or the SDK runs during rendering
- Confirm the integration component begins with
"use client"for App Router. - Keep SDK imports and calls out of server-only files.
- Do not call
init()fromgetServerSidePropsor a Server Component.
The SDK initializes twice in development
- Keep the module-level
sdkReadypromise. - Do not mount the client component from multiple layouts.
- Verify production behavior separately from React Strict Mode's development checks.
Navigation is missing or duplicated
- Check Live Events before adding a fallback.
- Add
registerRouteChange()only for a demonstrated missed navigation. - Remove the fallback if automatic detection and the manual hook both fire.
The wrong account receives an experience
- Pass the active
account.id, not a stale account from an earlier session. - Re-identify after tenant switching.
- Call
logout()when application auth signs out. - Match attribute keys and values exactly in the targeting rule.
The experience does not render
- Confirm it is published in the same workspace as the Account ID.
- Check URL rules against the actual client URL.
- Confirm the staging contact matches the audience.
- Wait until the target element exists.
- Check prior completion, dismissal, frequency, and schedule state.
Implementation ownership
Userorbit provides the browser SDK, person identity, custom attributes, configured events, targeting, tours, checklists, Live Events, and delivery analytics. Next.js provides the server/client and routing boundaries. Your application owns authentication, serializable identity mapping, stable IDs, tenant switching, consent, CSP, event correctness, and regression testing.
For framework-neutral details, see How to Install the Userorbit SDK in a React Application. For additional failure modes, see SDK Troubleshooting Reference in the help center.