<html lang="en">
<head></head>
<body>

<form id="mainForm" method="post" action="https://stackblitz.com/run" target="_self">
<input type="hidden" name="project[files][README.md]" value="# TanStack Start - Streaming Data Example

A TanStack Start example demonstrating streaming data from server functions.

- [TanStack Router Docs](https://tanstack.com/router)

## Start a new project based on this example

To start a new project based on this example, run:

```sh
npx gitpick TanStack/router/tree/main/examples/react/start-streaming-data-from-server-functions start-streaming-data-from-server-functions
```

## Getting Started

From your terminal:

```sh
pnpm install
pnpm dev
```

This starts your app in development mode, rebuilding assets on file changes.

## Build

To build the app for production:

```sh
pnpm build
```

## Streaming Data

This example demonstrates how to stream data from server functions to the client, enabling:

- Progressive data loading
- Real-time updates
- Improved perceived performance
">
<input type="hidden" name="project[files][package.json]" value="{&quot;name&quot;:&quot;tanstack-start-streaming-data-from-server-functions&quot;,&quot;private&quot;:true,&quot;sideEffects&quot;:false,&quot;type&quot;:&quot;module&quot;,&quot;scripts&quot;:{&quot;dev&quot;:&quot;vite dev&quot;,&quot;build&quot;:&quot;vite build &amp;&amp; tsc --noEmit&quot;,&quot;preview&quot;:&quot;vite preview&quot;,&quot;start&quot;:&quot;pnpx srvx --prod -s ../client dist/server/server.js&quot;},&quot;dependencies&quot;:{&quot;@tanstack/react-router&quot;:&quot;https://pkg.pr.new/TanStack/router/@tanstack/react-router@e1c34ffdf31d70311e79f524a1c35a5fa644ea03&quot;,&quot;@tanstack/react-router-devtools&quot;:&quot;https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@e1c34ffdf31d70311e79f524a1c35a5fa644ea03&quot;,&quot;@tanstack/react-start&quot;:&quot;https://pkg.pr.new/TanStack/router/@tanstack/react-start@e1c34ffdf31d70311e79f524a1c35a5fa644ea03&quot;,&quot;react&quot;:&quot;^19.0.0&quot;,&quot;react-dom&quot;:&quot;^19.0.0&quot;,&quot;zod&quot;:&quot;^3.24.2&quot;},&quot;devDependencies&quot;:{&quot;@types/node&quot;:&quot;^22.5.4&quot;,&quot;@types/react&quot;:&quot;^19.0.8&quot;,&quot;@types/react-dom&quot;:&quot;^19.0.3&quot;,&quot;@vitejs/plugin-react&quot;:&quot;^4.3.4&quot;,&quot;typescript&quot;:&quot;^5.7.2&quot;,&quot;vite&quot;:&quot;^7.3.1&quot;,&quot;vite-tsconfig-paths&quot;:&quot;^5.1.4&quot;}}">
<input type="hidden" name="project[files][tsconfig.json]" value="{
  &quot;include&quot;: [&quot;**/*.ts&quot;, &quot;**/*.tsx&quot;, &quot;public/script*.js&quot;],
  &quot;compilerOptions&quot;: {
    &quot;strict&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;,
    &quot;module&quot;: &quot;ESNext&quot;,
    &quot;moduleResolution&quot;: &quot;Bundler&quot;,
    &quot;lib&quot;: [&quot;DOM&quot;, &quot;DOM.Iterable&quot;, &quot;ES2022&quot;],
    &quot;isolatedModules&quot;: true,
    &quot;resolveJsonModule&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;target&quot;: &quot;ES2022&quot;,
    &quot;allowJs&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;baseUrl&quot;: &quot;.&quot;,
    &quot;paths&quot;: {
      &quot;~/*&quot;: [&quot;./src/*&quot;]
    },
    &quot;noEmit&quot;: true
  }
}
">
<input type="hidden" name="project[files][vite.config.ts]" value="import { tanstackStart } from &#39;@tanstack/react-start/plugin/vite&#39;
import { defineConfig } from &#39;vite&#39;
import tsConfigPaths from &#39;vite-tsconfig-paths&#39;
import viteReact from &#39;@vitejs/plugin-react&#39;

export default defineConfig({
  server: {
    port: 3000,
  },
  plugins: [
    tsConfigPaths({
      projects: [&#39;./tsconfig.json&#39;],
    }),
    tanstackStart(),
    viteReact(),
  ],
})
">
<input type="hidden" name="project[files][public/favicon.ico]" value="https://pkg.pr.new/template/92d9921c-8218-4657-a54c-1442f987b570">
<input type="hidden" name="project[files][src/routeTree.gen.ts]" value="/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from &#39;./routes/__root&#39;
import { Route as IndexRouteImport } from &#39;./routes/index&#39;

const IndexRoute = IndexRouteImport.update({
  id: &#39;/&#39;,
  path: &#39;/&#39;,
  getParentRoute: () =&gt; rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
  &#39;/&#39;: typeof IndexRoute
}
export interface FileRoutesByTo {
  &#39;/&#39;: typeof IndexRoute
}
export interface FileRoutesById {
  __root__: typeof rootRouteImport
  &#39;/&#39;: typeof IndexRoute
}
export interface FileRouteTypes {
  fileRoutesByFullPath: FileRoutesByFullPath
  fullPaths: &#39;/&#39;
  fileRoutesByTo: FileRoutesByTo
  to: &#39;/&#39;
  id: &#39;__root__&#39; | &#39;/&#39;
  fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
  IndexRoute: typeof IndexRoute
}

declare module &#39;@tanstack/react-router&#39; {
  interface FileRoutesByPath {
    &#39;/&#39;: {
      id: &#39;/&#39;
      path: &#39;/&#39;
      fullPath: &#39;/&#39;
      preLoaderRoute: typeof IndexRouteImport
      parentRoute: typeof rootRouteImport
    }
  }
}

const rootRouteChildren: RootRouteChildren = {
  IndexRoute: IndexRoute,
}
export const routeTree = rootRouteImport
  ._addFileChildren(rootRouteChildren)
  ._addFileTypes&lt;FileRouteTypes&gt;()

import type { getRouter } from &#39;./router.tsx&#39;
import type { createStart } from &#39;@tanstack/react-start&#39;
declare module &#39;@tanstack/react-start&#39; {
  interface Register {
    ssr: true
    router: Awaited&lt;ReturnType&lt;typeof getRouter&gt;&gt;
  }
}
">
<input type="hidden" name="project[files][src/router.tsx]" value="import { createRouter } from &#39;@tanstack/react-router&#39;
import { routeTree } from &#39;./routeTree.gen&#39;

export function getRouter() {
  const router = createRouter({
    routeTree,
    defaultPreload: &#39;intent&#39;,
    defaultErrorComponent: (err) =&gt; &lt;p&gt;{err.error.stack}&lt;/p&gt;,
    defaultNotFoundComponent: () =&gt; &lt;p&gt;not found&lt;/p&gt;,
    scrollRestoration: true,
  })

  return router
}

declare module &#39;@tanstack/react-router&#39; {
  interface Register {
    router: ReturnType&lt;typeof getRouter&gt;
  }
}
">
<input type="hidden" name="project[files][src/styles/app.css]" value="body {
  font-family:
    Gordita, Roboto, Oxygen, Ubuntu, Cantarell, &#39;Open Sans&#39;, &#39;Helvetica Neue&#39;,
    sans-serif;
}

a {
  margin-right: 1rem;
}

main {
  text-align: center;
  padding: 1em;
  margin: 0 auto;
}

#streamed-results {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

#streamed-results &gt; button {
  margin: auto;
}
">
<input type="hidden" name="project[files][src/routes/__root.tsx]" value="/// &lt;reference types=&quot;vite/client&quot; /&gt;
import * as React from &#39;react&#39;
import { TanStackRouterDevtools } from &#39;@tanstack/react-router-devtools&#39;
import {
  HeadContent,
  Link,
  Outlet,
  Scripts,
  createRootRoute,
} from &#39;@tanstack/react-router&#39;
import appCss from &#39;~/styles/app.css?url&#39;

export const Route = createRootRoute({
  head: () =&gt; ({
    links: [{ rel: &#39;stylesheet&#39;, href: appCss }],
  }),
  component: RootComponent,
})

function RootComponent() {
  return (
    &lt;RootDocument&gt;
      &lt;Outlet /&gt;
    &lt;/RootDocument&gt;
  )
}

function RootDocument({ children }: { children: React.ReactNode }) {
  return (
    &lt;html&gt;
      &lt;head&gt;
        &lt;HeadContent /&gt;
      &lt;/head&gt;
      &lt;body&gt;
        {children}
        &lt;TanStackRouterDevtools position=&quot;bottom-right&quot; /&gt;
        &lt;Scripts /&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  )
}
">
<input type="hidden" name="project[files][src/routes/index.tsx]" value="import { createFileRoute } from &#39;@tanstack/react-router&#39;
import { createServerFn } from &#39;@tanstack/react-start&#39;
import { useCallback, useState } from &#39;react&#39;
import { z } from &#39;zod&#39;

/**
  This schema will be used to define the type
  of each chunk in the `ReadableStream`.
  (It mimics OpenAI&#39;s streaming response format.)
*/
const textPartSchema = z.object({
  choices: z.array(
    z.object({
      delta: z.object({
        content: z.string().optional(),
      }),
      index: z.number(),
      finish_reason: z.string().nullable(),
    }),
  ),
})

export type TextPart = z.infer&lt;typeof textPartSchema&gt;

/**
  This helper function generates the array of messages
  that we&#39;ll stream to the client.
*/
function generateMessages() {
  const messages = Array.from({ length: 10 }, () =&gt;
    Math.floor(Math.random() * 100),
  ).map((n, i) =&gt;
    textPartSchema.parse({
      choices: [
        {
          delta: { content: `Number #${i + 1}: ${n}\n` },
          index: i,
          finish_reason: null,
        },
      ],
    }),
  )
  return messages
}

/**
  This helper function is used to simulate the
  delay between each message being sent.
*/
function sleep(ms: number) {
  return new Promise((resolve) =&gt; setTimeout(resolve, ms))
}

/**
  This server function returns a `ReadableStream`
  that streams `TextPart` chunks to the client.
*/
const streamingResponseFn = createServerFn().handler(async () =&gt; {
  const messages = generateMessages()
  // This `ReadableStream` is typed, so each
  // will be of type `TextPart`.
  const stream = new ReadableStream&lt;TextPart&gt;({
    async start(controller) {
      for (const message of messages) {
        // simulate network latency
        await sleep(500)
        controller.enqueue(message)
      }
      controller.close()
    },
  })

  return stream
})

/**
  You can also use an async generator function to stream
  typed chunks to the client.
*/
const streamingWithAnAsyncGeneratorFn = createServerFn().handler(
  async function* () {
    const messages = generateMessages()
    for (const msg of messages) {
      await sleep(500)
      // The streamed chunks are still typed as `TextPart`
      yield msg
    }
  },
)

export const Route = createFileRoute(&#39;/&#39;)({
  component: RouteComponent,
})

function RouteComponent() {
  const [readableStreamMessages, setReadableStreamMessages] = useState(&#39;&#39;)

  const [asyncGeneratorFuncMessages, setAsyncGeneratorFuncMessages] =
    useState(&#39;&#39;)

  const getTypedReadableStreamResponse = useCallback(async () =&gt; {
    const response = await streamingResponseFn()

    if (!response) {
      return
    }

    const reader = response.getReader()
    let done = false
    setReadableStreamMessages(&#39;&#39;)
    while (!done) {
      const { value, done: doneReading } = await reader.read()
      done = doneReading
      if (value) {
        // Notice how we know the value of `chunk` (`TextPart | undefined`)
        // here, because it&#39;s coming from the typed `ReadableStream`
        const chunk = value?.choices[0].delta.content
        if (chunk) {
          setReadableStreamMessages((prev) =&gt; prev + chunk)
        }
      }
    }
  }, [])

  const getResponseFromTheAsyncGenerator = useCallback(async () =&gt; {
    setAsyncGeneratorFuncMessages(&#39;&#39;)
    for await (const msg of await streamingWithAnAsyncGeneratorFn()) {
      const chunk = msg?.choices[0].delta.content
      if (chunk) {
        setAsyncGeneratorFuncMessages((prev) =&gt; prev + chunk)
      }
    }
  }, [])

  return (
    &lt;main&gt;
      &lt;h1&gt;Typed Readable Stream&lt;/h1&gt;
      &lt;div id=&quot;streamed-results&quot;&gt;
        &lt;button onClick={() =&gt; getTypedReadableStreamResponse()}&gt;
          Get 10 random numbers (ReadableStream)
        &lt;/button&gt;
        &lt;button onClick={() =&gt; getResponseFromTheAsyncGenerator()}&gt;
          Get 10 random numbers (Async Generator Function)
        &lt;/button&gt;
        &lt;pre&gt;{readableStreamMessages}&lt;/pre&gt;
        &lt;pre&gt;{asyncGeneratorFuncMessages}&lt;/pre&gt;
      &lt;/div&gt;
    &lt;/main&gt;
  )
}
">
<input type="hidden" name="project[description]" value="generated by https://pkg.pr.new">
<input type="hidden" name="project[template]" value="node">
<input type="hidden" name="project[title]" value="tanstack-start-streaming-data-from-server-functions">
</form>
<script>document.getElementById("mainForm").submit();</script>

</body></html>