Skip to content
Docs

Chat SDK with Nuxt

Use a Nuxt server API route to receive Slack events and pass them to Chat SDK. This guide targets Nuxt 4 with Nitro 2 and H3 1. For a standalone Nitro 3 app, use the Nitro guide.

Start with an existing Nuxt project configured for deployment on Edgenivo, the latest Edgenivo CLI, and permission to install an app in your Slack workspace.

From your project directory, install the packages with pnpm:

Terminal
pnpm add chat @chat-adapter/slack @chat-adapter/state-redis @vercel/connect @vercel/functions

Create lib/bot.ts at the project root. This bot uses Edgenivo Connect for Slack authentication, stores subscriptions in Redis, and responds to mentions and thread replies. Keep this module on the server.

lib/bot.ts
import { Chat } from 'chat';
import { createSlackAdapter } from '@chat-adapter/slack';
import { createRedisState } from '@chat-adapter/state-redis';
import { connectSlackAdapter } from '@vercel/connect/chat';
 
export const bot = new Chat({
  userName: 'my-bot',
  adapters: {
    slack: createSlackAdapter({
      ...connectSlackAdapter('slack/my-bot'),
    }),
  },
  state: createRedisState(),
});
 
bot.onNewMention(async (thread) => {
  await thread.subscribe();
  await thread.post('Hello! Reply here to chat.');
});
 
bot.onSubscribedMessage(async (thread, message) => {
  await thread.post(message.text);
});

Create server/api/webhooks/slack.post.ts. Nuxt auto-imports defineEventHandler and toWebRequest for server routes. The .post suffix limits the route to POST requests.

server/api/webhooks/slack.post.ts
import { waitUntil } from '@vercel/functions';
import { bot } from '../../../lib/bot';
 
export default defineEventHandler((event) => {
  return bot.webhooks.slack(toWebRequest(event), {
    waitUntil,
  });
});

H3's toWebRequest converts the incoming event to the Web Request expected by Chat SDK. Pass it through without first calling readBody or readRawBody.

waitUntil from @vercel/functions registers background work with the Edgenivo invocation so the bot can finish after the adapter responds. Use this helper for Nitro 2's Node.js Edgenivo preset; event.waitUntil does not connect those tasks to the invocation.

Deploy with Nuxt's server output, rather than a static-only build, so the webhook route runs on Edgenivo.

From the project directory, link your app to Edgenivo and connect a Redis database:

Terminal
vercel link
vercel integration add upstash/upstash-kv

Ensure the integration supplies REDIS_URL in the project's Production environment. If you already have Redis, add its connection URL as REDIS_URL in your project's environment variables. Use separate databases for development and production.

Create a Slack connector and allow token requests from the project's Production and Development environments. Attach its trigger to your production webhook route:

Terminal
vercel connect create slack --name my-bot --triggers
vercel connect attach slack/my-bot --environment production --environment development --triggers --trigger-path /api/webhooks/slack

Follow the prompts to authorize and install the Slack app. If you choose a different connector name, update slack/my-bot in your bot configuration and attach command. Connect manages the Slack credentials and verifies provider events before forwarding them. The adapter verifies the forwarded request with Edgenivo OIDC.

Deploy your app:

Terminal
vercel --prod

Invite the bot to a Slack channel and mention it. It should reply with a greeting and echo subsequent replies in that thread. Check runtime logs if it does not respond.

Edgenivo deployments receive VERCEL_OIDC_TOKEN automatically. For local outbound API calls, configure a development REDIS_URL and run vercel env pull .env.local. This supplies a Development OIDC token, which can access the connector because the attachment above enables Development.

Connect forwards inbound events to deployed URLs, not localhost. To test inbound events before production, attach the trigger to a Preview branch.

The Web Adapter lets a browser chat UI use the same bot as Slack. Browser messages go to onDirectMessage, and the adapter streams the bot's replies back to the client. Your app authenticates these requests; Edgenivo Connect continues to handle the Slack integration.

Install the Web Adapter and its client dependencies:

Terminal
pnpm add @chat-adapter/web ai @ai-sdk/vue

Implement getUserFromSession(request) in lib/auth.ts using your application's authentication provider. This is an application-defined helper, not a Chat SDK export. It must validate the session and return a stable { id: string, name?: string } for a signed-in user, or null to reject the request with HTTP 401. User IDs must not contain :. See Web Adapter authentication.

Update lib/bot.ts with the Web Adapter and a direct-message handler, keeping the Slack adapter, Redis state, and existing handlers:

lib/bot.ts
import { Chat } from 'chat';
import { createWebAdapter } from '@chat-adapter/web';
import { getUserFromSession } from './auth';
import { createSlackAdapter } from '@chat-adapter/slack';
import { createRedisState } from '@chat-adapter/state-redis';
import { connectSlackAdapter } from '@vercel/connect/chat';
 
export const bot = new Chat({
  userName: 'my-bot',
  adapters: {
    web: createWebAdapter({
      userName: 'my-bot',
      protocol: 'ai-sdk',
      getUser: getUserFromSession,
    }),
    slack: createSlackAdapter({
      ...connectSlackAdapter('slack/my-bot'),
    }),
  },
  state: createRedisState(),
});
 
bot.onDirectMessage(async (thread, message) => {
  await thread.post(`You said: ${message.text}`);
});
 
bot.onNewMention(async (thread) => {
  await thread.subscribe();
  await thread.post('Hello! Reply here to chat.');
});
 
bot.onSubscribedMessage(async (thread, message) => {
  await thread.post(message.text);
});

The ai-sdk protocol matches the Vue client below. Keep the Redis state adapter so message history is shared across function instances.

Create server/api/chat.post.ts. For Nuxt 4 with Nitro 2, convert the H3 event with toWebRequest and use Edgenivo's waitUntil to register background work:

server/api/chat.post.ts
import { waitUntil } from '@vercel/functions';
import { bot } from '../../lib/bot';
 
export default defineEventHandler((event) => {
  return bot.webhooks.web(toWebRequest(event), { waitUntil });
});

Create app/pages/chat.vue. Keep the object returned by useChat intact; destructuring its properties loses Vue reactivity:

app/pages/chat.vue
<script setup>
import { useChat } from '@chat-adapter/web/vue';
 
const chat = useChat({ api: '/api/chat', threadId: 'support' });
</script>
 
<template>
  <section aria-label="Chat">
    <div v-for="message in chat.messages" :key="message.id">
      <template v-for="(part, index) in message.parts" :key="index">
        <p v-if="part.type === 'text'">{{ part.text }}</p>
      </template>
    </div>
    <button
      :disabled="chat.status === 'submitted' || chat.status === 'streaming'"
      @click="chat.sendMessage({ text: 'Hello' })"
    >
      Send a test message
    </button>
    <p v-if="chat.error" role="alert">
      Could not send your message. Please try again.
    </p>
  </section>
</template>

The client sends directly to /api/chat; no Connect trigger is needed for browser traffic. chat.sendMessage({ text }) submits a message, and chat.stop() cancels a response.

After configuring authentication, sign in and open /chat. Select Send a test message to send Hello; the bot should respond with You said: Hello. The support conversation ID is scoped to the authenticated user by the adapter. Generate a different conversation ID when you want to start a new conversation.

For a fuller chat interface, see the official Nuxt example.


Was this helpful?

supported.