Next.js AI Chat App
Full-stack AI app: chat UI + a Route Handler that calls the OpenAI Responses API with the key kept server-side.
7 steps
Shown with defaults: npm, pip, Node LTS, Python 3.12 and the template's default add-ons.
1. Install and select Node.js with nvm
runtimenvm lets you install several Node.js versions side by side and switch per project. `nvm use` activates it for this shell.
bashnvm install --ltsnvm use --lts- Expected result
- `node -v` prints the selected version.
- Verify
- node -v && npm -v
- OS notes
- macOS/Linux/WSL: install nvm with `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash`, then reopen the terminal. Windows: use nvm-windows (github.com/coreybutler/nvm-windows) from an elevated terminal, or fnm.
2. Scaffold a Next.js app
templatecreate-next-app generates an App Router project. Flags make it non-interactive and reflect your add-on choices (TypeScript, Tailwind, ESLint).
bashnpx create-next-app@latest nvx-ai-chat --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --use-npm --yes- Expected result
- A ./nvx-ai-chat folder with src/app, package.json and installed dependencies.
- Verify
- ls nvx-ai-chat/src/app
- OS notes
- Requires Node.js 20.9 or newer. On Windows, run in PowerShell or Windows Terminal.
3. Enter the project folder
templateThe scaffolder created ./nvx-ai-chat. Move into it before installing anything else.
bashcd nvx-ai-chat- Expected result
- You are inside ./nvx-ai-chat
- Verify
- ls package.json
4. Install the OpenAI SDK
templateUsed only on the server inside the Route Handler.
bashnpm install openai- Expected result
- openai in dependencies.
- Verify
- npm ls openai
5. Write the chat API and UI
templateThe browser posts to /api/chat; the server calls OpenAI. Put your key in .env.local (git-ignored by create-next-app).
Files written by this step: src/app/api/chat/route.ts, src/app/page.tsx, .env.local
src/app/api/chat/route.tsimport OpenAI from "openai"; export async function POST(req: Request) { if (!process.env.OPENAI_API_KEY) { return Response.json({ error: "Set OPENAI_API_KEY in .env.local" }, { status: 503 }); } const { message } = await req.json(); const client = new OpenAI(); try { const response = await client.responses.create({ model: process.env.OPENAI_MODEL || "gpt-6.1-sol", instructions: "You are a concise, friendly assistant.", input: String(message ?? "").slice(0, 4000), }); return Response.json({ answer: response.output_text }); } catch (error) { const status = error instanceof OpenAI.APIError && error.status ? error.status : 500; return Response.json({ error: "OpenAI request failed (" + status + ")" }, { status: 502 }); } }src/app/page.tsx"use client"; import { useState } from "react"; export default function Chat() { const [messages, setMessages] = useState<{ role: string; text: string }[]>([]); const [input, setInput] = useState(""); const [busy, setBusy] = useState(false); async function send() { const message = input.trim(); if (!message || busy) return; setInput(""); setBusy(true); setMessages((m) => [...m, { role: "you", text: message }]); try { const res = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message }) }); const data = await res.json(); setMessages((m) => [...m, { role: "ai", text: data.answer ?? data.error ?? "No answer" }]); } finally { setBusy(false); } } return ( <main className="mx-auto flex h-screen max-w-2xl flex-col gap-4 p-6"> <h1 className="text-2xl font-bold">nvx-ai-chat</h1> <ul className="flex-1 space-y-3 overflow-y-auto" aria-live="polite"> {messages.map((m, i) => ( <li key={i} className={m.role === "you" ? "text-right" : ""}> <span className="inline-block rounded-xl border px-3 py-2 whitespace-pre-wrap">{m.text}</span> </li> ))} </ul> <form className="flex gap-2" onSubmit={(e) => { e.preventDefault(); send(); }}> <label htmlFor="msg" className="sr-only">Message</label> <input id="msg" className="flex-1 rounded-xl border px-3 py-2" value={input} onChange={(e) => setInput(e.target.value)} placeholder="Ask anything..." /> <button className="rounded-xl border px-4 py-2" disabled={busy}>{busy ? "..." : "Send"}</button> </form> </main> ); }.env.localOPENAI_API_KEY= OPENAI_MODEL=gpt-6.1-sol- Expected result
- Route and page files exist.
6. Start chatting
templaterun manually · dev serverAdd your key to .env.local, restart, and send a message.
bashnpm run dev- Expected result
- Replies appear in the chat.
- Verify
- curl -s -X POST http://localhost:3000/api/chat -H "Content-Type: application/json" -d "{\"message\":\"hi\"}"
7. Create a production build
templateMake sure everything compiles before deploying.
bashnpm run build- Expected result
- Build succeeds.
- Verify
- ls .next