This guide will walk you through building a generative UI chat box similar to the one used by ChatGPT. To achieve the smooth, real-time typing effect, we'll be using the powerful ai package developed by Vercel.

Understanding How It Works

Feel free to skip this section if you'd rather jump straight into the code.

When you send a prompt to ChatGPT, the response doesn’t arrive all at once. Instead, it appears gradually like the AI is typing in real-time. This effect is made possible through streaming.

Rather than waiting for the entire message to be generated, the server sends the response in small chunks. Meanwhile, the frontend listens for these chunks and updates the UI live, creating that smooth, conversational flow. Under the hood this is possible because the server is sending a ReadableStream.

Vercel’s ai package handles this complexity for you, making it easy to implement a real-time chat experience.

Pre-requisites

Before we dive in, here are a few things you should be familiar with:

  • This guide assumes you have some prior experience with React or Next.js. I’ll focus only on the core logic needed to create a generative UI, so you’re expected to handle the integration and overall app structure on your own.
  • You should have a basic frontend setup ready, preferably using Next.js or React.js.
  • Make sure you have the ai package installed. You can do that by running:

npm install ai

Backend

Let’s start by creating the backend API route that will handle incoming chat messages and stream AI-generated responses back to the frontend.

1. Import Required Dependencies

Import the AI model you want to use and the streamText utility from the ai package:

import { google } from '@ai-sdk/google'; // You can switch this to any other supported model
import { streamText } from 'ai';

2. Extract Input Data from the Request

Extract the latest user message and the chat history from the request body:

const {
  body: { userInput, messages },
} = req;

3. Stream Response from the Model

Pass the input and chat history to the model using streamText. This returns a ReadableStream, which we send back to the client:

const result = await streamText({
  model: google('gemini-2.0-flash-001'),
  messages: [...messages, { role: 'user', content: userInput }],
});

return result.toTextStreamResponse();

🧠 toTextStreamResponse() automatically handles the response headers and streams the content to the frontend.

Frontend

Now that our backend endpoint is ready, let’s build the frontend to send user messages and stream AI responses in real-time.

1. Create State Variables

We'll need two state variables, one for storing the chat history and another for tracking user input:

const [messages, setMessages] = useState([]);
const [userInput, setUserInput] = useState('');

2. Create handleSendMessage Function

This function will send the user’s input and chat history to the backend, read the streamed response, and update the UI accordingly:

const handleSendMessage = async (userInput) => {
  const response = await axios.post('/api/conversation', {
    userInput,
    messages,
  });

  const reader = response.body?.getReader();
  const decoder = new TextDecoder();
  let result = '';
  const newMessageId = generateId();

  // Add an empty assistant message placeholder
  setMessages((currentMessages) => [
    ...currentMessages,
    {
      id: newMessageId,
      role: 'assistant',
      display: '',
      timestamp: dayjs().format('DD/MM hh:mm A'),
    },
  ]);

  if (reader) {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      result += decoder.decode(value, { stream: true });

      // Update the assistant message with streamed content
      setMessages((currentMessages) => {
        const messageIndex = currentMessages.findIndex(
          (message) => message.id === newMessageId
        );
        if (messageIndex === -1) return currentMessages;

        return currentMessages.map((message, index) =>
          index === messageIndex
            ? { ...message, display: result }
            : message
        );
      });
    }
  }
};

Wrapping Up

And there you have it. A fully functional, real-time generative UI powered by streaming and Vercel’s ai package.

You can further enhance this experience by:

  • Adding error handling for failed requests
  • Supporting different model providers like OpenAI or Anthropic
  • Including features like loading indicators or typing animations
  • Saving chat history to localStorage or a database

If you found this helpful or built something cool using this guide, I’d love to see it! Feel free to share your project or improvements in the comments. 🚀