<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Abubakar Farooq]]></title><description><![CDATA[Abubakar Farooq]]></description><link>https://abubakarfarooq.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Abubakar Farooq</title><link>https://abubakarfarooq.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 29 Sep 2026 13:31:10 GMT</lastBuildDate><atom:link href="https://abubakarfarooq.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Add an AI Chatbot to Your Next.js App (Without Overcomplicating It)]]></title><description><![CDATA[How to Add an AI Chatbot to Your Next.js App (Without Overcomplicating It)
Every client wants an "AI chatbot" now. Most tutorials show you a demo that breaks the moment real users touch it. Here's the]]></description><link>https://abubakarfarooq.hashnode.dev/how-to-add-an-ai-chatbot-to-your-next-js-app-without-overcomplicating-it</link><guid isPermaLink="true">https://abubakarfarooq.hashnode.dev/how-to-add-an-ai-chatbot-to-your-next-js-app-without-overcomplicating-it</guid><category><![CDATA[Next.js]]></category><category><![CDATA[AI]]></category><category><![CDATA[chatbot]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Abubakar Farooq]]></dc:creator><pubDate>Thu, 24 Sep 2026 22:59:31 GMT</pubDate><content:encoded><![CDATA[<h1>How to Add an AI Chatbot to Your Next.js App (Without Overcomplicating It)</h1>
<p>Every client wants an "AI chatbot" now. Most tutorials show you a demo that breaks the moment real users touch it. Here's the setup I actually use in production — simple, but it handles the parts demos skip.</p>
<h2>The architecture</h2>
<p>Keep it boring:</p>
<ul>
<li><strong>Next.js API route</strong> (<code>/api/chat</code>) — your server-side proxy. Your LLM API key never touches the browser.</li>
<li><strong>Streaming responses</strong> — the bot "types" token by token. Users perceive this as 3x faster even when it isn't.</li>
<li><strong>A small React chat component</strong> — input, messages, done.</li>
</ul>
<h2>1. The API route</h2>
<pre><code class="language-ts">// app/api/chat/route.ts
export async function POST(req: Request) {
  const { messages } = await req.json();

  const response = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
    },
    body: JSON.stringify({
      model: "gpt-4o-mini",
      stream: true,
      messages: [
        {
          role: "system",
          content:
            "You are a helpful assistant for an e-commerce store. Keep answers short.",
        },
        ...messages,
      ],
    }),
  });

  return new Response(response.body, {
    headers: { "Content-Type": "text/event-stream" },
  });
}
</code></pre>
<p>Note the system prompt — that's 80% of "bot quality" right there. Tune it per client, not per tutorial.</p>
<h2>2. The frontend</h2>
<p>Fetch the stream and append chunks as they arrive:</p>
<pre><code class="language-tsx">const res = await fetch("/api/chat", {
  method: "POST",
  body: JSON.stringify({ messages }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let done = false;

while (!done) {
  const { value, done: d } = await reader.read();
  done = d;
  if (value) {
    const chunk = decoder.decode(value);
    // parse the SSE "data:" lines and append each delta.content
  }
}
</code></pre>
<p>Parsing SSE chunks by hand is fiddly — in production, use a library like Vercel's <code>ai</code> package, which handles the streaming protocol for you.</p>
<h2>3. Three things demos skip</h2>
<ol>
<li><strong>Rate limiting.</strong> One shared API key plus a public endpoint equals a surprise bill. Add per-IP or per-user limits (Upstash Ratelimit takes about ten minutes to wire up).</li>
<li><strong>Failure states.</strong> LLMs time out. Show a retry button, not a spinner of death.</li>
<li><strong>Conversation memory.</strong> Sending the full history on every turn gets expensive fast. Summarize or truncate old messages server-side.</li>
</ol>
<h2>Going further</h2>
<p>Once text chat works, the same pattern extends nicely: retrieval-augmented generation over the client's own docs, and voice agents (speech-to-text → LLM → text-to-speech) for hands-free support. I build these for clients regularly — you can see the kind of work I do at <a href="https://theabubakar.dev">theabubakar.dev</a>.</p>
<h2>TL;DR</h2>
<p>API route as proxy, stream the response, nail the system prompt, and handle rate limits and failures before launch. That's a production chatbot — everything else is polish.</p>
]]></content:encoded></item></channel></rss>