DEV Community

Cover image for Building an AI chatbot using Next.Js
Samuel Ellis Kpeglah
Samuel Ellis Kpeglah

Posted on • Edited on

Building an AI chatbot using Next.Js

CONTENTS


Introduction
This tutorial is to help you build a simple but modern ChatBot using Next.js. The chatbot provides an interface where the users can interact with an Ai powered model.


Prerequisties
Before you go straight into building in your IDE, you've got to have;

  • Node.js installed(v-16.x or higher)
  • Code IDE (Vscode recommended or any IDE of your choice)
  • API Key (Would be using one from Groq)
  • Basics on React and or Javascript


Project Setup

  1. Open terminal(admin) and nagivate to where you want to create your project. Image description
  2. Next create your next.js project using npx create-next-app@latest chatbot
  3. This prompt will follow up. Use the image below as reference for your prompt. Image description


Installing Dependency
In terminal, typecd chatbotto navigate to your project directory.
Now install groq sdk dependency using npm install --save groq-sdk

GROQ SETUP

  1. Visit GroqCloud and sign in to create an account. Image description
  2. To get an API key you'd need to create one and remember to copy your key. Watch how to generate your key


Environment Configuration

  1. Back in your terminal, create a new file called ".env.local" in your project file. touch .env.local
  2. Add your API key into the .env.local file. echo "API_Key=your_copied_api_key" >> .env.local
  3. Also add your .env.local to .gitignore file in your project file if not added by default. echo ".env.local" >> .gitignore
  4. Finally, type code . and hit enter to open your IDE(code editor).


Project Structure
Your project structure should look like this:
Image description
NB: "next.config.mjs", "jsconfig.json" and ".eslintrc.json" files are created automatically when you run npx create-next-app


Application Code
Here's a preview of what you'll be building.
Image description

  1. Open "app/layout.js" and edit it like this:
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "", //title of the site
  description: "", //description of the site
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode
  1. In "app/global.css", you can modify it and add some styling like this:
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  @apply bg-gray-100 text-gray-900;
}

::-webkit-scrollbar {
  width: 8px;
}

::-webkit-scrollbar-track {
  background-color: rgba(60, 103, 109, 0.1);
  border-radius: 10px;
}
::-webkit-scrollbar-thumb {
  background-color: rgba(63, 198, 216, 0.2);
  border-radius: 10px;
}

::-webkit-scrollbar-thumb:hover {
  background-color: rgba(60, 108, 114, 0.4);
}
Enter fullscreen mode Exit fullscreen mode
  1. Lastly open "app/page.js" and write your code like this:
'use client';

import { useState, useEffect, FormEvent, useRef } from "react";

type Message = {
  id: number;
  sender: "user" | "bot";
  text: string;
};

const ChatPage = () => {
  const [messages, setMessages] = useState<Message[]>([]);
  const [inputValue, setInputValue] = useState("");

  const messagesEndRef = useRef<HTMLDivElement>(null);
  const [loading, setLoading] = useState(false);

  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  };

  useEffect(() => {
    scrollToBottom();
  }, [messages]);

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    if (!inputValue.trim()) return;

    const userMessage: Message = {
      id: Date.now(),
      sender: "user",
      text: inputValue.trim(),
    };

    setMessages((prevMessages) => [...prevMessages, userMessage]);
    setInputValue("");
    setLoading(true);

    try {
      const response = await fetch("/api/chat", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ message: userMessage.text }),
      });

      const data = await response.json();

      if (response.ok) {
        const botMessage: Message = {
          id: Date.now() + 1,
          sender: "bot",
          text: data.message,
        };

        setMessages((prevMessages) => [...prevMessages, botMessage]);
      } else {
        const errorMessage: Message = {
          id: Date.now() + 1,
          sender: "bot",
          text: data.error || "Processing failed",
        };
        setMessages((prevMessages) => [...prevMessages, errorMessage]);
      }
    } catch (error) {
      console.error("Error fetching chat", error);
      const errorMessage: Message = {
        id: Date.now() + 1,
        sender: "bot",
        text: "Unexpected error occurred",
      };
      setMessages((prevMessages) => [...prevMessages, errorMessage]);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="flex flex-col h-screen bg-blue-100">
      {/* Header */}
      <header className="bg-white shadow px-4 py-4">
        <h1 className="font-semibold text-blue-800 text-2xl">
          Chat with ElAioT
        </h1>
      </header>

      {/* Chat Box */}
      <div className="flex-1 p-4 overflow-y-auto">
        {messages.map((message) => (
          <div
            key={message.id}
            className={`flex flex-col ${message.sender === "user" ? "justify-end" : "justify-start"} mb-4`}
          >
            <div
              className={`px-4 py-2 rounded-lg max-w-xl ${message.sender === "user" ? "bg-blue-500 text-white" : "bg-gray-200 text-gray-800"}`}
            >
              {message.text}
            </div>
          </div>
        ))}

        {loading && (
          <div className="flex justify-start mb-4">
            <div className="flex space-x-1">
              <span className="block w-2 h-2 bg-gray-400 rounded-full animate-pulse"></span>
              <span className="block w-2 h-2 bg-gray-400 rounded-full animate-pulse delay-200"></span>
              <span className="block w-2 h-2 bg-gray-400 rounded-full animate-pulse delay-400"></span>
            </div>
          </div>
        )}

        <div ref={messagesEndRef} />
      </div>

      {/* Input Form */}
      <form onSubmit={handleSubmit} className="flex p-4 bg-white shadow">
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="Type your message..."
          className="flex-1 border border-gray-300 rounded-full px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
          disabled={loading}
        />
        <button
          type="submit"
          className="ml-4 bg-blue-500 text-white p-2 rounded-full hover:bg-blue-600 focus:outline-none disabled:bg-blue-300"
          disabled={loading}
        >
          {/* Send Icon (Paper Plane) */}
          <svg
            xmlns="http://www.w3.org/2000/svg"
            className="h-6 w-6"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={2}
              d="M3 8L7.89 2.632a3 3 0 001.11 0L21 8M5 19h14a2 0 002-2v-7a2 2 0 00-2-2H5a2 2 0 00-2 2v7a2 2 002 2z"
            />
          </svg>
        </button>
      </form>
    </div>
  );
};

export default ChatPage;
Enter fullscreen mode Exit fullscreen mode
  1. Now, create "api/chat/route.ts" in "app" and write this:
import { NextResponse } from "next/server";
import Groq from "groq-sdk";

const groq = new Groq({ apiKey: process.env.GROQ_API_KEY });

export async function POST(request: Request) {
  try {
    const { message } = await request.json();

    if (!message) {
      return NextResponse.json(
        { error: "Message content is required" },
        { status: 400 }
      );
    }

    const chatCompletion = await groq.chat.completions.create({
      messages: [
        {
          role: "user",
          content: message,
        },
      ],
      model: "llama-3.3-70b-versatile",
    });

    const responseMessage =
      chatCompletion.choices[0]?.message?.content || "I'm sorry, I don't understand that.";

    return NextResponse.json({ message: responseMessage });
  } catch (error) {
    console.error("Error in chat API", error);
    return NextResponse.json(
      { error: "An error occurred while processing your request" },
      { status: 500 }
    );
  }
}
Enter fullscreen mode Exit fullscreen mode


Running & Testing

  1. Start the development server in your terminal using npm run dev Image description
  2. From the terminal, locate "http://localhost:3000", hold CTRL key and click on the link to open your build. (Ref from img in 1). An interface will show as seen in the preview.
  3. Play around in your build and when satisfied enough, press ` to stop the development server.


Deployment
For deployment, I'd be using vercel.
Vercel, a cloud platform which allows developer's host their projects.

  1. To begin, sign in with your GitHub to create an account on vercel. Follow the prompts, to get you started.
  2. Import your repository from GitHub through vercel
  3. Select your chatbot repository
  4. Scroll down to Environment variables and add your API_Key. (NB: This step is crucial. It allows vercel to know and use your API as done in your local dev server.)
  5. Click deploy and wait for vercel to do it thing.
  6. An interface like this would show: Image description
  7. To access your deployed work, click on a link like this: "https://chatbot-pink-five.vercel.app/"
  8. In case you don't get step 2 to step 5, please use this link to watch a short video on it.

Congratulations your first chatbot has been deployed if you're able to get to this point.

I hope you find this documentation helpful in achieving your goals. Your success in following these guidelines is a testament to your dedication and skills.
If you found this documentation valuable, I encourage you to follow my future work for more insights, tips, and guidance. By doing so, you’ll not only stay updated but also help grow our community of like-minded individuals. Together, we can achieve even greater heights.
Thank you for your trust and collaboration.

API Trace View

How I Cut 22.3 Seconds Off an API Call with Sentry 🕒

Struggling with slow API calls? Dan Mindru walks through how he used Sentry's new Trace View feature to shave off 22.3 seconds from an API call.

Get a practical walkthrough of how to identify bottlenecks, split tasks into multiple parallel tasks, identify slow AI model calls, and more.

Read more →

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series