@tanstack/router-core
Version:
Modern and scalable routing for React applications
98 lines (84 loc) • 2.71 kB
text/typescript
import { createMemoryHistory } from '@tanstack/history'
import { mergeHeaders } from './headers'
import {
attachRouterServerSsrUtils,
getNormalizedURL,
getOrigin,
} from './ssr-server'
import type { HandlerCallback } from './handlerCallback'
import type { AnyHeaders } from './headers'
import type { AnyRouter } from '../router'
import type { Manifest } from '../manifest'
export type RequestHandler<TRouter extends AnyRouter> = (
cb: HandlerCallback<TRouter>,
) => Promise<Response>
export function createRequestHandler<TRouter extends AnyRouter>({
createRouter,
request,
getRouterManifest,
}: {
createRouter: () => TRouter
request: Request
getRouterManifest?: () => Manifest | Promise<Manifest>
}): RequestHandler<TRouter> {
return async (cb) => {
const router = createRouter()
// Track whether the callback will handle cleanup
let cbWillCleanup = false
try {
attachRouterServerSsrUtils({
router,
manifest: await getRouterManifest?.(),
})
// normalizing and sanitizing the pathname here for server, so we always deal with the same format during SSR.
const { url } = getNormalizedURL(request.url, 'http://localhost')
const origin = getOrigin(request)
const href = url.href.replace(url.origin, '')
// Create a history for the router
const history = createMemoryHistory({
initialEntries: [href],
})
// Update the router with the history and context
router.update({
history,
origin: router.options.origin ?? origin,
})
await router.load()
await router.serverSsr?.dehydrate()
const responseHeaders = getRequestHeaders({
router,
})
// Mark that the callback will handle cleanup
cbWillCleanup = true
return cb({
request,
router,
responseHeaders,
})
} finally {
if (!cbWillCleanup) {
// Clean up router SSR state if the callback won't handle it
// (e.g., if an error occurred before the callback was invoked).
// When the callback runs, it handles cleanup (either via transformStreamWithRouter
// for streaming, or directly in renderRouterToString for non-streaming).
router.serverSsr?.cleanup()
}
}
}
}
function getRequestHeaders(opts: { router: AnyRouter }): Headers {
const matchHeaders = opts.router.state.matches.map<AnyHeaders>(
(match) => match.headers,
)
// Handle Redirects
const { redirect } = opts.router.state
if (redirect) {
matchHeaders.push(redirect.headers)
}
return mergeHeaders(
{
'Content-Type': 'text/html; charset=UTF-8',
},
...matchHeaders,
)
}