DEV Community

Cover image for "Build a Full AI Chatbot With Next.js in Under 100 Lines"
Devraj Singh
Devraj Singh

Posted on

"Build a Full AI Chatbot With Next.js in Under 100 Lines"

"Every developer says they want to build AI projects. Most spend weeks planning and never start. Here's how to go from zero to deployed AI chatbot in under 100 lines β€” today."

Let me guess your situation right now. πŸ‘‡

You've seen ChatGPT. You've used Claude. You've thought β€” "I want to build something like this."

Then you Googled "how to build AI chatbot" and got hit with a wall of tutorials β€” LangChain, vector databases, embeddings, RAG pipelines, fine-tuning... 😡

And you closed the tab. Because it felt way too complicated.

Here's the truth nobody tells you. 🀫

A fully working, deployed, impressive AI chatbot takes under 100 lines of code. No LangChain. No vector databases. No PhD required.

This post is that chatbot. Complete. Working. Copy-paste ready.

By the end of this β€” you will have a deployed AI chatbot with a live URL. Not tomorrow. Today. ⏱️

Let's go. πŸ‘‡


πŸ—ΊοΈ What We're Building

Before we touch code β€” here's exactly what we're building: 🎯

βœ… What the final chatbot has:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
πŸ€– Custom AI personality (you define it)
πŸ’¬ Full conversation memory (remembers context)
⚑ Streaming responses (words appear instantly)
πŸ“± Mobile responsive UI
🌐 Deployed on Vercel with live URL
πŸ”’ API key secured server-side
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total lines of code: ~95 🀯
Time to build: 60-90 minutes ⏱️
Cost: Free tier covers hundreds of chats πŸ†“
Enter fullscreen mode Exit fullscreen mode

Let's build it step by step. πŸ‘‡


πŸ› οΈ Step 1 β€” Project Setup (5 Minutes)

# Create Next.js app πŸš€
npx create-next-app@latest ai-chatbot --typescript --tailwind --app
cd ai-chatbot

# Install OpenAI SDK πŸ“¦
npm install openai

# Create environment file πŸ”’
touch .env.local
Enter fullscreen mode Exit fullscreen mode
# .env.local β€” add your OpenAI API key
OPENAI_API_KEY=sk-your-key-here

# Get free key at: platform.openai.com πŸ†“
Enter fullscreen mode Exit fullscreen mode
# .gitignore β€” make sure this line exists! ⚠️
.env.local
Enter fullscreen mode Exit fullscreen mode

That's setup. Done. 5 minutes. βœ…


πŸ› οΈ Step 2 β€” The API Route (20 Lines) πŸ”₯

This is the backend brain of your chatbot. Every message goes through here. 🧠

// app/api/chat/route.ts
// 20 lines. Full AI backend. 🀯

import OpenAI from 'openai'

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY  // πŸ”’ server-side only
})

export async function POST(req: Request) {
  const { messages } = await req.json()

  const stream = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    stream: true,              // ⚑ words appear instantly
    messages: [
      {
        role: 'system',
        // 🎨 THIS is where you define your chatbot's personality!
        content: `You are a helpful coding assistant specializing in 
                  React and Next.js. You give concise, practical answers 
                  with code examples. You're friendly and encouraging 
                  to beginners. Never give up on a question.`
      },
      ...messages             // πŸ’¬ full conversation history
    ]
  })

  // Stream response back to frontend ✨
  return new Response(stream.toReadableStream())
}
Enter fullscreen mode Exit fullscreen mode

20 lines. Full streaming AI backend. That's it. 🎯

πŸ’‘ The personality trick: Change that system prompt and you have a completely different chatbot. A fitness coach. A recipe sugganger. A sarcastic code reviewer. The system prompt IS the product. 🎨


πŸ› οΈ Step 3 β€” The Chat UI (75 Lines) πŸ’¬

Now the frontend. This is the part users see and interact with. πŸ‘‡

// app/page.tsx
// 75 lines. Full chat UI with streaming. πŸ”₯

'use client'

import { useState, useRef, useEffect } from 'react'

// Message type definition πŸ“
type Message = {
  role: 'user' | 'assistant'
  content: string
}

export default function ChatBot() {
  const [messages, setMessages]   = useState<Message[]>([])
  const [input, setInput]         = useState('')
  const [loading, setLoading]     = useState(false)
  const bottomRef                 = useRef<HTMLDivElement>(null)

  // Auto-scroll to latest message πŸ“œ
  useEffect(() => {
    bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
  }, [messages])

  const sendMessage = async () => {
    if (!input.trim() || loading) return

    const userMessage: Message = { role: 'user', content: input }
    const updatedMessages      = [...messages, userMessage]

    setMessages(updatedMessages)
    setInput('')
    setLoading(true)

    // Add empty assistant message β€” we'll stream into it ⚑
    setMessages(prev => [...prev, { role: 'assistant', content: '' }])

    try {
      const res = await fetch('/api/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ messages: updatedMessages })
      })

      // Read the stream chunk by chunk 🌊
      const reader  = res.body!.getReader()
      const decoder = new TextDecoder()

      while (true) {
        const { done, value } = await reader.read()
        if (done) break

        const chunk = decoder.decode(value)

        // Parse SSE chunks from OpenAI stream πŸ“¦
        const lines = chunk.split('\n').filter(l => l.startsWith('data: '))
        for (const line of lines) {
          const data = line.replace('data: ', '')
          if (data === '[DONE]') break
          try {
            const parsed = JSON.parse(data)
            const text   = parsed.choices[0]?.delta?.content || ''
            // Append each word to the last message ✨
            setMessages(prev => {
              const updated = [...prev]
              updated[updated.length - 1].content += text
              return updated
            })
          } catch { /* skip malformed chunks */ }
        }
      }
    } catch (err) {
      setMessages(prev => {
        const updated = [...prev]
        updated[updated.length - 1].content = '❌ Something went wrong. Try again.'
        return updated
      })
    } finally {
      setLoading(false)
    }
  }

  // Send on Enter key ⌨️
  const handleKey = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault()
      sendMessage()
    }
  }

  return (
    <div className="flex flex-col h-screen max-w-2xl mx-auto p-4">

      {/* Header πŸ€– */}
      <div className="text-center py-4 border-b">
        <h1 className="text-2xl font-bold">πŸ€– AI Coding Assistant</h1>
        <p className="text-gray-500 text-sm">Powered by GPT-4o-mini</p>
      </div>

      {/* Messages πŸ’¬ */}
      <div className="flex-1 overflow-y-auto py-4 space-y-4">

        {/* Empty state */}
        {messages.length === 0 && (
          <div className="text-center text-gray-400 mt-20">
            <p className="text-4xl mb-3">πŸ’¬</p>
            <p className="text-lg">Ask me anything about React or Next.js!</p>
            <p className="text-sm mt-2">I remember our whole conversation 🧠</p>
          </div>
        )}

        {/* Message bubbles */}
        {messages.map((msg, i) => (
          <div
            key={i}
            className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
          >
            <div
              className={`max-w-[80%] px-4 py-3 rounded-2xl text-sm leading-relaxed
                ${msg.role === 'user'
                  ? 'bg-blue-600 text-white rounded-br-none'      // πŸ‘€ user β€” right
                  : 'bg-gray-100 text-gray-800 rounded-bl-none'   // πŸ€– AI β€” left
                }`}
            >
              {/* Streaming cursor effect β–Š */}
              {msg.content || (loading && i === messages.length - 1
                ? <span className="animate-pulse">β–Š</span>
                : '...'
              )}
            </div>
          </div>
        ))}

        <div ref={bottomRef} /> {/* scroll anchor */}
      </div>

      {/* Input area ⌨️ */}
      <div className="border-t pt-4 flex gap-2">
        <textarea
          value={input}
          onChange={e => setInput(e.target.value)}
          onKeyDown={handleKey}
          placeholder="Ask anything... (Enter to send)"
          disabled={loading}
          rows={1}
          className="flex-1 resize-none border rounded-xl px-4 py-3 text-sm
                     focus:outline-none focus:ring-2 focus:ring-blue-500
                     disabled:opacity-50"
        />
        <button
          onClick={sendMessage}
          disabled={loading || !input.trim()}
          className="px-5 py-3 bg-blue-600 text-white rounded-xl font-medium
                     hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed
                     transition-colors text-sm"
        >
          {loading ? '⏳' : 'πŸš€'}
        </button>
      </div>

    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Total: ~95 lines. Full working chatbot. Done. βœ…


πŸ› οΈ Step 4 β€” Deploy in 5 Minutes 🌐

# Push to GitHub first πŸ“€
git init
git add .
git commit -m "feat: add AI chatbot πŸ€–"
git remote add origin https://github.com/you/ai-chatbot.git
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Then on Vercel: πŸ‘‡

  1. Go to vercel.com β†’ New Project
  2. Import your GitHub repo
  3. Add environment variable: OPENAI_API_KEY = your key πŸ”‘
  4. Click Deploy βœ…

5 minutes. Live URL. Done. πŸŽ‰

Your chatbot is now live at:
https://ai-chatbot-yourname.vercel.app 🌐

Share it. Add it to your resume. Demo it in interviews.
Enter fullscreen mode Exit fullscreen mode

🎨 Level Up β€” 5 Easy Customizations

The base chatbot is great. These 5 tweaks make it portfolio-worthy. πŸ’Ž

Customization 1 β€” Change the Personality 🎭

// Change the system prompt = completely different product 🎨

// 🍳 Recipe assistant
content: `You are a friendly chef who suggests recipes based on 
          ingredients the user has. Always ask what ingredients 
          they have before suggesting. Give step-by-step instructions.`

// πŸ’ͺ Fitness coach
content: `You are an encouraging fitness coach. Create personalized 
          workout plans. Always ask about fitness level and goals first.
          Use motivational language. πŸ’ͺ`

// πŸ˜‚ Sarcastic code reviewer
content: `You are a brutally honest (but funny) code reviewer. 
          Point out every issue with light sarcasm. Always end with 
          something genuinely helpful. Keep it fun, not mean.`

// πŸŽ“ Interview prep coach
content: `You are a senior developer conducting mock interviews. 
          Ask technical questions one at a time. Give detailed feedback
          on every answer. Be encouraging but honest about gaps.`
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Portfolio tip: Each system prompt = a different product. Build 3 chatbots with different personalities = 3 portfolio projects. Same code, different value propositions. 🎯

Customization 2 β€” Add a Clear Chat Button πŸ—‘οΈ

// Add this button next to the header β€” 3 lines 🧹
<button
  onClick={() => setMessages([])}
  className="text-sm text-gray-400 hover:text-gray-600"
>
  πŸ—‘οΈ Clear chat
</button>
Enter fullscreen mode Exit fullscreen mode

Customization 3 β€” Show Message Timestamps ⏰

// Update Message type
type Message = {
  role: 'user' | 'assistant'
  content: string
  timestamp: Date  // add this ⏰
}

// Add timestamp when creating messages
const userMessage: Message = {
  role: 'user',
  content: input,
  timestamp: new Date()  // βœ…
}

// Show it in the bubble
<div className="text-xs opacity-50 mt-1">
  {msg.timestamp.toLocaleTimeString()}
</div>
Enter fullscreen mode Exit fullscreen mode

Customization 4 β€” Copy Message Button πŸ“‹

// Add copy button to each AI message β€” super useful feature ✨
const [copied, setCopied] = useState<number | null>(null)

const copyMessage = async (text: string, index: number) => {
  await navigator.clipboard.writeText(text)
  setCopied(index)
  setTimeout(() => setCopied(null), 2000) // reset after 2s
}

// In the AI message bubble:
{msg.role === 'assistant' && (
  <button
    onClick={() => copyMessage(msg.content, i)}
    className="text-xs text-gray-400 hover:text-gray-600 mt-1"
  >
    {copied === i ? 'βœ… Copied!' : 'πŸ“‹ Copy'}
  </button>
)}
Enter fullscreen mode Exit fullscreen mode

Customization 5 β€” Suggested Starter Questions πŸ’‘

// Show clickable starter questions when chat is empty 🎯
const starters = [
  "How do I use useEffect correctly? πŸ€”",
  "What's the difference between SSR and SSG?",
  "How do I handle forms in React? πŸ“",
  "Explain React hooks for a beginner πŸͺ"
]

// In the empty state:
{messages.length === 0 && (
  <div className="space-y-2 mt-6">
    {starters.map((q, i) => (
      <button
        key={i}
        onClick={() => setInput(q)}
        className="w-full text-left px-4 py-3 border rounded-xl
                   text-sm text-gray-600 hover:bg-gray-50 transition-colors"
      >
        {q}
      </button>
    ))}
  </div>
)}
Enter fullscreen mode Exit fullscreen mode

πŸ“Š What You Just Built vs What Others Think You Built

What you actually built πŸ‘‡
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
~95 lines of code
1 API route
1 React component
30 minutes of actual coding

What interviewers THINK when they see it πŸ‘‡
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
"This person knows AI APIs"          βœ…
"They understand streaming"          βœ…
"They can build full-stack features" βœ…
"They care about UX (auto-scroll)"   βœ…
"They know TypeScript"               βœ…
"This is deployed and working"       βœ…
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Perception: Senior-level thinking 🎯
Reality: 95 lines + 30 minutes πŸ˜„
Enter fullscreen mode Exit fullscreen mode

The gap between what you build and what people perceive is massive. Use it. πŸš€


🎯 Challenge β€” Build It Right Now

Set a timer for 90 minutes. πŸ‘‡

0:00  β†’ npx create-next-app@latest ai-chatbot
0:10  β†’ Write the API route (20 lines)
0:35  β†’ Write the chat UI (75 lines)
1:10  β†’ Deploy on Vercel
1:25  β†’ Add your favorite customization
1:30  β†’ Share the link in comments below πŸ‘‡
Enter fullscreen mode Exit fullscreen mode

90 minutes. Working AI chatbot. Live URL. On your resume. 🏁


πŸ’¬ Your Turn!

Built it? πŸŽ‰ Drop your live link in the comments β€” I'll check every single one!

Stuck on something? πŸ€” Drop your error in the comments β€” let's debug it together!

Planning to build but haven't started yet? ⏰ Drop a ⏱️ β€” I'll check back in 90 minutes!

Share this with someone who's been "planning to learn AI" for months. Today is the day. πŸ™

Drop a ❀️ if this finally made AI feel approachable β€” helps more developers find this before they give up! πŸ”₯


πŸ”– P.S. β€” The system prompt is the most powerful line in this entire codebase. Change it. Play with it. That one line is what separates a generic chatbot from a product people actually want to use.

Top comments (0)