Next.js
@nexafon/react works in the app router: every export is marked 'use client' and nothing touches window during
server rendering.
npm install @nexafon/react @nexafon/node1. A route handler for tokens
Section titled “1. A route handler for tokens”The secret key stays on the server, in a route handler (NEXAFON_SECRET_KEY in the server’s environment, never a
NEXT_PUBLIC_ variable):
import { createUserToken } from '@nexafon/node'
interface SignedInUser { id: string name: string role: string email: string emailVerified: boolean isStaff: boolean}
// Your app's own session decides who is signed in, and its own records whether it has confirmed their email. The// example has no sign-in: EXAMPLE_DEMO_USER_EMAIL is a demo user for local runs only, so replace this with your session.function signedInUser(_req: Request): SignedInUser | null { const email = process.env.EXAMPLE_DEMO_USER_EMAIL if (!email) return null return { id: 'demo-user-1', name: 'Demo User', role: process.env.EXAMPLE_DEMO_USER_ROLE || 'agent', email, // The demo address is a member you invited, so you have confirmed it. emailVerified: true, isStaff: true, }}
// Everyone who gets a token may reach your workspace's inbox and calls: sign only for your staff, never for customers// who signed up. The example's only user is the demo teammate.function mayUseNexafon(user: SignedInUser): boolean { return user.isStaff}
// Signs a token for the signed-in user with NEXAFON_SECRET_KEY.export function POST(req: Request) { const user = signedInUser(req) if (!user) return new Response(null, { status: 401 }) if (!mayUseNexafon(user)) return new Response(null, { status: 403 }) const token = createUserToken({ userId: user.id, name: user.name, role: user.role, email: user.email, // Nexafon links a first sign-in to the member with this email only when it is verified: never send true for an // address your app has not confirmed, or whoever sets a member's email on your app acts as that member. emailVerified: user.emailVerified, }) return new Response(token, { headers: { 'content-type': 'text/plain', 'cache-control': 'no-store' }, })}signedInUser stands for your app’s own session: answer 401 when nobody is signed in. The example has no sign-in,
so on a local run it signs for the demo member named in EXAMPLE_DEMO_USER_EMAIL and answers 401 without one;
replace it with your session. mayUseNexafon answers 403 to signed-in users who must not use Nexafon: sign only for
your staff, never for customers who signed up, since a token can make its user a member of your workspace. The example
treats its demo user as staff.
2. The provider in a client component
Section titled “2. The provider in a client component”getUserToken is a function, so render <NexafonProvider> from a client component ('use client'):
'use client'
import { CallButton, Inbox, NexafonProvider, Softphone } from '@nexafon/react'
// getUserToken is a function, so the provider is rendered from a client component.const getUserToken = async () => { const r = await fetch('/api/nexafon-token', { method: 'POST' }) if (!r.ok) throw new Error(`The token endpoint answered ${r.status}`) return r.text()}
export function Support({ publishableKey }: { publishableKey: string }) { return ( <NexafonProvider publishableKey={publishableKey} getUserToken={getUserToken}> <main style={{ display: 'grid', gap: 16, padding: 16, gridTemplateColumns: '1fr 340px' }}> <Inbox style={{ height: 640 }} /> <aside> <CallButton phone="+252615550142" /> <Softphone /> </aside> </main> </NexafonProvider> )}3. The page
Section titled “3. The page”The page is a server component. It reads NEXAFON_PUBLISHABLE_KEY on each request (dynamic = 'force-dynamic'), so
one build serves any key, and passes it to the client component:
import { Support } from './support'
// Read on each request, so one build serves any key.export const dynamic = 'force-dynamic'
export default function Page() { return <Support publishableKey={process.env.NEXAFON_PUBLISHABLE_KEY ?? ''} />}Use the React components and hooks inside it as usual. The full example is examples/next.