Install the React SDK for React and Next.js
Add @bugmojo/react to mount the BugMojo widget, catch render errors with their component stack, and file bugs straight from your code.
@bugmojo/react is the official BugMojo SDK for React and Next.js (App Router and Pages Router). On top of the widget's capture, it adds what only React can tell you: the component stack that broke. Unhandled render errors and programmatic report() calls are filed with environment metadata, breadcrumbs and the component trail, so you, or your AI coding agent, know exactly where to look.
It is safe with SSR and React Server Components, and every code path degrades to a safe no-op, so it never crashes your app.
Package: @bugmojo/react on npm · Full reference: the README on GitHub.
Install
pnpm add @bugmojo/react
# or: npm install @bugmojo/react
# or: yarn add @bugmojo/reactreact and react-dom 18 or newer are peer dependencies.
Find your project token
The provider needs your projectId, the public embed token for your project. Copy it from Project Settings → Widget in the BugMojo dashboard at www.bugmojo.com. It is the same value the script-tag widget uses in data-project, and the provider also accepts it as embedToken.
Set up Next.js (App Router)
Create a client component that wraps your app in the provider, then render it from your root layout.
'use client';
import { BugMojoProvider } from '@bugmojo/react';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<BugMojoProvider
projectId={process.env.NEXT_PUBLIC_BUGMOJO_PROJECT_ID!}
// Optional: defaults to https://www.bugmojo.com — set for self-hosted installs.
apiBase={process.env.NEXT_PUBLIC_BUGMOJO_API_BASE}
env={process.env.NODE_ENV}
release={process.env.NEXT_PUBLIC_APP_VERSION}
>
{children}
</BugMojoProvider>
);
}import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Only projectId is required. apiBase defaults to https://www.bugmojo.com, so leave it unset unless you self-host.
Catch render errors
'use client';
import { BugMojoErrorBoundary } from '@bugmojo/react';
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<BugMojoErrorBoundary
name="dashboard"
fallback={({ reset }) => (
<div role="alert">
<p>Something went wrong. We've been notified.</p>
<button onClick={reset}>Try again</button>
</div>
)}
>
{children}
</BugMojoErrorBoundary>
);
}When any child throws during render, BugMojo files a bug carrying the error and the React componentStack, then renders your fallback. Your app keeps running. fallback can be a static node or a render function that receives { error, reset }. name labels the report (a route name, for example), onError runs after the report is filed, and disableReporting turns the automatic report off if you would rather handle it in onError.
Identify users and file bugs from code
'use client';
import { useBugMojo } from '@bugmojo/react';
export function SupportButton() {
const { report, identify, track } = useBugMojo();
// Attach who the user is (also used for cohort-based capture sampling).
// identify({ id: user.id, email: user.email, name: user.name });
async function fileBug() {
track('support_clicked');
const result = await report({
title: 'Checkout is stuck on the spinner',
description: 'Clicked Pay, nothing happened.',
});
if (result.ok) {
// result.number is the created bug number.
}
}
return <button onClick={fileBug}>Report a problem</button>;
}useBugMojo() returns { ready, report, identify, setUser, setRelease, track, open, close, api }. report() files a bug without opening the UI and always resolves. track() records a breadcrumb, and the last 50 go along with each report. open() and close() drive the floating panel. The hook is safe to call outside a provider: every method becomes a no-op and report() resolves to { ok: false }.
Pages Router and plain React
Next.js Pages Router
Wrap <Component /> once in pages/_app.tsx. You don't need 'use client', because the Pages Router has no Server Component boundary.
import type { AppProps } from 'next/app';
import { BugMojoProvider, BugMojoErrorBoundary } from '@bugmojo/react';
export default function App({ Component, pageProps }: AppProps) {
return (
<BugMojoProvider projectId={process.env.NEXT_PUBLIC_BUGMOJO_PROJECT_ID!}>
<BugMojoErrorBoundary fallback={<p>Something went wrong.</p>}>
<Component {...pageProps} />
</BugMojoErrorBoundary>
</BugMojoProvider>
);
}Vite or Create React App
Wrap your root component in main.tsx (or index.tsx). The 'use client' directives above are Next.js App Router markers. Outside the App Router they do nothing, so you can drop them.
import { createRoot } from 'react-dom/client';
import { BugMojoProvider, BugMojoErrorBoundary } from '@bugmojo/react';
import App from './App';
createRoot(document.getElementById('root')!).render(
<BugMojoProvider projectId={import.meta.env.VITE_BUGMOJO_PROJECT_ID}>
<BugMojoErrorBoundary fallback={<p>Something went wrong.</p>}>
<App />
</BugMojoErrorBoundary>
</BugMojoProvider>,
);Provider options
| Prop | Type | What it does |
|---|---|---|
projectId | string | Public project id (embed token). Required, or pass embedToken. |
embedToken | string | Alias of projectId. |
apiBase | string | API origin. Defaults to https://www.bugmojo.com. |
env | string | Environment label forwarded to remote config. |
release | string | Release or version, used for gating and attached to reports. |
nonce | string | CSP nonce for the Shadow-DOM <style> the widget injects. |
position | WidgetPosition | Floating launcher position. |
screenshot | ScreenshotMode | 'none' (derived from rrweb, the default), 'html2canvas' or 'displaymedia'. |
user | WidgetUser | Initial reporter identity. |
customData | Record<string, unknown> | Metadata attached to every submission. |
showLauncher | boolean | Mount the floating UI. Default true; false runs the SDK headless. |
captureInteractions | boolean | ComponentAnnotatorConfig | Component-aware interaction annotations. Default true; false disables them, or pass an object to tune sampling and caps. |
onOpen / onClose / onSubmit | callbacks | Lifecycle hooks. |
SSR and React Server Components
The provider, the error boundary and the hook are Client Components ('use client'), so import them from client components or client boundaries. Nothing touches browser globals during render or module load: the widget bundle, rrweb included, is imported dynamically inside a useEffect, so it never runs on the server and stays out of your server bundle. Before hydration, useBugMojo() returns the no-op context, so early calls are safe.
Privacy
The provider mounts the BugMojo widget, which redacts PII from console and network data in the browser, before anything is uploaded. With the default screenshot: 'none', the screenshot is derived from the rrweb snapshot, so masking has already been applied. Component annotations (which components the user clicked or typed into just before the bug) travel as compact metadata alongside the report and are never injected into the rrweb recording.
Read more about client-side PII redaction, and see disclose the BugMojo widget to your visitors for privacy-policy wording.
Common questions
Why doesn't the launcher appear?
The most common cause is a missing projectId: the SDK deliberately does nothing instead of crashing. In development, look for "[BugMojo] Missing projectId/embedToken — SDK will no-op." in the console, and check that the variable is exposed to the browser (NEXT_PUBLIC_… in Next.js, VITE_… in Vite).
Why does report() resolve to { ok: false }?
Either no projectId is configured, or the hook is being called outside a BugMojoProvider, where every method is a safe no-op. The error field on the result tells you which.
Is the projectId a secret?
No. It is the public embed token, the same value the script-tag widget uses in data-project, and it is safe to ship to the browser.
Will the SDK slow down or break my app?
The widget core, including rrweb, is lazy-loaded with a dynamic import() after mount, so it stays out of your initial and server bundles. If it fails to load (CSP, offline, an ad-blocker), the SDK stays in a headless no-op state and your app keeps working.
Why does Jest fail with 'Cannot use import statement outside a module'?
The package is ESM-only. Run Jest in ESM mode, or map @bugmojo/react to a mock in unit tests. Vitest consumes it without configuration.
Related articles
- Embed the feedback widget on your websiteAdd one script tag, or the @bugmojo/widget npm package, so visitors can report bugs with a session replay and console and network logs attached.
- Install the React Native and Expo SDKAdd @bugmojo/react-native so users can shake their phone to report a bug, with console and network data redacted on the device before upload.
- Use the BugMojo CLI to reproduce and verify fixesTurn a captured bug into a runnable Playwright repro, verify the fix against a real deploy, and brief your AI agent from your terminal or CI.
- Redact PII on the client before it leaves the browserControl what personal data the extension masks, so sensitive text never leaves the browser in a bug report.
- Disclose the BugMojo widget to your visitorsWhat the always-on widget buffer does, when data is uploaded, and copy you can paste into your own privacy policy.

