Slack bot with Next.js and Redis
This guide walks through building a Slack bot with Next.js, covering project setup, Slack app configuration, event handling, interactive features, and deployment.
Prerequisites
- Node.js 18+
- pnpm (or npm/yarn)
- A Slack workspace where you can install apps
- A Redis instance for state management
Create a Next.js app
Scaffold a new Next.js project and install Chat SDK dependencies:
npx create-next-app@latest my-slack-bot --typescript --app
cd my-slack-bot
pnpm add chat @chat-adapter/slack @chat-adapter/state-redisCreate a Slack app
- Go to api.slack.com/apps
- Click Create New App then From an app manifest
- Select your workspace and paste the following manifest:
display_information:
name: My Bot
description: A bot built with Chat SDK
features:
bot_user:
display_name: My Bot
always_online: true
oauth_config:
scopes:
bot:
- app_mentions:read
- channels:history
- channels:read
- chat:write
- groups:history
- groups:read
- im:history
- im:read
- mpim:history
- mpim:read
- reactions:read
- reactions:write
- users:read
settings:
event_subscriptions:
request_url: https://your-domain.com/api/webhooks/slack
bot_events:
- app_mention
- message.channels
- message.groups
- message.im
- message.mpim
interactivity:
is_enabled: true
request_url: https://your-domain.com/api/webhooks/slack
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false- Replace
https://your-domain.com/api/webhooks/slackwith your deployed webhook URL - Click Create
Get credentials
After creating the app:
- Go to OAuth & Permissions, click Install to Workspace, and copy the Bot User OAuth Token (
xoxb-...) — you'll need this asSLACK_BOT_TOKEN - Go to Basic Information → App Credentials and copy the Signing Secret — you'll need this as
SLACK_SIGNING_SECRET
Configure environment variables
Create a .env.local file in your project root:
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_SIGNING_SECRET=your-signing-secret
REDIS_URL=redis://localhost:6379Create the bot
Create lib/bot.ts with a Chat instance configured with the Slack adapter:
import { Chat } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";
import { createRedisState } from "@chat-adapter/state-redis";
export const bot = new Chat({
userName: "mybot",
adapters: {
slack: createSlackAdapter(),
},
state: createRedisState(),
});
// Respond when someone @mentions the bot
bot.onNewMention(async (thread) => {
await thread.subscribe();
await thread.post("Hello! I'm listening to this thread now.");
});
// Respond to follow-up messages in subscribed threads
bot.onSubscribedMessage(async (thread, message) => {
await thread.post(`You said: ${message.text}`);
});The adapter auto-detects SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET from your environment, and createRedisState() reads REDIS_URL automatically.
onNewMention fires when a user @mentions your bot. Calling thread.subscribe() tells the SDK to track that thread, so subsequent messages trigger onSubscribedMessage.
Create the webhook route
Create a dynamic API route that handles incoming webhooks:
import { after } from "next/server";
import { bot } from "@/lib/bot";
type Platform = keyof typeof bot.webhooks;
export async function POST(
request: Request,
context: RouteContext<"/api/webhooks/[platform]">
) {
const { platform } = await context.params;
const handler = bot.webhooks[platform as Platform];
if (!handler) {
return new Response(`Unknown platform: ${platform}`, { status: 404 });
}
return handler(request, {
waitUntil: (task) => after(() => task),
});
}This creates a POST /api/webhooks/slack endpoint. The waitUntil option ensures message processing completes after the HTTP response is sent — required on serverless platforms where the function would otherwise terminate before your handlers finish.
Test locally
- Start your development server (
pnpm dev) - Expose it with a tunnel (e.g.
ngrok http 3000) - Update the Slack Event Subscriptions Request URL to your tunnel URL
- Invite your bot to a Slack channel (
/invite @mybot) - @mention the bot — it should respond with "Hello! I'm listening to this thread now."
- Reply in the thread — it should echo your message back
Add interactive features
Chat SDK supports rich interactive messages using a JSX-like syntax. Update your bot to send cards with buttons:
import { Chat, Card, CardText as Text, Actions, Button, Divider } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";
import { createRedisState } from "@chat-adapter/state-redis";
export const bot = new Chat({
userName: "mybot",
adapters: {
slack: createSlackAdapter(),
},
state: createRedisState(),
});
bot.onNewMention(async (thread) => {
await thread.subscribe();
await thread.post(
<Card title="Welcome!">
<Text>I'm now listening to this thread. Try clicking a button:</Text>
<Divider />
<Actions>
<Button id="hello" style="primary">Say Hello</Button>
<Button id="info">Show Info</Button>
</Actions>
</Card>
);
});
bot.onAction("hello", async (event) => {
await event.thread.post(`Hello, ${event.user.fullName}!`);
});
bot.onAction("info", async (event) => {
await event.thread.post(`You're on ${event.thread.adapter.name}.`);
});The file extension must be .tsx (not .ts) when using JSX components like Card and Button. Make sure your tsconfig.json has "jsx": "react-jsx" and "jsxImportSource": "chat".
Deploy to Vercel
Deploy your bot to Vercel:
vercel deployAfter deployment, set your environment variables in the Vercel dashboard (SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET, REDIS_URL). If your manifest used a placeholder URL, update the Event Subscriptions and Interactivity Request URLs in your Slack app settings to your production URL.
Next steps
- Cards — Build rich interactive messages with buttons, fields, and selects
- Modals — Open forms and dialogs from button clicks
- Streaming — Stream AI-generated responses to chat
- Actions — Handle button clicks, select menus, and other interactions
- Slack adapter — Multi-workspace OAuth, token encryption, and full configuration reference