Skip to content

Chapter 17 · Integrating React and Next.js ​

Chapter goals: Connect a Mastra Agent to a React/Next.js application — expose a streaming API route on the backend, implement a typewriter-effect chat UI on the frontend.

17.1 Architecture Overview ​

text
React Component ──fetch──▶ Next.js API Route ──▶ Mastra Agent
   ▲                        │                  │
   └──── SSE Streaming ◀───┴──────────────────┘

Mastra does not绑定 a specific frontend library — you can use Vercel AI SDK's useChat, CopilotKit, or directly consume the Mastra Client SDK.

17.2 Next.js API Route ​

typescript
// app/api/chat/route.ts (App Router)
import { mastra } from '@/mastra'
import { createUIMessageStreamResponse } from 'ai'

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

  const agent = mastra.getAgent('support')
  // streamVNext outputs a UI-message stream compatible with the AI SDK
  const stream = await agent.streamVNext(messages, {
    format: 'aisdk',
    memory: {
      resource: resourceId,   // User identifier
      thread: threadId,       // Session identifier
    },
  })

  return createUIMessageStreamResponse({ stream: stream.toUIMessageStream() })
}

17.3 Frontend useChat Integration ​

tsx
// app/page.tsx
'use client'
import { useChat } from '@ai-sdk/react'

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit, status } = useChat({
    api: '/api/chat',
    // Carry session info so the server-side memory works
    body: { resourceId: 'user-123', threadId: 'thread-a' },
  })

  return (
    <div className="chat">
      {messages.map((m) => (
        <div key={m.id} className={m.role}>
          {/* Streaming: content arrives incrementally — natural typewriter effect */}
          {m.parts.map((p, i) =>
            p.type === 'text' ? <span key={i}>{p.text}</span> : null,
          )}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} disabled={status !== 'ready'} />
        <button type="submit" disabled={status !== 'ready'}>Send</button>
      </form>
    </div>
  )
}

17.4 Tool Call Visualization ​

The AI SDK message parts contain tool events, enabling intermediate states like "querying order…" to be rendered:

tsx
{m.parts.map((p, i) => {
  if (p.type === 'tool-queryOrder') {
    return p.state === 'output-available'
      ? <Tag key={i}>✅ Order found</Tag>
      : <Tag key={i}>⏳ Querying order…</Tag>
  }
  if (p.type === 'text') return <span key={i}>{p.text}</span>
  return null
})}

17.5 Other Integration Paths ​

  • Mastra Client SDK — @mastra/client-js calls the deployed Mastra server directly from the browser, no自建 route needed;
  • CopilotKit — drop-in React components with built-in streaming and tool visualization;
  • Custom SSE consumer — manually consume the ReadableStream and render incrementally.

17.6 Chapter Summary ​

  • Backend: Next.js API route streams the agent via streamVNext with AI SDK format;
  • Frontend: useChat handles streaming, memory, and UI rendering自动;
  • Tool calls are visible in message parts for intermediate state visualization;
  • Multiple integration paths: AI SDK, Mastra Client SDK, CopilotKit, or custom SSE.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. What does streamVNext with format: "aisdk" produce?

2. How does the frontend know which session/thread to continue?

3. What enables the "typewriter effect" in the chat UI?

4. When should you use the Mastra Client SDK instead of a custom API route?

🛠️ Hands-On Exercises ​

  1. Build a Next.js app with a Mastra agent; connect it via useChat and verify streaming works.
  2. Add tool-call visualization: show "⏳ querying…" while a tool is executing, "✅ done" when the result is available.
  3. Switch to the Mastra Client SDK and call the same agent from a browser component without a custom API route.

Next: Chapter 18 · Standalone Deployment and Server-Side