8 min read

React Server Components and AI: The Full-Stack Pattern You're Missing

Why AI logic belongs on the server, how React Server Components create a natural boundary for LLM calls, and patterns for streaming AI responses to interactive client UIs.

ReactServer ComponentsAIFull-Stack

I remember the first time I tried to build an AI-powered feature directly in a React client component. It felt fast, easy, and immediate. Then I realized I had just exposed my OpenAI API key to the entire internet. That was the moment I stopped treating AI as just another client-side fetch request and started rethinking my entire architecture.

Why AI Belongs on the Server

Security is the obvious reason to move AI logic to the server. When you run LLM calls in the browser, your API keys are visible to anyone who knows how to open the developer tools. That's a non-starter for any production application.

Beyond security, there's the issue of cost and control. If you run AI logic on the client, you have no way to rate-limit requests effectively. A malicious user could spam your endpoint and run up a massive bill in minutes. By moving this to the server, you can implement robust rate limiting, authentication, and logging.

Latency is another factor. LLM calls often require multiple steps, like fetching context from a database, formatting a prompt, and then calling the model. Doing this on the client means multiple round trips. Doing it on the server means you can perform these operations locally, reducing the time to first token significantly.

The Server Component Pattern for AI

React Server Components (RSC) provide a clean way to separate AI logic from UI. I treat the server component as the orchestrator. It fetches the necessary data, constructs the prompt, and calls the LLM. The client component only handles the user interaction and displays the result.

'use server'

export async function generateAIResponse(prompt: string) {
  const apiKey = process.env.OPENAI_API_KEY
  // Perform database lookups or other server-side tasks here
  const context = await getDatabaseContext(prompt)
  const response = await callLLM(prompt, context)
  return response
}

The server component then calls this function directly.

export default async function AIComponent({ prompt }: { prompt: string }) {
  const response = await generateAIResponse(prompt)
  return <div>{response}</div>
}

This separation ensures that sensitive logic and keys never leave the server.

Streaming AI Responses from Server Components

One of the most powerful features of React 19 is its streaming capability. Instead of waiting for the entire LLM response to finish, you can stream the output to the client as it's generated. This makes the application feel much faster.

// Using React 19 streaming
export async function AIStreamingComponent({ prompt }: { prompt: string }) {
  const stream = await generateAIStream(prompt)
  return <Suspense fallback={<Loading />}>{stream}</Suspense>
}

This pattern provides a much better user experience. The user sees the response appearing in real-time, which is essential for AI chat interfaces.

Keeping the Client Interactive

While the server handles the heavy lifting, the client still needs to be interactive. I use TanStack Router and TanStack Store to manage client-side state. The server component handles the initial render and the AI response, but the client component manages the input field, loading states, and other UI interactions.

'use client'

export function ChatInput() {
  const [input, setInput] = useState('')
  const [isPending, startTransition] = useTransition()

  const handleSubmit = () => {
    startTransition(async () => {
      // Trigger server action
    })
  }

  return (
    <div>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button onClick={handleSubmit} disabled={isPending}>Send</button>
    </div>
  )
}

This boundary is clear. The server handles the AI, and the client handles the user.

When NOT to Use Server Components for AI

Server components aren't a silver bullet. If you're building a real-time chat application that requires low-latency, bidirectional communication, you might still need WebSockets or Server-Sent Events (SSE) directly from the client. If your AI tools need to interact with client-side APIs, like the browser's microphone or camera, you'll need to handle that on the client.

"The goal isn't to move everything to the server. The goal is to move the right things to the server."

Takeaways

  • Move AI logic to the server to protect API keys and control costs.
  • Use Server Components to orchestrate AI calls and keep sensitive logic off the client.
  • Stream AI responses to improve perceived performance.
  • Keep the client interactive by managing UI state with client components.
  • Recognize when client-side AI is necessary, such as for real-time browser interactions.
Ask about Kyle
AI-powered resume assistant

Ask me about Kyle's skills, experience, or projects