Mmcp.market

nextjs-chatbot skill

by laguagu·laguagu/claude-code-nextjs-skills·67 stars·MIT

Advanced patterns for production Next.js web chatbots built with AI SDK 7 (with a fallback table for projects still on v6) + ai-elements. Covers tool calling with human-in-the-loop (HITL) approval, PostgreSQL session persistence, consent gating, SQL-first search, per-tool UI rendering, popup widget embedding, message feedback, follow-up suggestions, scope enforcement, streaming error handling, and evals. Use when building a customer support bot, conversational interface, or any web chatbot needing tool approval, database sessions, or custom tool output components. Not a scaffolding tool — use `/ai-app` to scaffold from scratch, `/ai-sdk-7` for general SDK questions, `/ai-elements` for chat UI components. For multi-platform bots, consult the official Chat SDK docs.

A100/100content scan

Is the nextjs-chatbot skill safe?

Clean: nothing in its files matched our rules. We read 10 files in the folder on 2026-09-28.

No findings.

Install the nextjs-chatbot skill

A skill is a folder. Copy it into your agent's skills folder and the agent loads it when the task matches its description.

git clone --depth 1 https://github.com/laguagu/claude-code-nextjs-skills.git /tmp/claude-code-nextjs-skills
mkdir -p ~/.claude/skills
cp -r /tmp/claude-code-nextjs-skills/skills/nextjs-chatbot ~/.claude/skills/nextjs-chatbot
available in every project

In the Claude apps, zip the folder and upload it from the Skills settings. The folder on GitHub

The instructions your agent would load

SKILL.md as published, without the frontmatter. Read it on GitHub

Next.js Chatbot

Opinionated blueprint for production web chatbots. Focuses on patterns not covered by /ai-sdk-7, /ai-elements, or /nextjs-shadcn — use those skills for general SDK, component, and framework questions. For multi-platform bots (Slack, Teams, Discord), consult the Chat SDK docs.

Stack defaults

the product. Read the version from the provider's live catalog, not from here.

  • Runtime: bun
  • Model: a non-reasoning flagship with reasoning effort off — chat latency is

⚠️ Check package.json — on a v6 codebase the names below are wrong; see If the project is still on ai@6.

  • AI SDK: ai@7 — ToolLoopAgent, createAgentUIStreamResponse.
  • UI: shadcn/ui (Base UI base) + ai-elements (see /ai-elements for component docs)
  • Scroll: @shadcn/react MessageScroller — don't hand-roll stick-to-bottom
  • Markdown: shadcn typeset (typeset typeset-chat), streaming-stable
  • ORM: Drizzle + PostgreSQL
  • State: Zustand for client-side chat state (consent, session, suggestions)
  • Attachments: See /ai-elements Attachments component for file upload

Recommended MCP servers

  • next-devtools (next-devtools-mcp@latest via npx) — route inspection, build diagnostics. See nextjs.org/docs/app/guides/mcp
  • ai-elements (via mcp-remote → https://registry.ai-sdk.dev/api/mcp) — component registry search

Add both to the project's .mcp.json (claude mcp add --scope project writes it); .claude/settings.json only enables and permits servers, it does not define them.

Agent setup

export function createAgent(opts?: { model?: LanguageModel }) {
  return new ToolLoopAgent({
    model: opts?.model ?? openai(CHAT_MODEL), // one constant, read from env
    instructions,                             // ToolLoopAgent uses this in v6 and v7
    reasoning: "low",                         // portable top-level, see below
    tools,
    stopWhen: isStepCount(10),
  });
}
export const agent = createAgent();
export type AgentUIMessage = InferAgentUIMessage<typeof agent>;

Export both factory and singleton — factory needed for benchmarks. Wrap with devToolsMiddleware() in dev.

⚠️ Reasoning effort is the portable top-level reasoning, not providerOptions.openai.reasoningEffort — set both and the top-level one is silently ignored, so your setting does nothing. Raising it above "low" means keeping sendReasoning: false on the stream: the Responses API rejects a reasoning part that comes back on the next turn without its required follower, and a context trimmer that strips step-start/tool-* usually doesn't strip this one.

Route handler

export const maxDuration = 60;

export async function POST(request: Request) {
  const { messages, chatId, ...consent } = await request.json();
  // 1. Validate consent — return 403 if missing
  // 2. Await session upsert BEFORE streaming (FK dependency)
  return createAgentUIStreamResponse({
    agent,
    uiMessages: messages,
    generateMessageId: createIdGenerator({ prefix: "msg", size: 16 }),
    consumeSseStream: ({ stream }) => consumeStream({ stream }),
    experimental_transform: smoothStream({ delayInMs: 15, chunking: "word" }),
    sendReasoning: false,
    onEnd: async ({ responseMessage }) => { /* save — see persistence.md */ },
  });
}

Azure OpenAI model routing

Choose Responses or Chat Completions from the deployed model's documented capabilities and the project's requirements. Azure deployment names are user-defined: do not infer model family from a name regex. If a multi-turn request fails, check retained tool-call/result pairs and provider metadata against the installed Azure provider docs before changing endpoints.

If the project is still on ai@6

Everything on this page is patterns, not API surface, so it carries over — but the snippets above are v7 names and v6 doesn't know them. Some fail loudly (an undefined import); the dangerous ones fail quietly — a callback that never fires or a step limit that never applies, which reads as a model bug rather than a typo. Check package.json and translate back:

Use /ai-sdk-6 for the rest; /ai-sdk if you don't know the version yet.

Never let raw error text reach the browser

onError streams its return value verbatim to the client. String(error) there puts a provider 401, an endpoint URL or a Postgres constraint on a customer's screen — and logs nothing, so you find out from a screenshot.

onError: (error) => {          // redact diagnostics; return a fixed public message
  logSafeError(error); // app helper: log redacted diagnostics and a correlation ID
  return "Something went wrong generating the answer. Try again.";
}

The client needs the same guard separately: a non-2xx response never goes through the stream, so useChat's onError receives an Error whose message is the raw response body. Map it to a fixed set of sentences before rendering.

Custom streams need X-Accel-Buffering: no

Only for responses you build yourself — NDJSON upload progress, an SSE grid fill. createAgentUIStreamResponse already sets it. Without the header a buffering proxy (nginx, Cloud Run) holds the whole stream and delivers the progress bar in one jump at the end, which is worse than having no progress bar. Pair it with Cache-Control: no-store, no-transform.

⚠️ An error emitted inside an already-open stream arrives with HTTP 200 — the status is long gone. Rejections that happen before the stream opens do use real codes. Client code has to handle both shapes.

Client transport patterns

Dynamic context via transport body

Inject per-request context (e.g., a saved document for edit mode) from the client:

// Simple: body function on DefaultChatTransport
const transport = new DefaultChatTransport({
  api: "/api/chat",
  body: () => ({ documentContext: activeDocRef.current }),
});

// Fine-grained: prepareSendMessagesRequest (official API)
const transport = new DefaultChatTransport({
  prepareSendMessagesRequest: ({ id, messages }) => ({
    body: { id, message: messages.at(-1), context: extraRef.current },
  }),
});

Server reads extra fields from the request body and passes to agent factory.

Chat remount (new conversation)

Always call stop() before clearing — otherwise the active stream writes into the new conversation:

const { messages, sendMessage, stop, setMessages } = useChat({ transport });

const startNew = useCallback(() => {
  stop();                     // Cancel active stream FIRST
  setMessages([]);
  clearStoredMessages();      // If using localStorage
  setChatId(crypto.randomUUID());
  setConversationKey(k => k + 1);
}, [stop, setMessages]);

localStorage persistence (no DB)

For lightweight chatbots that don't need server-side persistence:

// Load on init via messages prop (NOT useEffect + setMessages)
const initialMessages = useMemo(() => {
  const stored = loadStoredMessages();
  return stored?.length ? (stored as UIMessage[]) : undefined;
}, []);

const { messages, sendMessage } = useChat({
  transport,
  messages: initialMessages,    // useChat accepts initial messages
  onFinish: ({ messages: all }) => saveStoredMessages(all),
});

Hydration: Zustand + localStorage

Zustand stores that read localStorage in create() cause React hydration mismatch (server: false, client: true). Fix with a mounted gate:

const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);

// In render:
{!mounted || !hasConsented ? <ConsentGate /> : <Chat />}

Same class of bug, different cause: relative timestamps ("6 seconds ago") on a server-rendered history list. The server and the client compute a different string, React calls it a mismatch and throws the whole subtree away to re-render it. Either render an absolute, locale-independent date, or gate the relative one behind the same mounted flag.

Adding a new tool

  1. Create lib/ai/tools/my-tool.ts with tool() from ai
  2. Export from lib/ai/tools/index.ts
  3. Add to tools object in the agent file
  4. Document in the agent's instructions string
  5. Add UI renderer in chat-message.tsx (handle tool-myTool part type)

Structured output tools (schema-as-output)

When the tool generates structured data (not query/compute), use the pass-through pattern — the Zod schema defines the output, execute just validates and returns:

const generateDocTool = tool({
  description: "Generate structured documentation",
  inputSchema: MyDocSchema,           // Zod schema IS the output shape
  execute: async (data) => data,       // Validate and return
});

Keep categorical output strict with z.enum(...). Reject or retry invalid values; normalize only documented aliases. Do not silently map an unknown category to the first enum value.

Building a new chatbot

When scaffolding from scratch, read checklist.md for the full setup sequence.

Theming

Theme setup belongs to /nextjs-shadcn: oklch variables in globals.css, never a hardcoded colour. What is specific to a chat surface:

scrollbar is the one everybody forgets, and a chat is mostly scrollbar.

  • The brand has to hold across bubble, buttons, borders and scrollbar — the

the reading width for the text that actually matters.

  • User messages: bg-muted rounded bubble, right-aligned.
  • Assistant messages: full width, no background. A bubble on both sides halves

Message streaming state & feedback visibility

Gate action icons (copy, thumbs up/down, regenerate) and inter-tool shimmers on the chat-level stream status, not tool-part states alone. During a multi-tool response (tool A finishes → tool B starts), all tool parts are briefly in a non-loading state and !toolParts.some(isToolLoading) flips true → icons and shimmers flicker on/off.

More skills from laguagu/claude-code-nextjs-skills

  • Aai-appFull-stack AI application generator with Next.js, AI SDK, and ai-elements. Use when creating chatbots, agent dashboards, or custom AI applications.
  • Aai-elementsBuild AI chat interfaces with pre-built shadcn-style components (Message, Conversation, PromptInput, Reasoning, Sources, Tool, Artifact, CodeBlock, Suggestion, Task, Image, ChainOfThought, InlineCitation, WebPreview, Checkpoint, Plan, Queue, ModelSelector, and more). Use when adding AI chat UI to a Next.js + AI SDK app, installing AI Elements components via the CLI (`bun x ai-elements@latest add message` or `npx shadcn@latest add @ai-elements/message`), composing message displays with markdown, building prompt inputs with attachments, or rendering streaming reasoning and tool output.
  • Aai-sdkAnswer questions about the AI SDK and help build AI-powered features. Use when developers ask about Vercel AI SDK, generateText, streamText, ToolLoopAgent, useChat, providers, tools, structured output, embeddings, streaming, or adding AI to an app. First identify the installed major version and route version-specific work: use ai-sdk-7 for AI SDK 7 features/migrations such as WorkflowAgent, HarnessAgent, reasoning, runtime/tools context, toolApproval, telemetry, realtime, or v6-to-v7 upgrades; use ai-sdk-6 for v6 code.
  • Aai-sdk-6Vercel AI SDK v6 development, for projects already on ai@6. Use when building or maintaining AI agents, chatbots, tool integrations, streaming apps, or structured output in a v6 codebase. New projects and ai@7 code use ai-sdk-7; an unknown version goes through ai-sdk. Covers ToolLoopAgent, useChat, generateText, streamText, tool approval, smoothStream, provider tools, MCP integration, and Output patterns.
  • Aai-sdk-7Vercel AI SDK v7 development and migration. Use when building or upgrading AI SDK 7 apps, especially ToolLoopAgent, WorkflowAgent, HarnessAgent, Claude Code/Codex/Pi harnesses, runtimeContext, toolsContext, toolApproval, telemetry, reasoning, file or skill uploads, realtime, video generation, or v6-to-v7 breaking changes. For AI SDK v6 code use ai-sdk-6; for version discovery and general doc lookup use ai-sdk.
  • Acache-componentsExpert guidance for Next.js Cache Components and Partial Prerendering (PPR). Use when implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, instant navigation validation, 'use cache: private', pass-through/interleaving patterns, GET Route Handler caching, debugging cache issues, and reviewing Cache Component implementations.
  • Achrome-devtoolsTests in real browsers via Chrome DevTools MCP. Use when building or debugging anything that runs in a browser. Use when you need to inspect the DOM, capture console errors, analyze network requests, profile performance (LCP/CLS/INP), or verify visual output with real runtime data. Complements Playwright — use this for live debugging and performance work, Playwright for stable E2E test suites.
  • Afrontend-designGuidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.
  • AgoOpens the running app in a browser and verifies that recent UI changes actually work. Use for any quick smoke test of recent work — "go", "test in browser", "check in browser", "make sure it works", "verify it works", "did it work", "works on mobile" — including when the user appends "...and make sure it works" to a UI request. For design critique, use go-ui or web-design-guidelines.
  • AhandoffWrite or update a HANDOFF.md so a fresh agent can continue this work. Use when the user says "handoff", "compact this", "context is full", or "/clear and continue".
  • Chetzner-cloudManage Hetzner Cloud infrastructure with the `hcloud` CLI — servers, networks, firewalls, load balancers, volumes, DNS zones, SSH keys, primary/floating IPs, snapshots, certificates, placement groups, storage boxes. Use whenever the user mentions Hetzner, hcloud, VPS provisioning, or Hetzner location codes (fsn1, hel1, nbg1, ash, hil, sin) — even if they don't say "hcloud". CLI-only; does NOT cover Hetzner Robot (dedicated servers, separate product and API).
  • AiconsFind, fetch, and install the right icon or logo from the right source — brand marks, country flags, file-type icons (PDF, DOCX, ZIP), and UI glyphs — and keep them visually consistent with the app. Use when the project's icon library has no match, when svgl comes up empty, or when the user asks for a flag, a file-type badge, a brand logo, or just "an icon for X". Covers the Iconify search API (200k+ icons across flags, file types, logos and UI sets), the svgl shadcn registry for full-colour brand logos, family and stroke-weight matching so a borrowed icon does not look pasted in, and fallback sources when neither Iconify nor svgl has the mark. Triggers on "add an icon", "country flag", "flag icon", "PDF icon", "file type icon", "brand logo", "sign in with Google/GitHub", "language switcher", "svgl", "iconify", "find an icon". For overall visual direction rather than sourcing one specific mark, use frontend-design; for installing shadcn components generally, use shadcn.

All agent skills → · MCP servers