Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

Can You Make a Discord Bot with Next.js? Yes, With One Important Caveat

Sean

Platform Writer

Aug 08, 2026
8 min read

You can build a Discord bot with Next.js, and it will work well for slash commands, buttons, and modals — anything Discord delivers over HTTP to an endpoint. What you cannot do is run a gateway bot, because that needs a persistent WebSocket connection and a Next.js route handler is request-scoped.

Can You Make a Discord Bot with Next.js? Yes, With One Important Caveat

That single distinction determines which bot features are available to you. It is worth understanding before you start, because discovering it after building half the bot means restructuring rather than adding.

Table of contents

Two ways a Discord bot receives events

Discord offers two mechanisms and they have very different infrastructure requirements.

The Gateway is a persistent WebSocket connection. Your bot connects, authenticates, and receives a live stream of events — every message sent, member joining, reaction added, voice state change. This is what discord.js uses by default and it requires a process that stays connected indefinitely.

HTTP Interactions work the opposite way. You register an endpoint URL with Discord, and Discord makes a signed POST request to it when a user runs a slash command or clicks a button. No persistent connection; each interaction is an independent HTTP request.

Next.js handles the second perfectly and cannot do the first. A route handler runs when a request arrives and finishes when it responds — there is nowhere for a long-lived socket to live, and on serverless deployment the instance may not exist between requests at all.

What you can and cannot build

The practical split:

Available over HTTP interactions:

  • Slash commands, including autocomplete and subcommands.
  • Buttons, select menus, and modal dialogs.
  • Context menu commands on messages and users.
  • Anything the bot does in response to a deliberate user action.

Requires a gateway connection:

  • Reacting to every message — moderation, auto-responses, keyword monitoring.
  • Detecting members joining or leaving.
  • Voice channel functionality of any kind.
  • Presence and status tracking.
  • Reactions added to existing messages.

The dividing line is clean: if a user explicitly invokes your bot, HTTP works. If your bot needs to observe things happening, it needs the gateway.

Most utility bots fall entirely on the HTTP side, which is why this is a viable approach rather than a compromise.

Verifying the request signature

Discord signs every interaction request, and verification is mandatory — Discord sends deliberately invalid requests during endpoint setup and refuses to register an endpoint that accepts them.

The critical implementation detail is that verification needs the raw request body. Parsing the JSON first and re-serialising it produces a different byte sequence and the signature will not match.

// app/api/discord/route.js
import { verifyKey } from 'discord-interactions';

export async function POST(request) {
  const signature = request.headers.get('x-signature-ed25519');
  const timestamp = request.headers.get('x-signature-timestamp');
  const rawBody = await request.text();   // text, not json

  const isValid = await verifyKey(
    rawBody, signature, timestamp,
    process.env.DISCORD_PUBLIC_KEY
  );

  if (!isValid) {
    return new Response('Invalid signature', { status: 401 });
  }

  const interaction = JSON.parse(rawBody);

  // Discord sends a PING to validate the endpoint
  if (interaction.type === 1) {
    return Response.json({ type: 1 });
  }

  return Response.json({
    type: 4,
    data: { content: 'Hello' },
  });
}

The PING handling is not optional either. Discord sends type 1 during setup and expects type 1 back.

The three-second deadline

Discord requires an initial response within three seconds. Miss it and the user sees an interaction failed message, regardless of what your code does afterwards.

Three seconds is not much for anything involving a database query and an external API call. The mechanism for that is a deferred response — acknowledge immediately, then send the real content afterwards:

// Respond immediately with type 5: deferred
return Response.json({ type: 5 });

// Then, within 15 minutes, PATCH the original response:
// https://discord.com/api/v10/webhooks/{app_id}/{token}/messages/@original

The user sees a thinking indicator and then your content when it arrives.

There is a serverless-specific trap here. Returning the deferred response ends the request, and the platform may terminate the function immediately — before your follow-up work completes. Fire-and-forget after responding does not reliably work.

The fixes are to complete the work before responding when it fits inside three seconds, or to hand it to something that outlives the request: a queue, a background worker, or a persistent service. On a platform where the process keeps running between requests rather than being torn down, this problem does not arise, which is a genuine argument for a long-running service over a serverless function for this specific use case.

Registering the commands

Commands must be registered with Discord before they appear. This is a separate API call, not something your endpoint does:

// scripts/register-commands.js — run manually or in your deploy
const commands = [
  { name: 'ping', description: 'Check the bot is alive' },
];

await fetch(
  `https://discord.com/api/v10/applications/${APP_ID}/commands`,
  {
    method: 'PUT',
    headers: {
      Authorization: `Bot ${BOT_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(commands),
  }
);

Two things worth knowing. Global commands can take up to an hour to propagate, while guild-specific commands are immediate — so register against a test guild during development and globally for release.

And the PUT method replaces the entire command set. Any command not in the array is deleted, which is usually what you want and is a surprise the first time you use POST expecting an update.

When to use a persistent service instead

If you need gateway features, the architecture changes. A gateway bot is a long-running process with an open WebSocket, which means:

  • It must stay running. A process that sleeps when idle disconnects from the gateway and stops receiving events.
  • One connection per bot. Running several instances requires sharding, coordinated so each handles a distinct portion.
  • It holds state in memory — the gateway sends the guild and member state on connect, and reconnecting means resuming or re-fetching it.

A common shape that works well: a Next.js application for the web interface and HTTP interactions, plus a separate long-running Node service holding the gateway connection, with both reading from the same database.

That split is worth reaching for deliberately rather than trying to force everything into one deployment model. The web app and the gateway bot have genuinely different runtime requirements, and treating them as two services with shared data is simpler than making one shape do both.

How this fits the rest of the stack

The decision here is really about runtime shape: HTTP interactions fit a request-response deployment, and gateway features need a process that stays connected and holds state. Trying to make one model cover both is where this gets painful. Node services on RunxBuild run as long-lived processes rather than per-request functions, which is what a gateway connection and a deferred follow-up both need, and a managed Postgres alongside gives the web app and the bot one place to share state. The RunxBuild hosting calculator shows the service and the database as separate line items.

Useful related references:

FAQ

Can Next.js run a Discord gateway bot?

No. A gateway bot needs a persistent WebSocket connection, and a Next.js route handler is request-scoped — it ends when it responds. Next.js works well for HTTP interactions: slash commands, buttons, and modals.

What Discord features need a gateway connection?

Anything that observes rather than responds — reacting to every message, detecting members joining or leaving, voice functionality, presence tracking, and reactions on existing messages. Deliberate user invocations work over HTTP.

Why does my signature verification fail?

Almost always because the body was parsed before verifying. Signature verification needs the raw request body — use request.text() and parse afterwards, since re-serialising JSON produces different bytes.

How do I handle work that takes longer than three seconds?

Return a deferred response (type 5) to acknowledge immediately, then PATCH the original response within fifteen minutes. On serverless, the function may be terminated after responding, so hand the work to a queue or a persistent process.

Why do my slash commands not appear in Discord?

Commands must be registered separately via the applications commands API. Global commands can take up to an hour to propagate; register against a test guild during development for immediate availability.

#discord bot nextjs#discord interactions#webhook#serverless#discord.js