Skip to main content

Streaming React Components

AI SDK RSC enables you to stream React components from server actions to the client as AI models generate content. This allows you to build responsive, progressively-rendered interfaces.

Core Primitives

createStreamableUI

createStreamableUI creates a streamable UI component that can be updated over time.
'use server';

import { createStreamableUI } from '@ai-sdk/rsc';

export async function generateComponent() {
  const stream = createStreamableUI(<div>Loading...</div>);

  (async () => {
    stream.update(<div>Processing...</div>);
    await delay(1000);
    stream.update(<div>Almost done...</div>);
    await delay(1000);
    stream.done(<div>Complete!</div>);
  })();

  return stream.value;
}
Key Methods:
  • update(node): Replace the current UI node
  • append(node): Append a new UI node
  • done(node?): Finalize the stream (required)
  • error(error): Signal an error
You must always call .done() to finalize the stream, otherwise the UI will remain in a loading state.

createStreamableValue

createStreamableValue creates a streamable primitive value (strings, numbers, objects).
'use server';

import { createStreamableValue } from '@ai-sdk/rsc';

export async function generateCounter() {
  const stream = createStreamableValue(0);

  (async () => {
    for (let i = 1; i <= 10; i++) {
      await delay(100);
      stream.update(i);
    }
    stream.done();
  })();

  return stream.value;
}
Reading on the Client:
'use client';

import { useStreamableValue } from '@ai-sdk/rsc';

export default function Counter({ streamableValue }) {
  const [value, error] = useStreamableValue(streamableValue);

  if (error) return <div>Error: {error.message}</div>;
  return <div>Count: {value}</div>;
}

Streaming with streamUI

The streamUI function integrates streaming components with language models.

Text Streaming

Stream text content as React components:
'use server';

import { streamUI } from '@ai-sdk/rsc';
import { openai } from '@ai-sdk/openai';

export async function generateResponse(prompt: string) {
  const result = await streamUI({
    model: openai('gpt-4'),
    prompt,
    text: ({ content, done }) => {
      return (
        <div>
          <p>{content}</p>
          {!done && <span className="cursor"></span>}
        </div>
      );
    },
  });

  return result.value;
}

Progressive Tool Rendering

Render components as tools are called:
'use server';

import { streamUI } from '@ai-sdk/rsc';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

export async function searchAndDisplay(query: string) {
  const result = await streamUI({
    model: openai('gpt-4'),
    prompt: query,
    text: ({ content }) => <div>{content}</div>,
    tools: {
      search: {
        description: 'Search for information',
        inputSchema: z.object({
          query: z.string(),
        }),
        generate: async function* ({ query }) {
          yield <div>Searching for {query}...</div>;
          
          const results = await performSearch(query);
          
          yield (
            <div>
              <h3>Results for {query}</h3>
              <ul>
                {results.map((r, i) => (
                  <li key={i}>{r.title}</li>
                ))}
              </ul>
            </div>
          );
          
          return <div>Search complete</div>;
        },
      },
    },
  });

  return result.value;
}

Generator Functions

Use async generators to stream multiple updates:
generate: async function* ({ location }) {
  // Initial loading state
  yield <Skeleton />;
  
  // Fetch data
  const data = await fetchWeather(location);
  
  // Intermediate state
  yield <PartialWeather data={data} />;
  
  // Final state
  return <CompleteWeather data={data} />;
}

Reading Streamable Values

Using Hooks (Client Components)

'use client';

import { useStreamableValue } from '@ai-sdk/rsc';

export function StreamDisplay({ stream }) {
  const [value, error] = useStreamableValue(stream);
  
  if (error) return <Error error={error} />;
  return <div>{value}</div>;
}

Without React

import { readStreamableValue } from '@ai-sdk/rsc';

for await (const value of readStreamableValue(stream)) {
  console.log('Current value:', value);
}

Appending vs Updating

update()

Replaces the current UI:
const stream = createStreamableUI(<div>1</div>);
stream.update(<div>2</div>); // Shows: 2
stream.done();

append()

Adds to existing UI:
const stream = createStreamableUI(<div>1</div>);
stream.append(<div>2</div>); // Shows: 1 2
stream.append(<div>3</div>); // Shows: 1 2 3
stream.done();

Error Handling

Handle errors in streaming components:
const stream = createStreamableUI();

try {
  const result = await riskyOperation();
  stream.done(<Success data={result} />);
} catch (error) {
  stream.error(error);
}
On the client:
'use client';

import { ErrorBoundary } from 'react-error-boundary';

export function SafeStream({ children }) {
  return (
    <ErrorBoundary fallback={<div>Something went wrong</div>}>
      {children}
    </ErrorBoundary>
  );
}

Best Practices

1. Always Call done()

// ❌ Bad - stream never closes
const stream = createStreamableUI();
stream.update(<div>Content</div>);
return stream.value;

// ✅ Good - stream properly closed
const stream = createStreamableUI();
stream.update(<div>Content</div>);
stream.done();
return stream.value;

2. Use Async Patterns

// ✅ Good - non-blocking
const stream = createStreamableUI(<Loading />);

(async () => {
  const data = await fetchData();
  stream.done(<Result data={data} />);
})();

return stream.value;

3. Handle Errors Gracefully

const stream = createStreamableUI(<Loading />);

(async () => {
  try {
    const data = await fetchData();
    stream.done(<Result data={data} />);
  } catch (error) {
    stream.error(error);
  }
})();

return stream.value;

Common Patterns

Progressive Enhancement

export async function generateArticle(topic: string) {
  const stream = createStreamableUI();

  (async () => {
    // 1. Show outline
    stream.update(<Outline topic={topic} />);
    
    // 2. Generate content
    const content = await generateContent(topic);
    stream.update(<Article content={content} loading />);
    
    // 3. Add images
    const images = await generateImages(topic);
    stream.done(<Article content={content} images={images} />);
  })();

  return stream.value;
}

Multi-Step Workflows

export async function analyzeData(data: any) {
  const stream = createStreamableUI();

  (async () => {
    stream.append(<Step>Validating data...</Step>);
    await validate(data);
    
    stream.append(<Step>Processing...</Step>);
    const results = await process(data);
    
    stream.append(<Step>Generating visualization...</Step>);
    const chart = await generateChart(results);
    
    stream.done(<Chart data={chart} />);
  })();

  return stream.value;
}

Next Steps