nuxt-livekit
nuxt-livekit

LiveKit integration for Nuxt: realtime video, audio, and data with client composables and server utilities

nuxt-livekit

nuxt-livekit

npm versionnpm downloadsLicenseNuxt

LiveKit integration for Nuxt. Add realtime video, audio, and data features to your Nuxt application with auto-imported client composables and Nitro server utilities.

Features

  • 🔌  Auto-imported composablesuseLiveKitRoom() and useLiveKitToken() available in any component
  • 🖥️  Server utilitiesgenerateLiveKitToken(), useLiveKitRoomService(), useLiveKitWebhookReceiver() auto-imported in Nitro routes
  • ⚡  Reactive state — connection state, participants, active speakers, reconnection status, and event log
  • 🔐  Secure by default — API key/secret stay server-side via runtime config
  • 📦  Zero config — just add your LiveKit credentials and go
  • 🛡️  SSR-safe — composables are guarded to only run on the client

Compatibility

nuxt-livekitNuxtlivekit-clientlivekit-server-sdk
>=0.0.1>=3.0.0^2.x^2.x

Quick Setup

Install the module:

npx nuxi module add nuxt-livekit

That's it! The module is now installed and you can start using it.

Configuration

Add your LiveKit credentials to nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['nuxt-livekit'],
  livekit: {
    wsUrl: 'wss://your-project.livekit.cloud',
  },
})
OptionDescriptionRequired
wsUrlLiveKit server WebSocket URLYes
apiKeyLiveKit API key (server-only)Yes
apiSecretLiveKit API secret (server-only)Yes

!IMPORTANTapiKey and apiSecret are never exposed to the client. Only wsUrl is available in the browser via useRuntimeConfig().public.livekit.wsUrl.

Environment Variables

Instead of hardcoding values in nuxt.config.ts, you can use environment variables with the standard Nuxt runtime config override convention:

VariableMaps to
NUXT_LIVEKIT_WS_URLruntimeConfig.livekit.wsUrl
NUXT_LIVEKIT_API_KEYruntimeConfig.livekit.apiKey
NUXT_LIVEKIT_API_SECRETruntimeConfig.livekit.apiSecret
NUXT_PUBLIC_LIVEKIT_WS_URLruntimeConfig.public.livekit.wsUrl

Create a .env file in your project root:

NUXT_LIVEKIT_WS_URL=wss://your-project.livekit.cloud
NUXT_LIVEKIT_API_KEY=your-api-key
NUXT_LIVEKIT_API_SECRET=your-api-secret

Usage

Client-side

useLiveKitRoom(options?)

Manages a LiveKit Room instance with reactive state. The room is automatically disconnected and cleaned up when the component is unmounted.

<script setup>
const {
  room,               // ShallowRef<Room>
  connectionState,    // Ref<ConnectionState>
  remoteParticipants, // ShallowRef<RemoteParticipant[]>
  localParticipant,   // ShallowRef<LocalParticipant>
  activeSpeakers,     // ShallowRef<Participant[]>
  isReconnecting,     // Ref<boolean>
  error,              // Ref<string | null>
  events,             // Ref<LiveKitRoomEvent[]>
  connect,            // (token: string, url?: string) => Promise<void>
  disconnect,         // () => Promise<void>
  clearEvents,        // () => void
} = useLiveKitRoom({ adaptiveStream: true, dynacast: true })

await connect(token)
</script>

Options: Accepts all RoomOptions from livekit-client (e.g. adaptiveStream, dynacast, videoCaptureDefaults).

useLiveKitToken(endpoint?)

Fetches a token from your server API route:

<script setup>
const { token, wsUrl, error, pending, fetchToken } = useLiveKitToken('/api/livekit/token')

const response = await fetchToken({ roomName: 'my-room', participantName: 'user-1' })
</script>

Server-side (Nitro)

All server utilities are auto-imported in your server/ routes.

generateLiveKitToken(identity, roomName, options?)

Generate a JWT token in one line:

// server/api/livekit/token.post.ts
export default defineEventHandler(async (event) => {
  const { roomName, participantName } = await readBody(event)

  const token = await generateLiveKitToken(participantName, roomName, {
    name: participantName,
    grants: { canPublish: true, canSubscribe: true },
  })

  return { token }
})

useLiveKitAccessToken(identity, options?)

Lower-level — returns an AccessToken instance for full control:

const at = useLiveKitAccessToken('user-1', { ttl: '2h' })
at.addGrant({ roomJoin: true, room: 'my-room', canPublish: false })
const token = await at.toJwt()

useLiveKitRoomService(host?)

Returns a RoomServiceClient for managing rooms:

const svc = useLiveKitRoomService()
const rooms = await svc.listRooms()
await svc.deleteRoom('old-room')

useLiveKitWebhookReceiver()

Returns a WebhookReceiver for verifying incoming LiveKit webhooks:

// server/api/livekit/webhook.post.ts
export default defineEventHandler(async (event) => {
  const receiver = useLiveKitWebhookReceiver()
  const body = await readRawBody(event)
  const authHeader = getHeader(event, 'authorization')
  const webhookEvent = await receiver.receive(body!, authHeader!)
  // Handle webhookEvent...
  return { ok: true }
})

Full Example

A complete flow from joining a room to rendering participants:

<script setup>
const roomName = ref('my-room')
const userName = ref('')

const { token, fetchToken } = useLiveKitToken()
const {
  connectionState,
  remoteParticipants,
  localParticipant,
  connect,
  disconnect,
} = useLiveKitRoom({ adaptiveStream: true, dynacast: true })

async function joinRoom() {
  const { token: t, wsUrl } = await fetchToken({
    roomName: roomName.value,
    participantName: userName.value,
  })
  await connect(t, wsUrl)
}
</script>

<template>
  <div>
    <div v-if="connectionState !== 'connected'">
      <input v-model="userName" placeholder="Your name" />
      <button @click="joinRoom">Join</button>
    </div>
    <div v-else>
      <p>Connected as {{ localParticipant?.identity }}</p>
      <p>{{ remoteParticipants.length }} other participant(s)</p>
      <button @click="disconnect">Leave</button>
    </div>
  </div>
</template>

Contributing

Local development
# Install dependencies
pnpm install

# Generate type stubs
pnpm dev:prepare

# Develop with the playground
pnpm dev

# Build the playground
pnpm dev:build

# Run ESLint
pnpm lint
pnpm lint:fix

# Run Vitest
pnpm test
pnpm test:watch

# Type check
pnpm test:types

# Release new version
pnpm release

License

MIT