Next.js integration
This guide walks you through dropping ReachBell into a Next.js 13+ App Router project. The setup is the React provider from @reachbell/react, marked as a client component, mounted in the root layout, with the service worker file shipped from public/.
Install
npm install @reachbell/react @reachbell/sdk
Set the API key as an env var
The browser needs the key, so it goes into a NEXT_PUBLIC_ variable. Anything prefixed with NEXT_PUBLIC_ is inlined into the client bundle at build time.
# .env.local
NEXT_PUBLIC_REACHBELL_KEY=rb_live_yourkey
The API key is a public credential. It identifies your project to the SDK and is safe to ship to the browser — it cannot be used to send pushes or mutate your project. Server-side actions use a separate secret key. Treat it like a Stripe publishable key, not a Stripe secret key.
Create the provider component
App Router defaults to server components, but @reachbell/react reads window, Notification, and other browser-only globals — so the provider has to be a client component. Create a dedicated file for it.
// app/providers.tsx
'use client';
import { ReachBellProvider } from '@reachbell/react';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ReachBellProvider
apiKey={process.env.NEXT_PUBLIC_REACHBELL_KEY!}
autoPrompt
>
{children}
</ReachBellProvider>
);
}
The "use client" directive at the top is what tells Next.js to bundle this component and everything it renders for the browser. Without it the build will fail with a useState/useEffect not allowed in server components error.
Wrap the root layout
Mount the provider once, in app/layout.tsx, around {children}.
// app/layout.tsx
import type { Metadata } from 'next';
import { Providers } from './providers';
export const metadata: Metadata = {
title: 'My app',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
The layout itself stays a server component. Only Providers and its descendants run on the client.
Drop the service worker in public/
Next.js serves anything under public/ from the site root with no rewriting. That makes public/ the right home for reachbell-sw.js.
your-project/
app/
layout.tsx
providers.tsx
public/
reachbell-sw.js <-- from the dashboard integration page
After a deploy the file should be reachable at https://yoursite.com/reachbell-sw.js. Hit that URL in your browser to verify before testing the prompt.
SSR safety
The SDK is SSR-safe out of the box. Every entry point inside @reachbell/sdk is wrapped in typeof window !== 'undefined' guards, so server-rendered passes return early without touching browser globals. The provider's effects only run on the client, so calling useReachBell() in a server component would be a build error long before runtime — keep usage inside client components.
You do not need to dynamic()-import the provider with ssr: false. The guards make that unnecessary, and skipping SSR for the whole provider would needlessly delay your tree's hydration.
Use a hook in a client component
Anywhere downstream of <Providers> can call useReachBell(). Mark the consuming component "use client" too.
// app/components/subscribe-button.tsx
'use client';
import { useReachBell } from '@reachbell/react';
export function SubscribeButton() {
const { isReady, permission, prompt } = useReachBell();
if (!isReady || permission !== 'default') return null;
return (
<button
onClick={async () => {
const r = await prompt();
if (!r.ok) console.warn('prompt failed:', r.reason);
}}
>
Get notified
</button>
);
}
Drop <SubscribeButton /> into any page or layout. It only renders when the SDK is ready and the user hasn't decided yet.
Coexisting with Sentry
If you ship the Sentry browser SDK, both can initialise on the same page with no special wiring. They don't share any globals, and neither monkey-patches the other's surface area. Order doesn't matter — initialise Sentry in app/instrumentation-client.ts as usual, and let ReachBellProvider do its thing in the React tree.
The only thing to watch for is that Sentry's Replay integration records the soft prompt by default. If your prompt design includes anything PII-shaped, add the .reachbell-prompt selector to Sentry's mask list.
Identify after sign-in
Same pattern as plain React. Put the side effect inside the provider tree, wherever your auth state becomes available.
'use client';
import { useEffect } from 'react';
import { useReachBell } from '@reachbell/react';
import { useUser } from './user-context';
export function IdentifyOnLogin() {
const { isReady, identify } = useReachBell();
const user = useUser();
useEffect(() => {
if (!isReady || !user) return;
identify(user.id);
}, [isReady, user, identify]);
return null;
}
Render it once inside <Providers> and forget about it.
What's next?
- Read Concepts for the project / subscriber model the SDK maps to.
- Build the server-side counterpart with the Subscribers API.
- Connect the same audience to a welcome automation.