본문으로 건너뛰기

서버 함수에서 데이터 스트리밍하기

AI 앱의 부상 덕분에 서버에서 데이터를 스트리밍하는 방식이 큰 인기를 얻었습니다. 다행히 TanStack Start에서는 매우 쉽게 구현할 수 있으며, 더욱 좋은 점은 스트리밍되는 데이터에 타입이 지정된다는 것입니다!

서버 함수에서 데이터를 스트리밍하는 가장 널리 사용되는 두 가지 방법은 ReadableStream 또는 비동기 제너레이터를 사용하는 것입니다.

두 가지 방법의 구현 방식은 서버 함수에서 데이터 스트리밍하기 예제에서 확인할 수 있습니다.

타입이 지정된 읽기 가능 스트림

다음은 메시지 배열을 타입 안전한 방식으로 클라이언트에 스트리밍하는 서버 함수의 예시입니다:

type Message = {
content: string
}

/**
This server function returns a `ReadableStream`
that streams `Message` chunks to the client.
*/
const streamingResponseFn = createServerFn().handler(async () => {
// These are the messages that you want to send as chunks to the client
const messages: Message[] = generateMessages()

// This `ReadableStream` is typed, so each
// will be of type `Message`.
const stream = new ReadableStream<Message>({
async start(controller) {
for (const message of messages) {
// Send the message
controller.enqueue(message)
}
controller.close()
},
})

return stream
})

클라이언트에서 이 스트림을 사용하면 스트리밍되는 청크에 올바른 타입이 지정됩니다:

const [message, setMessage] = useState('')

const getTypedReadableStreamResponse = useCallback(async () => {
const response = await streamingResponseFn()

if (!response) {
return
}

const reader = response.getReader()
let done = false
while (!done) {
const { value, done: doneReading } = await reader.read()
done = doneReading
if (value) {
// Notice how we know the value of `chunk` (`Message | undefined`)
// here, because it's coming from the typed `ReadableStream`
const chunk = value.content
setMessage((prev) => prev + chunk)
}
}
}, [])

서버 함수의 비동기 제너레이터

동일한 결과를 얻는 훨씬 더 깔끔한 방법은 비동기 제너레이터 함수를 사용하는 것입니다:

const streamingWithAnAsyncGeneratorFn = createServerFn().handler(
async function* () {
const messages: Message[] = generateMessages()
for (const msg of messages) {
await sleep(500)
// The streamed chunks are still typed as `Message`
yield msg
}
},
)

클라이언트 측 코드도 더 간결해집니다:

const getResponseFromTheAsyncGenerator = useCallback(async () => {
for await (const msg of await streamingWithAnAsyncGeneratorFn()) {
const chunk = msg.content
setMessages((prev) => prev + chunk)
}
}, [])