Next.js 14 + AI: Building Intelligent Web Applications
Combine the power of Next.js App Router with AI capabilities. Learn streaming responses, Server Actions, and building production-ready AI-powered features.

DevForge Team
AI Development Educators

Why Next.js + AI is the Perfect Stack
Next.js 14 with the App Router is arguably the best framework for building AI-powered web applications today. Here's why:
Server Components — AI API calls belong on the server. Server Components let you call Claude directly in your components without extra API routes, without exposing your API key, and without client-server round trips.
Streaming — AI responses are streamed token by token. Next.js has excellent built-in support for streaming from both Server Components and API Routes.
Server Actions — Form submissions and mutations that call Claude without needing to build a full API layer.
Edge Runtime — Some AI features benefit from running closer to users. Next.js makes deploying to Vercel's edge network trivial.
In this tutorial, we'll build an AI-powered coding assistant that includes:
- Streaming chat interface
- Code generation with syntax highlighting
- Conversation history
- Context-aware responses (the AI knows what code the user is viewing)
Project Setup
npx create-next-app@latest ai-coding-assistant --typescript --tailwind --app
cd ai-coding-assistant
npm install @anthropic-ai/sdk aiThe ai package from Vercel makes streaming much easier — we'll use it.
Set up environment variables in .env.local:
ANTHROPIC_API_KEY=sk-ant-...Building the Streaming API Route
// app/api/chat/route.ts
import Anthropic from "@anthropic-ai/sdk";
import { NextRequest } from "next/server";
const client = new Anthropic();
export async function POST(req: NextRequest) {
const { messages, systemPrompt } = await req.json();
const stream = await client.messages.stream({
model: "claude-opus-4-5",
max_tokens: 4096,
system: systemPrompt || "You are a helpful coding assistant.",
messages,
});
const readableStream = new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
if (
chunk.type === "content_block_delta" &&
chunk.delta.type === "text_delta"
) {
const text = chunk.delta.text;
controller.enqueue(new TextEncoder().encode(text));
}
}
controller.close();
},
});
return new Response(readableStream, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Transfer-Encoding": "chunked",
},
});
}The Chat UI Component
// components/ChatInterface.tsx
"use client";
import { useState, useRef, useEffect } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
interface Message {
role: "user" | "assistant";
content: string;
}
export function ChatInterface({ contextCode }: { contextCode?: string }) {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [isStreaming, setIsStreaming] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const systemPrompt = `You are an expert coding assistant integrated into DevForge Academy.
You help developers understand and write code.
${contextCode ? `The user is currently viewing this code:
\`\`\`
${contextCode}
\`\`\`
Reference this code in your responses when relevant.` : ""}
Format code blocks with appropriate language tags.
Be concise but thorough. Explain your reasoning.`;
async function sendMessage() {
if (!input.trim() || isStreaming) return;
const userMessage: Message = { role: "user", content: input };
const updatedMessages = [...messages, userMessage];
setMessages(updatedMessages);
setInput("");
setIsStreaming(true);
// Add empty assistant message to stream into
setMessages((prev) => [
...prev,
{ role: "assistant", content: "" },
]);
try {
const response = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
messages: updatedMessages,
systemPrompt,
}),
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (reader) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value, { stream: true });
setMessages((prev) => {
const updated = [...prev];
updated[updated.length - 1] = {
role: "assistant",
content: updated[updated.length - 1].content + text,
};
return updated;
});
}
} finally {
setIsStreaming(false);
}
}
return (
<div className="flex flex-col h-full">
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg, i) => (
<MessageBubble key={i} message={msg} />
))}
{isStreaming && (
<div className="flex gap-1">
<span className="animate-bounce">●</span>
<span className="animate-bounce delay-100">●</span>
<span className="animate-bounce delay-200">●</span>
</div>
)}
<div ref={messagesEndRef} />
</div>
<div className="border-t p-4">
<div className="flex gap-2">
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}}
placeholder="Ask about code, get explanations, generate functions..."
className="flex-1 resize-none rounded-lg border p-3 text-sm"
rows={3}
/>
<button
onClick={sendMessage}
disabled={isStreaming || !input.trim()}
className="px-4 rounded-lg bg-violet-600 text-white disabled:opacity-50"
>
Send
</button>
</div>
</div>
</div>
);
}Server Actions for AI Features
Server Actions let you call AI directly from forms:
// app/actions/explain.ts
"use server";
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
export async function explainCode(code: string): Promise<string> {
const message = await client.messages.create({
model: "claude-haiku-3-5", // Use cheaper model for simple tasks
max_tokens: 1024,
messages: [{
role: "user",
content: `Explain this code in 3-5 sentences, focusing on what it does and any notable patterns:
\`\`\`
${code}
\`\`\``
}]
});
return message.content[0].type === "text"
? message.content[0].text
: "";
}
// Use in a component:
// import { explainCode } from "@/app/actions/explain";
// const explanation = await explainCode(selectedCode);Implementing Context-Aware AI
The key to great AI coding assistants is context. The more you know about what the user is doing, the better the responses:
// Build rich context for the AI
function buildSystemPrompt(context: {
currentFile?: string;
language?: string;
selectedCode?: string;
errorMessage?: string;
userStack?: string[];
}) {
const lines = [
"You are an expert coding assistant integrated into a code editor.",
"",
"Current context:",
];
if (context.currentFile) {
lines.push(`- File: ${context.currentFile}`);
}
if (context.language) {
lines.push(`- Language: ${context.language}`);
}
if (context.userStack?.length) {
lines.push(`- Tech stack: ${context.userStack.join(", ")}`);
}
if (context.selectedCode) {
lines.push("", "Selected code:", "```", context.selectedCode, "```");
}
if (context.errorMessage) {
lines.push("", `Current error: ${context.errorMessage}`);
}
lines.push(
"",
"Provide precise, actionable responses.",
"Format code blocks with language tags.",
"Be concise unless the user asks for detail."
);
return lines.join("\n");
}Performance Optimizations
1. Model Selection
Match model to task complexity:
- claude-haiku-3-5: Simple explanations, quick fixes (fast, cheap)
- claude-opus-4-5: Complex architecture, code review (slower, more capable)
2. Caching with React cache()
import { cache } from "react";
export const getExplanation = cache(async (codeHash: string) => {
// This will only run once per request even if called multiple times
return fetchExplanation(codeHash);
});3. Abort Controller for Cancellation
const controller = new AbortController();
const response = await fetch("/api/chat", {
method: "POST",
signal: controller.signal,
body: JSON.stringify({ messages }),
});
// Cancel if user clicks stop:
cancelButton.onclick = () => controller.abort();Deploying to Vercel
Next.js + Vercel + Anthropic is a natural combination:
vercel deployAdd your environment variables in the Vercel dashboard. The streaming will work automatically — Vercel supports streaming responses natively.
Set appropriate function timeouts for long AI responses:
// vercel.json
{
"functions": {
"app/api/chat/route.ts": {
"maxDuration": 60
}
}
}What You've Built
You now have the foundation for a production AI coding assistant:
- Streaming chat interface
- System prompt with code context
- Server Actions for simple AI tasks
- Context-aware responses
From here, you can add: RAG over your codebase, voice input, multi-file editing, automated testing generation, and much more. The architecture scales to any complexity.