Capítulo 8 de 24
Identical app to Chapter 7 (same router config, same posts/$postId route with a deferred commentsPromise) but the server renders using renderRouterToStream instead of renderRouterToString. This lets the initial HTML (post title/body) flush to the browser immediately while slow loader data (comments, delayed 2s) streams in afterward.
@tanstack/react-router, @tanstack/router-plugin, express, compression, get-port (same dependency set as the non-streaming example)server.js, src/router.tsx, src/entry-server.tsx, src/routes/posts/$postId.tsx. The only functional diff is inside entry-server.tsx.// src/entry-server.tsx (streaming variant)
import { pipeline } from 'node:stream/promises'
import {
RouterServer,
createRequestHandler,
renderRouterToStream,
} from '@tanstack/react-router/ssr/server'
import { createRouter } from './router'
export async function render({ req, res, head }) {
const url = new URL(req.originalUrl || req.url, 'http://localhost:3000').href
const request = new Request(url, { method: req.method, headers: new Headers(/* ... */) })
const handler = createRequestHandler({
request,
createRouter: () => {
const router = createRouter()
router.update({ context: { ...router.options.context, head } })
return router
},
})
// Note: renderRouterToStream also receives `request`, not just responseHeaders/router
const response = await handler(({ request, responseHeaders, router }) =>
renderRouterToStream({
request,
responseHeaders,
router,
children: <RouterServer router={router} />,
}),
)
res.statusMessage = response.statusText
res.status(response.status)
response.headers.forEach((value, name) => res.setHeader(name, value))
return pipeline(response.body, res)
}
renderRouterToString for renderRouterToStream (which additionally needs the request object) turns the SSR response into a stream, so wrapInSuspense: true route options and <Await> boundaries in the route component resolve progressively on the wire instead of blocking the first byte.renderRouterToString -> renderRouterToStream), everything else (router factory, Express glue, route definitions) is unchanged.renderRouterToStream requires the Fetch request object as an argument; renderRouterToString does not, because streaming needs to track the request lifecycle to know when to close the stream.post) and a deferred one (commentsPromise): the shell renders fast, the deferred piece arrives later without a second round-trip.entry-server.tsx side by side to see the minimal diff.defer() + <Await>), useful for understanding what wrapInSuspense/Await do independent of SSR.