Skip to content

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

Terminal window
npm install @nexafon/vue @nexafon/node

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.

The .client.ts suffix keeps the plugin out of server rendering. It reads the publishable key from the public runtime config:

examples/nuxt/app/plugins/nexafon.client.ts
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()
},
}),
)
})

POST /api/nexafon-token signs a user token with the secret key from the server runtime config:

examples/nuxt/server/api/nexafon-token.post.ts
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.

examples/nuxt/app/app.vue
<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.