How-to

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.

Applies toWhere:Web app

@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

Terminal
pnpm add @bugmojo/react
# or: npm install @bugmojo/react
# or: yarn add @bugmojo/react

react 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.

app/providers.tsx
'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>
  );
}
app/layout.tsx
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

tsx
'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&apos;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

tsx
'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.

pages/_app.tsx
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.

main.tsx
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

PropTypeWhat it does
projectIdstringPublic project id (embed token). Required, or pass embedToken.
embedTokenstringAlias of projectId.
apiBasestringAPI origin. Defaults to https://www.bugmojo.com.
envstringEnvironment label forwarded to remote config.
releasestringRelease or version, used for gating and attached to reports.
noncestringCSP nonce for the Shadow-DOM <style> the widget injects.
positionWidgetPositionFloating launcher position.
screenshotScreenshotMode'none' (derived from rrweb, the default), 'html2canvas' or 'displaymedia'.
userWidgetUserInitial reporter identity.
customDataRecord<string, unknown>Metadata attached to every submission.
showLauncherbooleanMount the floating UI. Default true; false runs the SDK headless.
captureInteractionsboolean | ComponentAnnotatorConfigComponent-aware interaction annotations. Default true; false disables them, or pass an object to tune sampling and caps.
onOpen / onClose / onSubmitcallbacksLifecycle 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.

Was this helpful?

Related articles