How I build a chatbot for my portfolio website with Next.js 15, Vercel AI SDK, and GPT-4o-mini
Publication date: March 20, 2025
- #NextJS
- #VercelAI
- #ChatbotTutorial
- #GPT4oMini
Building a chatbot for my portfolio website, vks.gr, was one of the most exciting projects I’ve tackled as a web developer. I wanted something interactive, user-friendly, and modern to showcase my skills while helping visitors explore my work. In this tutorial, I’ll walk you through how I created "Vai"—my AI assistant—using Next.js 15, the Vercel AI SDK, Tailwind CSS, and OpenAI’s GPT-4o-mini model. Whether you’re a beginner or a seasoned dev, this step-by-step guide is designed to be easy to follow, with code snippets you can adapt for your own projects.
By the end, you’ll have a sleek, functional chatbot embedded in your site—perfect for portfolios, e-commerce, or any web app. Let’s dive in!
Why I Built This Chatbot
As a freelance developer, I wanted vks.gr to stand out. A chatbot seemed like the perfect way to:
- Engage visitors with real-time answers about my portfolio, services, and blog.
- Showcase my skills with cutting-edge tech like AI and Next.js.
- Make the site more interactive and fun.
I chose Next.js 15 for its App Router and server-side capabilities, Vercel AI SDK for seamless AI integration, Tailwind CSS for fast styling, and GPT-4o-mini for its efficiency and cost-effectiveness. Ready to build your own? Let’s get started.
Prerequisites
Before we begin, make sure you have:
- Node.js (v18 or later) installed.
- A basic understanding of React and TypeScript.
- An OpenAI API key (get one from platform.openai.com).
- A Next.js project set up (
npx create-next-app@latest).
Step 1: Set Up Your Next.js Project
If you don’t already have a Next.js app, create one:
npx create-next-app@15 my-chatbot-app
cd my-chatbot-app
npm install
I used TypeScript for type safety, so select that option when prompted. Next, install the key dependencies:
npm install @ai-sdk/openai ai tailwindcss framer-motion @heroicons/react firebase-admin
@ai-sdk/openai: Connects to OpenAI’s API via Vercel AI SDK.ai: Vercel AI SDK core package.tailwindcss: For styling the chatbot UI.framer-motion: For smooth animations.@heroicons/react: Icons for the UI.firebase-admin: Optional, for storing chat sessions (I used Firebase, but you can skip this).
Configure Tailwind CSS by running npx tailwindcss init and updating your tailwind.config.ts—check the Tailwind docs for details.
Step 2: Create the Chat API Route
The chatbot needs a backend to process messages and talk to GPT-4o-mini. I created an API route at app/api/chat/route.ts. Here’s a simplified version:
// app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";
export const maxDuration = 60; // Vercel timeout limit
export async function POST(req: Request) {
const { messages } = await req.json();
const systemPrompt = `
You are Vai, an AI assistant for a portfolio website (vks.gr).
Be friendly, concise, and helpful. Answer questions about my work, services, or anything related to the site!
`;
const result = await streamText({
model: openai("gpt-4o-mini"),
system: systemPrompt,
messages,
});
return result.toDataStreamResponse();
}
What’s Happening Here?
- Imports: We bring in OpenAI and streaming utilities from Vercel AI SDK.
- POST Handler: Accepts user messages via a JSON request.
- System Prompt: Tells GPT-4o-mini it’s "Vai" and sets its tone.
- Streaming: Uses
streamTextto send responses in real-time, improving UX. - Environment: Add your OpenAI API key to a
.envfile:OPENAI_API_KEY=your-key-here.
This API streams responses from GPT-4o-mini, keeping the chatbot fast and responsive. In my full version, I also pulled in portfolio data from JSON and Firebase—feel free to customize this with your own content!
Step 3: Build the Chat UI Component
Now, let’s create the front-end. I placed my chatbot in app/components/chat/Chat.tsx. Here’s a streamlined version:
// app/components/chat/Chat.tsx
"use client";
import { useChat } from "@ai-sdk/react";
import React, { useState } from "react";
import { XMarkIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline";
import { motion } from "framer-motion";
export default function Chat() {
const [isOpen, setIsOpen] = useState(false);
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: "/api/chat",
initialMessages: [
{
id: "welcome",
role: "assistant",
content: "I’m Vai — your AI assistant! How can I help you today?",
},
],
});
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
handleSubmit(e);
};
return (
<>
{!isOpen && (
<motion.button
onClick={() => setIsOpen(true)}
className="fixed bottom-4 right-4 px-4 py-2 bg-yellow-400 text-gray-900 rounded-full shadow-lg"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
Chat with Vai
</motion.button>
)}
{isOpen && (
<section className="fixed bottom-4 right-4 w-[90vw] md:w-[400px] h-[500px] bg-gray-800 text-white rounded-xl shadow-2xl flex flex-col">
<header className="p-4 border-b border-gray-700 flex justify-between">
<h2 className="text-lg font-semibold">Vai — AI Assistant</h2>
<button onClick={() => setIsOpen(false)}>
<XMarkIcon className="w-5 h-5" />
</button>
</header>
<div className="flex-1 p-4 overflow-y-auto">
{messages.map((m) => (
<div
key={m.id}
className={`p-3 rounded-lg my-2 ${
m.role === "user" ? "bg-yellow-400 text-black ml-auto" : "bg-gray-700"
}`}
>
{m.content}
</div>
))}
</div>
<form onSubmit={onSubmit} className="p-4 border-t border-gray-700 flex gap-2">
<textarea
value={input}
onChange={handleInputChange}
placeholder="Ask anything"
className="flex-1 p-2 bg-gray-700 rounded-xl text-white"
rows={2}
/>
<button type="submit" className="p-2 bg-yellow-400 rounded-full">
<PaperAirplaneIcon className="w-5 h-5 text-gray-900" />
</button>
</form>
</section>
)}
</>
);
}
Key Features:
useChatHook: Manages chat state and API calls effortlessly.- Modal Design: A floating button opens the chat window.
- Tailwind CSS: Styles like
bg-yellow-400androunded-xlkeep it clean and modern. - Framer Motion: Adds subtle animations to the button (
whileHover,whileTap). - Accessibility: Basic ARIA labels and focus management (my full version has more).
In my real app, I added keyboard navigation, session persistence with Firebase, and Markdown parsing as well.
Step 4: Integrate the Chatbot into Your Layout
To make the chatbot accessible site-wide, I added it to app/layout.tsx:
// app/layout.tsx
import Chat from "@/app/components/chat/Chat";
import "./globals.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className="min-h-screen flex flex-col">
{children}
<div className="fixed bottom-10 right-10">
<Chat />
</div>
</body>
</html>
);
This positions the chatbot in the bottom-right corner, floating above the content. I also used custom fonts (Comfortaa, Montserrat) and a progress bar via nextjs-toploader—small touches to polish the experience.
Step 5: Test and Deploy
Run your app locally:
npm run dev
Click the "Chat with Vai" button, type a message, and watch Vai respond! If it works, deploy to Vercel:
vercel
Vercel’s serverless functions handle the API route perfectly, and the streaming response keeps it snappy.
Bonus: Customization Tips
- Styling: Tweak Tailwind classes to match your brand (I used yellow accents for vks.gr).
- AI Model: Swap GPT-4o-mini for another model via
@ai-sdk/openai. - Data: Feed your chatbot custom content (I used JSON and Firebase for portfolio data).
- Animations: Add more Framer Motion effects to the chat window.
Conclusion
Building Vai for vks.gr was a blast, and I hope this tutorial inspires you to add a chatbot to your own site. With Next.js 15, Vercel AI SDK, and Tailwind CSS, it’s easier than ever. Have questions? Drop a comment or reach out—I’d love to hear how you tweak this for your projects!