Nuxt uses @nexafon/vue. Importing it on the server never touches window; install the plugin on
the client only and render the components inside <ClientOnly>.
npm install @nexafon/vue @nexafon/node1. Runtime config
Section titled “1. Runtime config”The keys are Nuxt runtime config, set when the server starts: the publishable key is
runtimeConfig.public.nexafonPublishableKey (from NUXT_PUBLIC_NEXAFON_PUBLISHABLE_KEY), and the secret key is
runtimeConfig.nexafonSecretKey (from NUXT_NEXAFON_SECRET_KEY), which stays on the server and is never under
public. Declare both in nuxt.config.ts with empty defaults.
2. A client-only plugin
Section titled “2. A client-only plugin”The .client.ts suffix keeps the plugin out of server rendering. It reads the publishable key from the public runtime
config:
import { createNexafon } from '@nexafon/vue'
export default defineNuxtPlugin((nuxtApp) => { const { nexafonPublishableKey } = useRuntimeConfig().public nuxtApp.vueApp.use( createNexafon({ publishableKey: nexafonPublishableKey, 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() }, }), )})3. A server route for tokens
Section titled “3. A server route for tokens”POST /api/nexafon-token signs a user token with the secret key from the server runtime config:
import { createUserToken } from '@nexafon/node'import type { H3Event } from 'h3'
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: NUXT_EXAMPLE_DEMO_USER_EMAIL is a demo user for local runs only, so replace this with your// session.function signedInUser(event: H3Event): SignedInUser | null { const { exampleDemoUserEmail, exampleDemoUserRole } = useRuntimeConfig(event) if (!exampleDemoUserEmail) return null return { id: 'demo-user-1', name: 'Demo User', role: exampleDemoUserRole || 'agent', email: exampleDemoUserEmail, // 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 the workspace secret key.export default defineEventHandler((event) => { const user = signedInUser(event) if (!user) throw createError({ statusCode: 401 }) if (!mayUseNexafon(user)) throw createError({ statusCode: 403 }) const config = useRuntimeConfig(event) setResponseHeaders(event, { 'content-type': 'text/plain', 'cache-control': 'no-store' }) return 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, }, { secretKey: config.nexafonSecretKey }, )})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 NUXT_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.
4. The page
Section titled “4. The page”<script setup lang="ts">import { CallButton, Inbox, Softphone } from '@nexafon/vue'
useHead({ title: 'Nexafon with Nuxt' })</script>
<template> <ClientOnly> <main class="support"> <Inbox style="height: 640px" /> <aside> <CallButton phone="+252615550142" /> <Softphone /> </aside> </main> </ClientOnly></template>
<style>body { margin: 0; font-family: system-ui, sans-serif; }.support { display: grid; gap: 16px; padding: 16px; grid-template-columns: 1fr 340px; }</style>The full example is examples/nuxt.