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
streamText
to send responses in real-time, improving UX. - Environment: Add your OpenAI API key to a
.env
file: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:
useChat
Hook: Manages chat state and API calls effortlessly.- Modal Design: A floating button opens the chat window.
- Tailwind CSS: Styles like
bg-yellow-400
androunded-xl
keep 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!