#Next.js#React

Build AI Apps with Next.js 16: Server Actions and Streaming Output

AI apps must call LLMs securely, stream tokens to the frontend in real time, and manage session state. This guide uses Next.js 16 App Router, Server Actions, and AI SDK to implement a clean, streamable AI chat page ready for production.

Key Points

  • Topic Focus: Next.js、React
  • Suitable for: indie hackers, full-stack engineers, AI application developers
  • Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Author @admin·Published July 26, 2026·Updated July 26, 2026

Building an AI application essentially means solving three problems: how to call an LLM securely without exposing the API key to the frontend, how to stream tokens to users in real time, and how to manage state across multi-turn interactions. Next.js 16's App Router provides convenient primitives for all three: Server Components / Server Actions keep secrets on the server, streaming + AI SDK display tokens as they are generated, and cookies or a database store sessions.

The complete minimal implementation below explains how it all works.

1. Project Structure

text
app/
  chat/page.tsx        // Client: message list + input box
  actions.ts           // Server Action: call the LLM and stream the response
lib/
  ai.ts                // Model client wrapper

Secrets (ANTHROPIC_API_KEY / OPENAI_API_KEY) belong only in server-side environment variables and are never exposed to the frontend. This is an inherent advantage of a Server Action over having the frontend directly fetch an /api route: it is a server-side function, so none of its code is bundled into the client in the first place.

2. Model Wrapper (lib/ai.ts)

Use Vercel's ai SDK. It normalizes the streaming protocols of different providers behind one interface, eliminating the need to implement SSE parsing manually.

ts
import { createAnthropic } from "@ai-sdk/anthropic";
import { streamText } from "ai";

const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

export function chat(system: string, messages: { role: "user" | "assistant"; content: string }[]) {
  return streamText({
    model: anthropic("claude-sonnet-5"),
    system,
    messages,
  });
}

To switch models, change only this file: createOpenAI, createGoogleGenerativeAI, and createMistral use the same structure.

3. Server Action: Streaming Calls (app/actions.ts)

ts
"use server";
import { chat } from "@/lib/ai";

export async function askAI(input: string) {
  const result = chat(
    "You are the assistant for the CodeNodeX AI community. Answer concisely and accurately in Chinese.",
    [{ role: "user", content: input }],
  );
  // streamUI / streaming text: return the result to the client component as a stream
  return result.toUIMessageStream();
}

A Server Action is marked with "use server". The frontend calls it like an asynchronous function, but it actually runs on the server. You do not need to write an /api/route, and you do not need to worry about leaking the key.

4. Client: Display Tokens as They Arrive (app/chat/page.tsx)

Use useChat (ai/react) to manage state and receive the stream:

tsx
"use client";
import { useChat } from "ai/react";

export default function ChatPage() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: "/api/chat", // You can also call the action directly; a route is more intuitive here
  });

  return (
    <main className="mx-auto max-w-2xl p-6">
      <div className="space-y-4">
        {messages.map((m) => (
          <div key={m.id} className={m.role === "user" ? "text-right" : ""}>
            <span className="whitespace-pre-wrap">{m.content}</span>
          </div>
        ))}
        {isLoading && <span className="animate-pulse">Thinking…</span>}
      </div>
      <form onSubmit={handleSubmit} className="mt-6 flex gap-2">
        <input value={input} onChange={handleInputChange}
          className="flex-1 rounded border px-3 py-2" placeholder="Ask something…" />
        <button className="rounded bg-black px-4 py-2 text-white">Send</button>
      </form>
    </main>
  );
}

Tokens are appended to the end of messages as they are generated, producing the "typewriter" effect users see. Behind the scenes, AI SDK handles SSE and stream parsing for you; you only need to work with messages.

The state managed by useChat exists only in memory and disappears after a refresh. To persist it, save every turn to the database in the Server Action and associate it with a sessionId stored in a cookie:

ts
"use server";
import { cookies } from "next/headers";
import { db } from "@/lib/db";

export async function askAI(input: string) {
  const sessionId = cookies().get("sid")?.value ?? crypto.randomUUID();
  cookies().set("sid", sessionId);
  await db.message.create({ data: { sessionId, role: "user", content: input } });
  // ...call the model and return the stream; save the assistant message before/after returning
}

The next time the user returns, load the history by sessionId to continue the multi-turn conversation.

6. Three Things to Consider in Production

  1. Rate limiting and abuse prevention. AI calls are billed by token, so the endpoint must be rate-limited by user/IP to prevent abuse. upstash/ratelimit or middleware will work.
  2. Input validation. Validate input with zod, limit its length, and filter dangerous content. A Server Action is not "secure by default"; it is a publicly accessible entry point.
  3. Edge vs Node runtime. Streaming responses have lower latency and smaller cold starts on the Edge Runtime, but not every SDK is Edge-compatible. Once traffic grows, it is worth benchmarking both runtimes.

Summary

Next.js 16 already provides an excellent experience for building AI applications:

  • Secure secrets: Server Actions/Components run on the server, so keys never enter the frontend bundle.
  • Simple streaming: AI SDK + useChat can deliver a typewriter-style conversation in just a few dozen lines.
  • Natural state management: Cookies and a database handle sessions, and App Router's data-flow model is sufficient.

Once these three pieces are in place, a production-ready AI chat page can be delivered in an afternoon. The remaining effort is better spent on prompt tuning, rate limiting, and product experience than on hand-crafting an SSE pipeline.

Related

Related Content