Skip to content
4th April 2025: This is a preview, whilst production-ready, it means some APIs might change

React Server Function Streams

This pattern is useful for sending partial responses to the client, such as when you’re waiting for data from an external API, like an AI model.

Example

First create a server function that returns a stream. In this example we’re using Cloudflare’s AI, and we’re streaming the response back to the client.

app/pages/Chat/functions.ts
"use server";
export async function sendMessage(prompt: string) {
console.log("Running AI with Prompt:", prompt);
const response = await env.AI.run("@cf/meta/llama-4-scout-17b-16e-instruct", {
prompt,
stream: true,
});
return response as unknown as ReadableStream;
}

Now on the client component, we can use the consumeEventStream function to parse the chunks whilst keeping the UI updated.

app/pages/Chat/Chat.tsx
"use client";
import { sendMessage } from "./functions";
import { useState } from "react";
import { consumeEventStream } from "rwsdk/client";
export function Chat() {
const [message, setMessage] = useState("");
const [reply, setReply] = useState("");
const [isLoading, setIsLoading] = useState(false);
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setReply("");
(await sendMessage(message)).pipeTo(
consumeEventStream({
onChunk: (event) => {
setReply((prev) => {
if (event.data === "[DONE]") {
setIsLoading(false);
return prev;
}
return (prev += JSON.parse(event.data).response);
});
},
})
);
};
return (
<div>
<div>{reply}</div>
<form onSubmit={onSubmit}>
<input
type="text"
value={message}
placeholder="Type a message..."
onChange={(e) => setMessage(e.target.value)}
/>
<button type="submit" disabled={message.length === 0 || isLoading}>
{isLoading ? "Sending..." : "Send"}
</button>
</form>
</div>
);
}

For a working example, please see the Chat example.