Install guides / Next.js
Bug reports from your Next.js app
Brainbox puts a feedback button in your app. A user marks what broke on a screenshot, or records what they did, and says what happened. You get a report with the page, the browser, the viewport and the console errors, in your Brainbox inbox and in GitHub Issues or Azure DevOps if you connect them.
There are two ways in. The script tag needs nothing installed. The React components know who is signed in, so every report says who sent it.
The script tag, in your root layout
Add it once in app/layout.tsx and it is on every page. Your project key is in the dashboard; it is public by design, since it ships in your HTML.
import Script from "next/script"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://app.brainbox.sh/widget.js"
data-project="pk_your_project_key"
data-endpoint="https://app.brainbox.sh/ingest"
strategy="afterInteractive"
/>
</body>
</html>
)
}afterInteractive loads it once the page is interactive, so it never holds up your first render. The launcher is all that loads up front: the code that takes screenshots and recordings is fetched only when someone starts a report.
On the Pages Router
The same <Script> goes in pages/_app.tsx, beside your page component.
import type { AppProps } from "next/app"
import Script from "next/script"
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<Script
src="https://app.brainbox.sh/widget.js"
data-project="pk_your_project_key"
data-endpoint="https://app.brainbox.sh/ingest"
strategy="afterInteractive"
/>
</>
)
}React components, with the signed-in user
Install the widget and the two packages it sits on. They need React 19, which Next.js 15 and later use.
npm install @ganeshdole/brainbox-widget @ganeshdole/brainbox-react @ganeshdole/brainbox-coreThe provider and the widget are client components, so they live in a file of their own marked "use client". Make the transport once, outside render: a new transport means a new Brainbox.
"use client"
import type { ReactNode } from "react"
import { brainboxTransport } from "@ganeshdole/brainbox-core"
import { BrainboxProvider } from "@ganeshdole/brainbox-react"
import { BrainboxWidget } from "@ganeshdole/brainbox-widget"
const transport = brainboxTransport({
endpoint: "https://app.brainbox.sh/ingest",
projectKey: "pk_your_project_key",
})
type User = { id: string; email: string; name: string }
export function Feedback({ user, children }: { user: User | null; children: ReactNode }) {
return (
<BrainboxProvider transport={transport} identity={user ?? undefined}>
{children}
<BrainboxWidget />
</BrainboxProvider>
)
}import { Feedback } from "./feedback"
import { getUser } from "@/lib/auth" // however your app knows who is signed in
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const user = await getUser()
return (
<html lang="en">
<body>
<Feedback user={user}>{children}</Feedback>
</body>
</html>
)
}Server rendering and your styles
Nothing runs on the server. The provider starts Brainbox in an effect and the widget renders nothing until then, so a server-rendered layout stays side-effect free. Your server components pass through <Feedback> as children and stay server components.
The widget draws into a shadow root on <body>, so Tailwind, your CSS reset and your global styles cannot reach it, and it cannot reach them.
Your own button
Under the provider, useBrainboxWidget() opens the widget from anywhere. Use <BrainboxWidget trigger="manual" /> to hide our floating button and keep only yours.
"use client"
import { useBrainboxWidget } from "@ganeshdole/brainbox-widget"
export function ReportProblemButton() {
const { open } = useBrainboxWidget()
return <button onClick={open}>Report a problem</button>
}With the script tag, any element marked data-brainbox opens it, even in a server component: screenshot goes straight to marking up the page, record to a recording and feature to a feature request.
<button data-brainbox="screenshot">Report a bug</button>Details from your app
Pass fields to the provider for whatever you want beside every report, such as the plan or the account ID. They show in the report under the person who sent it, with your labels, in your order.
<BrainboxProvider
transport={transport}
identity={user ?? undefined}
fields={[
{ label: "Plan", value: account.plan, highlight: true },
{ label: "Account ID", value: account.id, type: "id" },
]}
>Passwords and personal data
Typed values are always masked in recordings, so passwords, emails and card numbers never leave the browser as plain text. Add the rr-block or rr-mask class to anything else you want kept out.
Only on preview deployments
To try it with your team before your users see it, render it only outside production. On Vercel, VERCEL_ENV is production, preview or development, and a server component can read it.
const showFeedback = process.env.VERCEL_ENV !== "production"
// in RootLayout's <body>:
{showFeedback && (
<Script
src="https://app.brainbox.sh/widget.js"
data-project="pk_your_project_key"
data-endpoint="https://app.brainbox.sh/ingest"
strategy="afterInteractive"
/>
)}Try it before you install it
The button in the corner of this page is the real widget. Report something on this page and it lands in our inbox, the way your users’ reports will land in yours.
Get started