Updated on June 11, 2026

TL;DR

To build an OpenAI chatbot with React and Next.js, follow these steps:

  1. Run npx create-next-app and install openai and zod
  2. Add your OPENAI_API_KEY to .env.local 
  3. Create app/api/chat/route.ts to place the OpenAI API call
  4. In that route, validate the incoming message with Zod, retrieve relevant knowledge from your FAQ/policy docs, and call openai.responses.create with a structured JSON schema that forces the model to return an action (answer, clarify, fallback, or handoff) alongside its message.
  5. Build a “use client” React component that sends user input to /api/chat and renders the response.
  6. Add error handling so every failure state (timeout, no source found, rate limit) returns a stable response that the UI can act on.
  7. When you’re ready for production, swap the plain widget for Kommunicate to get human handoff, routing, and analytics without rebuilding the support layer yourself.

Most React chatbot tutorials focus on the chat UI first.

The UI matters, but the support problem is more important. If the AI answers a refund question without policy context, no amount of React polish will save the experience. If the model cannot decide when to hand off, the chatbot becomes a bottleneck rather than a relief valve.

So this guide builds the chatbot as a small support system that looks like this:

Architecture flow for an OpenAI support chatbot. The React chat UI passes requests down to a Next.js route handler, then to the OpenAI Responses API, then to a structured support decision that resolves into one of four actions: answer, clarify, fallback, or handoff.
OpenAI chatbot architecture flow

In this article, we’ll talk about:

  1. What are we building?
  2. Where to put the OpenAI API key?
  3. Project setup
  4. Build the Next.js API route
  5. Build the React chat UI
  6. Add knowledge context
  7. Add streaming and error handling
  8. Should you use this chatbot instead of a support platform?
  9. Parting Thoughts

What are we building?

We will build a simple customer support chatbot with:

We’re going to focus heavily on the escalation paths in this tutorial. The chatbot here will perform different tasks depending on the type of question.

User Situation Correct Action
Clear FAQ question Answer from the knowledge context.
Vague question Ask one clarifying question.
Missing knowledge Fallback or suggest support.
Human request Hand off.
Refund, billing, and account risk Hand off or require review.

First, we’ll start by taking a look at how we’ll use the OpenAI API in this tutorial and the steps we will take to keep it safe.

Where to put the OpenAI API key?

Never call OpenAI directly from React in the browser.

The browser cannot protect your API key. A user can inspect network calls, extract keys, and abuse your account. Your backend should own the following:

  • API keys
  • Model configuration
  • Prompt rules
  • Retrieval
  • Rate limits
  • Logging
  • Fallback behavior

We will use React only to render messages and send user input. Additionally, you should try to avoid some mistakes.

Common mistakes to avoid before you start

Mistake Better Approach
Calling OpenAI from React Use a Next.js server route.
Sending entire chat history Send recent and useful turns only.
No knowledge context Retrieve sources before calling the model.
Free-text response only Use structured decisions.
No fallback Design fallback as a product action.
UI-first thinking Build the support workflow first.

Now that you have an idea about how to avoid some common security failures, let’s get started with this project. 

Project setup

  1. Create a Next.js app:
npx create-next-app@latest openai-next-chatbot
cd openai-next-chatbot
npm install openai zod
  1. Add .env.local:
OPENAI_API_KEY=your_api_key_here
OPENAI_SUPPORT_MODEL=gpt-5.4-mini

gpt-5.4-mini is OpenAI’s fast, cost-efficient small model released in April 2026. It runs more than 2x faster than its predecessor and handles high-volume workloads well. 

Now that we have this configured, we’re going to start building the Next.js server.

Build the Next.js API route

Create app/api/chat/route.ts:

import OpenAI from “openai”;
import { z } from “zod”;

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

const RequestSchema = z.object({
  message: z.string().min(1).max(4000),
  history: z.array(z.object({
    role: z.enum([“user”, “assistant”]),
    content: z.string()
  })).default([])
});

const supportDecisionSchema = {
  type: “object”,
  additionalProperties: false,
  properties: {
    action: { type: “string”, enum: [“answer”, “clarify”, “fallback”, “handoff”] },
    message: { type: “string” },
    reason: { type: “string” }
  },
  required: [“action”, “message”, “reason”]
};

export async function POST(req: Request) {
  const body = await req.json();
  const { message, history } = RequestSchema.parse(body);

  const response = await openai.responses.create({
    model: process.env.OPENAI_SUPPORT_MODEL || “gpt-5.4-mini”,
    input: [
      {
        role: “system”,
        content: `You are a customer support AI agent. Answer only when safe. If the customer asks for a human, choose handoff. If the answer is missing from the context provided, choose fallback.`
      },
      …history.slice(-6),
      { role: “user”, content: message }
    ],
    text: {
      format: {
        type: “json_schema”,
        name: “support_decision”,
        schema: supportDecisionSchema,
        strict: true
      }
    }
  });

  return Response.json(JSON.parse(response.output_text));
}

This route returns an action, not just a message. The React UI does not need to guess what happened because the backend tells it.

Build the React chat UI

Create a simple client component:

“use client”;

import { useState } from “react”;

type Message = {
  role: “user” | “assistant”;
  content: string;
};

export default function SupportChat() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState(“”);
  const [loading, setLoading] = useState(false);

  async function sendMessage() {
    if (!input.trim()) return;

    const nextMessages = […messages, { role: “user” as const, content: input }];
    setMessages(nextMessages);
    setInput(“”);
    setLoading(true);

    const res = await fetch(“/api/chat”, {
      method: “POST”,
      headers: { “Content-Type”: “application/json” },
      body: JSON.stringify({
        message: input,
        history: messages
      })
    });

    const decision = await res.json();

    setMessages([
      …nextMessages,
      { role: “assistant”, content: decision.message }
    ]);
    setLoading(false);
  }

  return (
    span class=”language-xml”>span class=”hljs-tag”><div className=”mx-auto max-w-xl p-4″>/span>
      span class=”hljs-tag”><div className=”space-y-3 rounded border p-4″>/span>
        {messages.map((message, index) => (
          span class=”hljs-tag”><div key={index} className={message.role === “user” ? “text-right” : “text-left”}>/span>
            span class=”hljs-tag”><span className=”inline-block rounded bg-gray-100 px-3 py-2″>/span>
              {message.content}
            span class=”hljs-tag”></span>/span>
          span class=”hljs-tag”></div>/span>
        ))}
        {loading && span class=”hljs-tag”><p>/span>Checking…span class=”hljs-tag”></p>/span>}
      span class=”hljs-tag”></div>/span>

      span class=”hljs-tag”><div className=”mt-3 flex gap-2″>/span>
        span class=”hljs-tag”><input
          className=”flex-1 rounded border px-3 py-2″
          value={input}
          onChange={(e) =>/span> setInput(e.target.value)}
          onKeyDown={(e) => e.key === “Enter” && sendMessage()}
        />
        span class=”hljs-tag”><button className=”rounded bg-black px-4 py-2 text-white” onClick={sendMessage}>/span>
          Send
        span class=”hljs-tag”></button>/span>
      span class=”hljs-tag”></div>/span>
    span class=”hljs-tag”></div>/span>/span>
  );
}

This is intentionally plain to help you get to an MVP and see support behavior without working on complicated UI.

If you are building with standalone React rather than Next.js, the setup for the backend will be different. We’ve written an OpenAI + ReactJS integration guide that covers that path using Kommunicate as the UI layer, which removes the need to build the route handler yourself.

Add knowledge context

For a support chatbot, the route should retrieve relevant FAQ or policy content before calling OpenAI.

function retrieveKnowledge(message: string) {
  const docs = [
    {
      title: “Refund policy”,
      text: “Refunds are available within 30 days if the item is unused and in original packaging.”
    },
    {
      title: “Shipping policy”,
      text: “Standard shipping takes 3-5 business days.”
    }
  ];

  return docs.filter(span class=”hljs-function”>(doc) =>/span>
    message.toLowerCase().includes(doc.title.split(” “)[0].toLowerCase())
  );
}

Then add it to the input using the following:

const sources = retrieveKnowledge(message);
const context = sources.map((s) => `${s.title}: ${s.text}`).join(“\n”);

A note on this retrieval function: The keyword filter above is intentional shorthand for a tutorial. 

In production, you’ll need to replace it with vector similarity search, a dedicated retrieval API, or a RAG pipeline. Keyword matching will miss most real customer queries, and the model should choose a fallback rather than guess when no source is found.

Add streaming and error handling

1. Handle failures as product states

A production chat route should not return raw OpenAI errors to the React UI. The UI needs a stable response contract even when the model call fails, retrieval returns no sources, or the structured output cannot be parsed.

Failure Backend Action UI Behavior
OpenAI timeout Return fallback with retry-safe reason. Show a helpful message and offer support.
No source found Return fallback or clarify. Do not show a made-up answer.
User asks for a human Return handoff. Open the handoff path instead of continuing the AI chat.
Schema parse failure Return fallback and log schema version. Keep the chat usable.
Rate limit Return fallback or queue async work. Avoid a spinning loading state.

The route response should include enough structure for the UI to know what to do next:

type ChatDecision = {

  action: “answer” | “clarify” | “fallback” | “handoff”;

  message: string;

  reason: string;

  handoffSummary?: string;

};

React should not infer handoff from a phrase like “talk to support.” The backend should return action: “handoff” so the UI or support widget can route the customer with context.

2. Add streaming carefully

Streaming is useful when the chatbot is producing a visible answer. It is less useful when the backend must first make a routing decision.

For support workflows, a good pattern is:

  1. Make a structured decision first.
  2. If action is answer: stream the final reply.
  3. If action is handoff, stop and route: do not stream a confident answer and then decide it needs a handoff.

You now have an OpenAI chatbot that can answer questions from your documents. However, this is just a prototype; you need to make some decisions before you ship this to production.

Should you use this chatbot instead of a support platform?

Building this in Next.js is a good way to understand the mechanics. 

For production support, you will need to add:

  1. Routing
  2. Handoff
  3. Conversation management
  4. Analytics
  5. Knowledge refresh
  6. Business-user controls 

So, when should you build a custom chatbot, and when should you buy an AI support platform like Kommunicate?

Build vs. buy

Build versus buy comparison for a support chatbot across five factors. The build path with Next.js and OpenAI requires you to manage API key security, knowledge base retrieval code, handoff routing, analytics, and model updates yourself, summed up as full control. The Kommunicate buy path handles each of those built in, summed up as fast to production.
Build vs buy support chatbot comparison
Build (Next.js + OpenAI) Buy (Kommunicate)
Setup time Hours to days Minutes
API key security You manage server-side protection Handled by the platform
Knowledge base Custom retrieval code Dashboard-managed
Human handoff You build the routing logic Built in
Chat history You manage state and storage Built in
Multi-channel Each channel is a separate build You get access to WhatsApp, web, and mobile out of the box
Analytics You instrument and maintain Built in
Non-engineer controls None, everything is code Business users can edit flows, FAQs, and routing
Streaming You implement and manage Handled by the platform
Fallback logic You design and maintain Configurable without code
Ongoing model updates You handle version pinning and testing The platform absorbs changes
Cost at low volume Low, pay only for API tokens Subscription cost regardless of usage
Cost at scale Grows with token usage Predictable per-seat or usage pricing
Right for Teams that need full control over the AI layer Support teams who need a production-ready solution fast

When your main product isn’t a support chatbot, it’s really hard to justify the engineering hours required to build a production support platform. So, if you decide to skip the custom route entirely and connect a pre-built AI agent to Next.js through a dashboard, see how to build an enterprise support chatbot for a Next.js website.

“use client”;

import { useEffect } from “react”;
import Kommunicate from “@kommunicate/kommunicate-chatbot-plugin”;

export function KommunicateSupportWidget() {
  useEffect(() => {
    Kommunicate.init(“APP_ID”, {
      automaticChatOpenOnNavigation: true,
      popupWidget: true,
    });
  }, []);

  return null;
}

Keep OpenAI calls on your server route. Let the widget own the support experience, and let your backend own retrieval, validation, and routing decisions.

Parting thoughts

React and Next.js make the interface easy. OpenAI makes the language layer strong. But the support workflow decides whether the chatbot is useful. On a high-level, you need to implement the following:

  1. Build the routing process
  2. Ground answers in knowledge
  3. Return structured decisions
  4. Escalate when needed

That is the difference between a chatbot demo and a support experience. 

If you want to build an AI-first support experience that doesn’t cost you engineering overhead, feel free to book a demo with us. You can also sign up to build and deploy an OpenAI-powered chatbot yourself.  

Write A Comment

You’ve unlocked 30 days for $0
Kommunicate Offer
Kommunicate Blog
×