You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@tanstack/start-server-core

Package Overview
Dependencies
Maintainers
4
Versions
391
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tanstack/start-server-core - npm Package Compare versions

Comparing version
1.158.4
to
1.159.0
+115
dist/esm/transformAssetUrls.d.ts
import { Awaitable, Manifest, RouterManagedTag } from '@tanstack/router-core';
export type AssetUrlType = 'modulepreload' | 'stylesheet' | 'clientEntry';
export interface TransformAssetUrlsContext {
url: string;
type: AssetUrlType;
}
export type TransformAssetUrlsFn = (context: TransformAssetUrlsContext) => Awaitable<string>;
export type CreateTransformAssetUrlsContext = {
/** True when the server is computing the cached manifest during startup warmup. */
warmup: true;
} | {
/**
* The current Request.
*
* Only available during request handling (i.e. when `warmup: false`).
*/
request: Request;
/** False when transforming URLs as part of request handling. */
warmup: false;
};
/**
* Async factory that runs once per manifest computation and returns the
* per-asset transform.
*/
export type CreateTransformAssetUrlsFn = (ctx: CreateTransformAssetUrlsContext) => Awaitable<TransformAssetUrlsFn>;
type TransformAssetUrlsOptionsBase = {
/**
* Whether to cache the transformed manifest after the first request.
*
* When `true` (default), the transform runs once on the first request and
* the resulting manifest is reused for all subsequent requests in production.
*
* Set to `false` for per-request transforms (e.g. geo-routing to different
* CDNs based on request headers).
*
* @default true
*/
cache?: boolean;
/**
* When `true`, warms up the cached transformed manifest in the background when
* the server starts (production only).
*
* This can reduce latency for the first request when `cache` is `true`.
* Has no effect when `cache: false` (per-request transforms) or in dev mode.
*
* @default false
*/
warmup?: boolean;
};
export type TransformAssetUrlsOptions = (TransformAssetUrlsOptionsBase & {
/**
* The transform to apply to asset URLs. Can be a string prefix or a callback.
*
* **String** — prepended to every asset URL.
* **Callback** — receives `{ url, type }` and returns a new URL.
*/
transform: string | TransformAssetUrlsFn;
createTransform?: never;
}) | (TransformAssetUrlsOptionsBase & {
/**
* Create a per-asset transform function.
*
* This factory runs once per manifest computation (per request when
* `cache: false`, or once per server when `cache: true`). It can do async
* setup work (fetch config, read from a KV, etc.) and return a fast
* per-asset transformer.
*/
createTransform: CreateTransformAssetUrlsFn;
transform?: never;
});
export type TransformAssetUrls = string | TransformAssetUrlsFn | TransformAssetUrlsOptions;
export type ResolvedTransformAssetUrlsConfig = {
type: 'transform';
transformFn: TransformAssetUrlsFn;
cache: boolean;
} | {
type: 'createTransform';
createTransform: CreateTransformAssetUrlsFn;
cache: boolean;
};
/**
* Resolves a TransformAssetUrls value (string prefix, callback, or options
* object) into a concrete transform function and cache flag.
*/
export declare function resolveTransformConfig(transform: TransformAssetUrls): ResolvedTransformAssetUrlsConfig;
export interface StartManifestWithClientEntry {
manifest: Manifest;
clientEntry: string;
/** Script content prepended before the client entry import (dev only) */
injectedHeadScripts?: string;
}
/**
* Builds the client entry `<script>` tag from a (possibly transformed) client
* entry URL and optional injected head scripts.
*/
export declare function buildClientEntryScriptTag(clientEntry: string, injectedHeadScripts?: string): RouterManagedTag;
/**
* Applies a URL transform to every asset URL in the manifest and returns a
* new manifest with a client entry script tag appended to the root route's
* assets.
*
* The source manifest is deep-cloned so the cached original is never mutated.
*/
export declare function transformManifestUrls(source: StartManifestWithClientEntry, transformFn: TransformAssetUrlsFn, opts?: {
/** When true, clone the source manifest before mutating it. */
clone?: boolean;
}): Promise<Manifest>;
/**
* Builds a final Manifest from a StartManifestWithClientEntry without any
* URL transforms. Used when no transformAssetUrls option is provided.
*
* Returns a new manifest object so the cached base manifest is never mutated.
*/
export declare function buildManifestWithClientEntry(source: StartManifestWithClientEntry): Manifest;
export {};
import { rootRouteId } from "@tanstack/router-core";
function resolveTransformConfig(transform) {
if (typeof transform === "string") {
const prefix = transform;
return {
type: "transform",
transformFn: ({ url }) => `${prefix}${url}`,
cache: true
};
}
if (typeof transform === "function") {
return {
type: "transform",
transformFn: transform,
cache: true
};
}
if ("createTransform" in transform && transform.createTransform) {
return {
type: "createTransform",
createTransform: transform.createTransform,
cache: transform.cache !== false
};
}
const transformFn = typeof transform.transform === "string" ? (({ url }) => `${transform.transform}${url}`) : transform.transform;
return {
type: "transform",
transformFn,
cache: transform.cache !== false
};
}
function buildClientEntryScriptTag(clientEntry, injectedHeadScripts) {
const clientEntryLiteral = JSON.stringify(clientEntry);
let script = `import(${clientEntryLiteral})`;
if (injectedHeadScripts) {
script = `${injectedHeadScripts};${script}`;
}
return {
tag: "script",
attrs: {
type: "module",
async: true
},
children: script
};
}
function transformManifestUrls(source, transformFn, opts) {
return (async () => {
const manifest = opts?.clone ? structuredClone(source.manifest) : source.manifest;
for (const route of Object.values(manifest.routes)) {
if (route.preloads) {
route.preloads = await Promise.all(
route.preloads.map(
(url) => Promise.resolve(transformFn({ url, type: "modulepreload" }))
)
);
}
if (route.assets) {
for (const asset of route.assets) {
if (asset.tag === "link" && asset.attrs?.href) {
asset.attrs.href = await Promise.resolve(
transformFn({
url: asset.attrs.href,
type: "stylesheet"
})
);
}
}
}
}
const transformedClientEntry = await Promise.resolve(
transformFn({
url: source.clientEntry,
type: "clientEntry"
})
);
const rootRoute = manifest.routes[rootRouteId];
if (rootRoute) {
rootRoute.assets = rootRoute.assets || [];
rootRoute.assets.push(
buildClientEntryScriptTag(
transformedClientEntry,
source.injectedHeadScripts
)
);
}
return manifest;
})();
}
function buildManifestWithClientEntry(source) {
const scriptTag = buildClientEntryScriptTag(
source.clientEntry,
source.injectedHeadScripts
);
const baseRootRoute = source.manifest.routes[rootRouteId];
const routes = {
...source.manifest.routes,
...baseRootRoute ? {
[rootRouteId]: {
...baseRootRoute,
assets: [...baseRootRoute.assets || [], scriptTag]
}
} : {}
};
return { routes };
}
export {
buildClientEntryScriptTag,
buildManifestWithClientEntry,
resolveTransformConfig,
transformManifestUrls
};
//# sourceMappingURL=transformAssetUrls.js.map
{"version":3,"file":"transformAssetUrls.js","sources":["../../src/transformAssetUrls.ts"],"sourcesContent":["import { rootRouteId } from '@tanstack/router-core'\n\nimport type {\n Awaitable,\n Manifest,\n RouterManagedTag,\n} from '@tanstack/router-core'\n\nexport type AssetUrlType = 'modulepreload' | 'stylesheet' | 'clientEntry'\n\nexport interface TransformAssetUrlsContext {\n url: string\n type: AssetUrlType\n}\n\nexport type TransformAssetUrlsFn = (\n context: TransformAssetUrlsContext,\n) => Awaitable<string>\n\nexport type CreateTransformAssetUrlsContext =\n | {\n /** True when the server is computing the cached manifest during startup warmup. */\n warmup: true\n }\n | {\n /**\n * The current Request.\n *\n * Only available during request handling (i.e. when `warmup: false`).\n */\n request: Request\n /** False when transforming URLs as part of request handling. */\n warmup: false\n }\n\n/**\n * Async factory that runs once per manifest computation and returns the\n * per-asset transform.\n */\nexport type CreateTransformAssetUrlsFn = (\n ctx: CreateTransformAssetUrlsContext,\n) => Awaitable<TransformAssetUrlsFn>\n\ntype TransformAssetUrlsOptionsBase = {\n /**\n * Whether to cache the transformed manifest after the first request.\n *\n * When `true` (default), the transform runs once on the first request and\n * the resulting manifest is reused for all subsequent requests in production.\n *\n * Set to `false` for per-request transforms (e.g. geo-routing to different\n * CDNs based on request headers).\n *\n * @default true\n */\n cache?: boolean\n\n /**\n * When `true`, warms up the cached transformed manifest in the background when\n * the server starts (production only).\n *\n * This can reduce latency for the first request when `cache` is `true`.\n * Has no effect when `cache: false` (per-request transforms) or in dev mode.\n *\n * @default false\n */\n warmup?: boolean\n}\n\nexport type TransformAssetUrlsOptions =\n | (TransformAssetUrlsOptionsBase & {\n /**\n * The transform to apply to asset URLs. Can be a string prefix or a callback.\n *\n * **String** — prepended to every asset URL.\n * **Callback** — receives `{ url, type }` and returns a new URL.\n */\n transform: string | TransformAssetUrlsFn\n createTransform?: never\n })\n | (TransformAssetUrlsOptionsBase & {\n /**\n * Create a per-asset transform function.\n *\n * This factory runs once per manifest computation (per request when\n * `cache: false`, or once per server when `cache: true`). It can do async\n * setup work (fetch config, read from a KV, etc.) and return a fast\n * per-asset transformer.\n */\n createTransform: CreateTransformAssetUrlsFn\n transform?: never\n })\n\nexport type TransformAssetUrls =\n | string\n | TransformAssetUrlsFn\n | TransformAssetUrlsOptions\n\nexport type ResolvedTransformAssetUrlsConfig =\n | {\n type: 'transform'\n transformFn: TransformAssetUrlsFn\n cache: boolean\n }\n | {\n type: 'createTransform'\n createTransform: CreateTransformAssetUrlsFn\n cache: boolean\n }\n\n/**\n * Resolves a TransformAssetUrls value (string prefix, callback, or options\n * object) into a concrete transform function and cache flag.\n */\nexport function resolveTransformConfig(\n transform: TransformAssetUrls,\n): ResolvedTransformAssetUrlsConfig {\n // String shorthand\n if (typeof transform === 'string') {\n const prefix = transform\n return {\n type: 'transform',\n transformFn: ({ url }) => `${prefix}${url}`,\n cache: true,\n }\n }\n\n // Callback shorthand\n if (typeof transform === 'function') {\n return {\n type: 'transform',\n transformFn: transform,\n cache: true,\n }\n }\n\n // Options object\n if ('createTransform' in transform && transform.createTransform) {\n return {\n type: 'createTransform',\n createTransform: transform.createTransform,\n cache: transform.cache !== false,\n }\n }\n\n const transformFn =\n typeof transform.transform === 'string'\n ? ((({ url }: TransformAssetUrlsContext) =>\n `${transform.transform}${url}`) as TransformAssetUrlsFn)\n : transform.transform\n\n return {\n type: 'transform',\n transformFn,\n cache: transform.cache !== false,\n }\n}\n\nexport interface StartManifestWithClientEntry {\n manifest: Manifest\n clientEntry: string\n /** Script content prepended before the client entry import (dev only) */\n injectedHeadScripts?: string\n}\n\n/**\n * Builds the client entry `<script>` tag from a (possibly transformed) client\n * entry URL and optional injected head scripts.\n */\nexport function buildClientEntryScriptTag(\n clientEntry: string,\n injectedHeadScripts?: string,\n): RouterManagedTag {\n const clientEntryLiteral = JSON.stringify(clientEntry)\n let script = `import(${clientEntryLiteral})`\n if (injectedHeadScripts) {\n script = `${injectedHeadScripts};${script}`\n }\n return {\n tag: 'script',\n attrs: {\n type: 'module',\n async: true,\n },\n children: script,\n }\n}\n\n/**\n * Applies a URL transform to every asset URL in the manifest and returns a\n * new manifest with a client entry script tag appended to the root route's\n * assets.\n *\n * The source manifest is deep-cloned so the cached original is never mutated.\n */\nexport function transformManifestUrls(\n source: StartManifestWithClientEntry,\n transformFn: TransformAssetUrlsFn,\n opts?: {\n /** When true, clone the source manifest before mutating it. */\n clone?: boolean\n },\n): Promise<Manifest> {\n return (async () => {\n const manifest = opts?.clone\n ? structuredClone(source.manifest)\n : source.manifest\n\n for (const route of Object.values(manifest.routes)) {\n // Transform preload URLs (modulepreload)\n if (route.preloads) {\n route.preloads = await Promise.all(\n route.preloads.map((url) =>\n Promise.resolve(transformFn({ url, type: 'modulepreload' })),\n ),\n )\n }\n\n // Transform asset tag URLs\n if (route.assets) {\n for (const asset of route.assets) {\n if (asset.tag === 'link' && asset.attrs?.href) {\n asset.attrs.href = await Promise.resolve(\n transformFn({\n url: asset.attrs.href,\n type: 'stylesheet',\n }),\n )\n }\n }\n }\n }\n\n // Transform and append the client entry script tag\n const transformedClientEntry = await Promise.resolve(\n transformFn({\n url: source.clientEntry,\n type: 'clientEntry',\n }),\n )\n\n const rootRoute = manifest.routes[rootRouteId]\n if (rootRoute) {\n rootRoute.assets = rootRoute.assets || []\n rootRoute.assets.push(\n buildClientEntryScriptTag(\n transformedClientEntry,\n source.injectedHeadScripts,\n ),\n )\n }\n\n return manifest\n })()\n}\n\n/**\n * Builds a final Manifest from a StartManifestWithClientEntry without any\n * URL transforms. Used when no transformAssetUrls option is provided.\n *\n * Returns a new manifest object so the cached base manifest is never mutated.\n */\nexport function buildManifestWithClientEntry(\n source: StartManifestWithClientEntry,\n): Manifest {\n const scriptTag = buildClientEntryScriptTag(\n source.clientEntry,\n source.injectedHeadScripts,\n )\n\n const baseRootRoute = source.manifest.routes[rootRouteId]\n const routes = {\n ...source.manifest.routes,\n ...(baseRootRoute\n ? {\n [rootRouteId]: {\n ...baseRootRoute,\n assets: [...(baseRootRoute.assets || []), scriptTag],\n },\n }\n : {}),\n }\n\n return { routes }\n}\n"],"names":[],"mappings":"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"}
import { rootRouteId } from '@tanstack/router-core'
import type {
Awaitable,
Manifest,
RouterManagedTag,
} from '@tanstack/router-core'
export type AssetUrlType = 'modulepreload' | 'stylesheet' | 'clientEntry'
export interface TransformAssetUrlsContext {
url: string
type: AssetUrlType
}
export type TransformAssetUrlsFn = (
context: TransformAssetUrlsContext,
) => Awaitable<string>
export type CreateTransformAssetUrlsContext =
| {
/** True when the server is computing the cached manifest during startup warmup. */
warmup: true
}
| {
/**
* The current Request.
*
* Only available during request handling (i.e. when `warmup: false`).
*/
request: Request
/** False when transforming URLs as part of request handling. */
warmup: false
}
/**
* Async factory that runs once per manifest computation and returns the
* per-asset transform.
*/
export type CreateTransformAssetUrlsFn = (
ctx: CreateTransformAssetUrlsContext,
) => Awaitable<TransformAssetUrlsFn>
type TransformAssetUrlsOptionsBase = {
/**
* Whether to cache the transformed manifest after the first request.
*
* When `true` (default), the transform runs once on the first request and
* the resulting manifest is reused for all subsequent requests in production.
*
* Set to `false` for per-request transforms (e.g. geo-routing to different
* CDNs based on request headers).
*
* @default true
*/
cache?: boolean
/**
* When `true`, warms up the cached transformed manifest in the background when
* the server starts (production only).
*
* This can reduce latency for the first request when `cache` is `true`.
* Has no effect when `cache: false` (per-request transforms) or in dev mode.
*
* @default false
*/
warmup?: boolean
}
export type TransformAssetUrlsOptions =
| (TransformAssetUrlsOptionsBase & {
/**
* The transform to apply to asset URLs. Can be a string prefix or a callback.
*
* **String** — prepended to every asset URL.
* **Callback** — receives `{ url, type }` and returns a new URL.
*/
transform: string | TransformAssetUrlsFn
createTransform?: never
})
| (TransformAssetUrlsOptionsBase & {
/**
* Create a per-asset transform function.
*
* This factory runs once per manifest computation (per request when
* `cache: false`, or once per server when `cache: true`). It can do async
* setup work (fetch config, read from a KV, etc.) and return a fast
* per-asset transformer.
*/
createTransform: CreateTransformAssetUrlsFn
transform?: never
})
export type TransformAssetUrls =
| string
| TransformAssetUrlsFn
| TransformAssetUrlsOptions
export type ResolvedTransformAssetUrlsConfig =
| {
type: 'transform'
transformFn: TransformAssetUrlsFn
cache: boolean
}
| {
type: 'createTransform'
createTransform: CreateTransformAssetUrlsFn
cache: boolean
}
/**
* Resolves a TransformAssetUrls value (string prefix, callback, or options
* object) into a concrete transform function and cache flag.
*/
export function resolveTransformConfig(
transform: TransformAssetUrls,
): ResolvedTransformAssetUrlsConfig {
// String shorthand
if (typeof transform === 'string') {
const prefix = transform
return {
type: 'transform',
transformFn: ({ url }) => `${prefix}${url}`,
cache: true,
}
}
// Callback shorthand
if (typeof transform === 'function') {
return {
type: 'transform',
transformFn: transform,
cache: true,
}
}
// Options object
if ('createTransform' in transform && transform.createTransform) {
return {
type: 'createTransform',
createTransform: transform.createTransform,
cache: transform.cache !== false,
}
}
const transformFn =
typeof transform.transform === 'string'
? ((({ url }: TransformAssetUrlsContext) =>
`${transform.transform}${url}`) as TransformAssetUrlsFn)
: transform.transform
return {
type: 'transform',
transformFn,
cache: transform.cache !== false,
}
}
export interface StartManifestWithClientEntry {
manifest: Manifest
clientEntry: string
/** Script content prepended before the client entry import (dev only) */
injectedHeadScripts?: string
}
/**
* Builds the client entry `<script>` tag from a (possibly transformed) client
* entry URL and optional injected head scripts.
*/
export function buildClientEntryScriptTag(
clientEntry: string,
injectedHeadScripts?: string,
): RouterManagedTag {
const clientEntryLiteral = JSON.stringify(clientEntry)
let script = `import(${clientEntryLiteral})`
if (injectedHeadScripts) {
script = `${injectedHeadScripts};${script}`
}
return {
tag: 'script',
attrs: {
type: 'module',
async: true,
},
children: script,
}
}
/**
* Applies a URL transform to every asset URL in the manifest and returns a
* new manifest with a client entry script tag appended to the root route's
* assets.
*
* The source manifest is deep-cloned so the cached original is never mutated.
*/
export function transformManifestUrls(
source: StartManifestWithClientEntry,
transformFn: TransformAssetUrlsFn,
opts?: {
/** When true, clone the source manifest before mutating it. */
clone?: boolean
},
): Promise<Manifest> {
return (async () => {
const manifest = opts?.clone
? structuredClone(source.manifest)
: source.manifest
for (const route of Object.values(manifest.routes)) {
// Transform preload URLs (modulepreload)
if (route.preloads) {
route.preloads = await Promise.all(
route.preloads.map((url) =>
Promise.resolve(transformFn({ url, type: 'modulepreload' })),
),
)
}
// Transform asset tag URLs
if (route.assets) {
for (const asset of route.assets) {
if (asset.tag === 'link' && asset.attrs?.href) {
asset.attrs.href = await Promise.resolve(
transformFn({
url: asset.attrs.href,
type: 'stylesheet',
}),
)
}
}
}
}
// Transform and append the client entry script tag
const transformedClientEntry = await Promise.resolve(
transformFn({
url: source.clientEntry,
type: 'clientEntry',
}),
)
const rootRoute = manifest.routes[rootRouteId]
if (rootRoute) {
rootRoute.assets = rootRoute.assets || []
rootRoute.assets.push(
buildClientEntryScriptTag(
transformedClientEntry,
source.injectedHeadScripts,
),
)
}
return manifest
})()
}
/**
* Builds a final Manifest from a StartManifestWithClientEntry without any
* URL transforms. Used when no transformAssetUrls option is provided.
*
* Returns a new manifest object so the cached base manifest is never mutated.
*/
export function buildManifestWithClientEntry(
source: StartManifestWithClientEntry,
): Manifest {
const scriptTag = buildClientEntryScriptTag(
source.clientEntry,
source.injectedHeadScripts,
)
const baseRootRoute = source.manifest.routes[rootRouteId]
const routes = {
...source.manifest.routes,
...(baseRootRoute
? {
[rootRouteId]: {
...baseRootRoute,
assets: [...(baseRootRoute.assets || []), scriptTag],
},
}
: {}),
}
return { routes }
}
+86
-1
import { RequestHandler } from './request-handler.js';
import { AnyRouter, Register } from '@tanstack/router-core';
import { HandlerCallback } from '@tanstack/router-core/ssr/server';
export declare function createStartHandler<TRegister = Register>(cb: HandlerCallback<AnyRouter>): RequestHandler<TRegister>;
import { TransformAssetUrls } from './transformAssetUrls.js';
export interface CreateStartHandlerOptions {
handler: HandlerCallback<AnyRouter>;
/**
* Transform asset URLs at runtime, e.g. to prepend a CDN prefix.
*
* **String** — a URL prefix prepended to every asset URL (cached by default):
* ```ts
* createStartHandler({
* handler: defaultStreamHandler,
* transformAssetUrls: 'https://cdn.example.com',
* })
* ```
*
* **Callback** — receives `{ url, type }` and returns a new URL
* (cached by default — runs once on first request):
* ```ts
* createStartHandler({
* handler: defaultStreamHandler,
* transformAssetUrls: ({ url, type }) => {
* return `https://cdn.example.com${url}`
* },
* })
* ```
*
* **Object** — for explicit cache control:
* ```ts
* createStartHandler({
* handler: defaultStreamHandler,
* transformAssetUrls: {
* transform: ({ url }) => {
* const region = getRequest().headers.get('x-region') || 'us'
* return `https://cdn-${region}.example.com${url}`
* },
* cache: false, // transform per-request
* },
* })
* ```
*
* `type` is one of `'modulepreload' | 'stylesheet' | 'clientEntry'`.
*
* By default, the transformed manifest is cached after the first request
* (`cache: true`). Set `cache: false` for per-request transforms.
*
* If you're using a cached transform, you can optionally set `warmup: true`
* (object form only) to compute the transformed manifest in the background at
* server startup.
*
* Note: This only transforms URLs managed by TanStack Start's manifest
* (JS preloads, CSS links, and the client entry script). For asset imports
* used directly in components (e.g. `import logo from './logo.svg'`),
* configure Vite's `experimental.renderBuiltUrl` in your vite.config.ts.
*/
transformAssetUrls?: TransformAssetUrls;
}
/**
* Creates the TanStack Start request handler.
*
* @example Backwards-compatible usage (handler callback only):
* ```ts
* export default createStartHandler(defaultStreamHandler)
* ```
*
* @example With CDN URL rewriting:
* ```ts
* export default createStartHandler({
* handler: defaultStreamHandler,
* transformAssetUrls: 'https://cdn.example.com',
* })
* ```
*
* @example With per-request URL rewriting:
* ```ts
* export default createStartHandler({
* handler: defaultStreamHandler,
* transformAssetUrls: {
* transform: ({ url }) => {
* const cdnBase = getRequest().headers.get('x-cdn-base') || ''
* return `${cdnBase}${url}`
* },
* cache: false,
* },
* })
* ```
*/
export declare function createStartHandler<TRegister = Register>(cbOrOptions: HandlerCallback<AnyRouter> | CreateStartHandlerOptions): RequestHandler<TRegister>;

@@ -9,2 +9,3 @@ import { createMemoryHistory } from "@tanstack/history";

import { handleServerAction } from "./server-functions-handler.js";
import { resolveTransformConfig, transformManifestUrls, buildManifestWithClientEntry } from "./transformAssetUrls.js";
import { HEADERS } from "./constants.js";

@@ -24,3 +25,4 @@ import { ServerFunctionSerializationAdapter } from "./serializer/ServerFunctionSerializationAdapter.js";

let entriesPromise;
let manifestPromise;
let baseManifestPromise;
let cachedFinalManifestPromise;
async function loadEntries() {

@@ -37,11 +39,27 @@ const routerEntry = await import("#tanstack-router-entry");

}
function getManifest(matchedRoutes) {
function getBaseManifest(matchedRoutes) {
if (process.env.TSS_DEV_SERVER === "true") {
return getStartManifest(matchedRoutes);
}
if (!manifestPromise) {
manifestPromise = getStartManifest();
if (!baseManifestPromise) {
baseManifestPromise = getStartManifest();
}
return manifestPromise;
return baseManifestPromise;
}
async function resolveManifest(matchedRoutes, transformFn, cache) {
const base = await getBaseManifest(matchedRoutes);
const computeFinalManifest = async () => {
return transformFn ? await transformManifestUrls(base, transformFn, { clone: !cache }) : buildManifestWithClientEntry(base);
};
if (process.env.TSS_DEV_SERVER === "true") {
return computeFinalManifest();
}
if (!transformFn || cache) {
if (!cachedFinalManifestPromise) {
cachedFinalManifestPromise = computeFinalManifest();
}
return cachedFinalManifestPromise;
}
return computeFinalManifest();
}
const ROUTER_BASEPATH = process.env.TSS_ROUTER_BASEPATH || "/";

@@ -120,3 +138,38 @@ const SERVER_FN_BASE = process.env.TSS_SERVER_FN_BASE;

}
function createStartHandler(cb) {
function createStartHandler(cbOrOptions) {
const cb = typeof cbOrOptions === "function" ? cbOrOptions : cbOrOptions.handler;
const transformAssetUrlsOption = typeof cbOrOptions === "function" ? void 0 : cbOrOptions.transformAssetUrls;
const warmupTransformManifest = !!transformAssetUrlsOption && typeof transformAssetUrlsOption === "object" && transformAssetUrlsOption.warmup === true;
const resolvedTransformConfig = transformAssetUrlsOption ? resolveTransformConfig(transformAssetUrlsOption) : void 0;
const cache = resolvedTransformConfig ? resolvedTransformConfig.cache : true;
let cachedCreateTransformPromise;
const getTransformFn = async (opts) => {
if (!resolvedTransformConfig) return void 0;
if (resolvedTransformConfig.type === "createTransform") {
if (cache) {
if (!cachedCreateTransformPromise) {
cachedCreateTransformPromise = Promise.resolve(
resolvedTransformConfig.createTransform(opts)
);
}
return cachedCreateTransformPromise;
}
return resolvedTransformConfig.createTransform(opts);
}
return resolvedTransformConfig.transformFn;
};
if (warmupTransformManifest && cache && process.env.TSS_DEV_SERVER !== "true" && !cachedFinalManifestPromise) {
const warmupPromise = (async () => {
const base = await getBaseManifest(void 0);
const transformFn = await getTransformFn({ warmup: true });
return transformFn ? await transformManifestUrls(base, transformFn, { clone: false }) : buildManifestWithClientEntry(base);
})();
cachedFinalManifestPromise = warmupPromise;
warmupPromise.catch(() => {
if (cachedFinalManifestPromise === warmupPromise) {
cachedFinalManifestPromise = void 0;
}
cachedCreateTransformPromise = void 0;
});
}
const startRequestResolver = async (request, requestOpts) => {

@@ -215,3 +268,7 @@ let router = null;

}
const manifest = await getManifest(matchedRoutes);
const manifest = await resolveManifest(
matchedRoutes,
await getTransformFn({ warmup: false, request }),
cache
);
const routerInstance = await getRouter();

@@ -218,0 +275,0 @@ attachRouterServerSsrUtils({

+1
-1

@@ -1,1 +0,1 @@

{"version":3,"file":"createStartHandler.js","sources":["../../src/createStartHandler.ts"],"sourcesContent":["import { createMemoryHistory } from '@tanstack/history'\nimport {\n createNullProtoObject,\n flattenMiddlewares,\n mergeHeaders,\n safeObjectMerge,\n} from '@tanstack/start-client-core'\nimport {\n executeRewriteInput,\n isRedirect,\n isResolvedRedirect,\n} from '@tanstack/router-core'\nimport {\n attachRouterServerSsrUtils,\n getNormalizedURL,\n getOrigin,\n} from '@tanstack/router-core/ssr/server'\nimport { runWithStartContext } from '@tanstack/start-storage-context'\nimport { requestHandler } from './request-response'\nimport { getStartManifest } from './router-manifest'\nimport { handleServerAction } from './server-functions-handler'\n\nimport { HEADERS } from './constants'\nimport { ServerFunctionSerializationAdapter } from './serializer/ServerFunctionSerializationAdapter'\nimport type {\n AnyFunctionMiddleware,\n AnyRequestMiddleware,\n AnyStartInstanceOptions,\n RouteMethod,\n RouteMethodHandlerFn,\n RouterEntry,\n StartEntry,\n} from '@tanstack/start-client-core'\nimport type { RequestHandler } from './request-handler'\nimport type {\n AnyRoute,\n AnyRouter,\n Manifest,\n Register,\n} from '@tanstack/router-core'\nimport type { HandlerCallback } from '@tanstack/router-core/ssr/server'\n\ntype TODO = any\n\ntype AnyMiddlewareServerFn =\n | AnyRequestMiddleware['options']['server']\n | AnyFunctionMiddleware['options']['server']\n\nfunction getStartResponseHeaders(opts: { router: AnyRouter }) {\n const headers = mergeHeaders(\n {\n 'Content-Type': 'text/html; charset=utf-8',\n },\n ...opts.router.state.matches.map((match) => {\n return match.headers\n }),\n )\n return headers\n}\n\n// Cached entries - promises stored immediately to prevent concurrent imports\n// that can cause race conditions during module initialization\nlet entriesPromise:\n | Promise<{\n startEntry: StartEntry\n routerEntry: RouterEntry\n }>\n | undefined\nlet manifestPromise: Promise<Manifest> | undefined\n\nasync function loadEntries() {\n // @ts-ignore when building, we currently don't respect tsconfig.ts' `include` so we are not picking up the .d.ts from start-client-core\n const routerEntry = (await import('#tanstack-router-entry')) as RouterEntry\n // @ts-ignore when building, we currently don't respect tsconfig.ts' `include` so we are not picking up the .d.ts from start-client-core\n const startEntry = (await import('#tanstack-start-entry')) as StartEntry\n return { startEntry, routerEntry }\n}\n\nfunction getEntries() {\n if (!entriesPromise) {\n entriesPromise = loadEntries()\n }\n return entriesPromise\n}\n\nfunction getManifest(matchedRoutes?: ReadonlyArray<AnyRoute>) {\n // In dev mode, always get fresh manifest (no caching) to include route-specific dev styles\n if (process.env.TSS_DEV_SERVER === 'true') {\n return getStartManifest(matchedRoutes)\n }\n // In prod, cache the manifest\n if (!manifestPromise) {\n manifestPromise = getStartManifest()\n }\n return manifestPromise\n}\n\n// Pre-computed constants\nconst ROUTER_BASEPATH = process.env.TSS_ROUTER_BASEPATH || '/'\nconst SERVER_FN_BASE = process.env.TSS_SERVER_FN_BASE\nconst IS_PRERENDERING = process.env.TSS_PRERENDERING === 'true'\nconst IS_SHELL_ENV = process.env.TSS_SHELL === 'true'\nconst IS_DEV = process.env.NODE_ENV === 'development'\n\n// Reusable error messages\nconst ERR_NO_RESPONSE = IS_DEV\n ? `It looks like you forgot to return a response from your server route handler. If you want to defer to the app router, make sure to have a component set in this route.`\n : 'Internal Server Error'\n\nconst ERR_NO_DEFER = IS_DEV\n ? `You cannot defer to the app router if there is no component defined on this route.`\n : 'Internal Server Error'\n\nfunction throwRouteHandlerError(): never {\n throw new Error(ERR_NO_RESPONSE)\n}\n\nfunction throwIfMayNotDefer(): never {\n throw new Error(ERR_NO_DEFER)\n}\n\n/**\n * Check if a value is a special response (Response or Redirect)\n */\nfunction isSpecialResponse(value: unknown): value is Response {\n return value instanceof Response || isRedirect(value)\n}\n\n/**\n * Normalize middleware result to context shape\n */\nfunction handleCtxResult(result: TODO) {\n if (isSpecialResponse(result)) {\n return { response: result }\n }\n return result\n}\n\n/**\n * Execute a middleware chain\n */\nfunction executeMiddleware(middlewares: Array<TODO>, ctx: TODO): Promise<TODO> {\n let index = -1\n\n const next = async (nextCtx?: TODO): Promise<TODO> => {\n // Merge context if provided using safeObjectMerge for prototype pollution prevention\n if (nextCtx) {\n if (nextCtx.context) {\n ctx.context = safeObjectMerge(ctx.context, nextCtx.context)\n }\n // Copy own properties except context (Object.keys returns only own enumerable properties)\n for (const key of Object.keys(nextCtx)) {\n if (key !== 'context') {\n ctx[key] = nextCtx[key]\n }\n }\n }\n\n index++\n const middleware = middlewares[index]\n if (!middleware) return ctx\n\n let result: TODO\n try {\n result = await middleware({ ...ctx, next })\n } catch (err) {\n if (isSpecialResponse(err)) {\n ctx.response = err\n return ctx\n }\n throw err\n }\n\n const normalized = handleCtxResult(result)\n if (normalized) {\n if (normalized.response !== undefined) {\n ctx.response = normalized.response\n }\n if (normalized.context) {\n ctx.context = safeObjectMerge(ctx.context, normalized.context)\n }\n }\n\n return ctx\n }\n\n return next()\n}\n\n/**\n * Wrap a route handler as middleware\n */\nfunction handlerToMiddleware(\n handler: RouteMethodHandlerFn<any, AnyRoute, any, any, any, any, any>,\n mayDefer: boolean = false,\n): TODO {\n if (mayDefer) {\n return handler\n }\n return async (ctx: TODO) => {\n const response = await handler({ ...ctx, next: throwIfMayNotDefer })\n if (!response) {\n throwRouteHandlerError()\n }\n return response\n }\n}\n\nexport function createStartHandler<TRegister = Register>(\n cb: HandlerCallback<AnyRouter>,\n): RequestHandler<TRegister> {\n const startRequestResolver: RequestHandler<Register> = async (\n request,\n requestOpts,\n ) => {\n let router: AnyRouter | null = null as AnyRouter | null\n let cbWillCleanup = false as boolean\n\n try {\n // normalizing and sanitizing the pathname here for server, so we always deal with the same format during SSR.\n // during normalization paths like '//posts' are flattened to '/posts'.\n // in these cases we would prefer to redirect to the new path\n const { url, handledProtocolRelativeURL } = getNormalizedURL(request.url)\n const href = url.pathname + url.search + url.hash\n const origin = getOrigin(request)\n\n if (handledProtocolRelativeURL) {\n return Response.redirect(url, 308)\n }\n\n const entries = await getEntries()\n const startOptions: AnyStartInstanceOptions =\n (await entries.startEntry.startInstance?.getOptions()) ||\n ({} as AnyStartInstanceOptions)\n\n const serializationAdapters = [\n ...(startOptions.serializationAdapters || []),\n ServerFunctionSerializationAdapter,\n ]\n\n const requestStartOptions = {\n ...startOptions,\n serializationAdapters,\n }\n\n // Flatten request middlewares once\n const flattenedRequestMiddlewares = startOptions.requestMiddleware\n ? flattenMiddlewares(startOptions.requestMiddleware)\n : []\n\n // Create set for deduplication\n const executedRequestMiddlewares = new Set<TODO>(\n flattenedRequestMiddlewares,\n )\n\n // Memoized router getter\n const getRouter = async (): Promise<AnyRouter> => {\n if (router) return router\n\n router = await entries.routerEntry.getRouter()\n\n let isShell = IS_SHELL_ENV\n if (IS_PRERENDERING && !isShell) {\n isShell = request.headers.get(HEADERS.TSS_SHELL) === 'true'\n }\n\n const history = createMemoryHistory({\n initialEntries: [href],\n })\n\n router.update({\n history,\n isShell,\n isPrerendering: IS_PRERENDERING,\n origin: router.options.origin ?? origin,\n ...{\n defaultSsr: requestStartOptions.defaultSsr,\n serializationAdapters: [\n ...requestStartOptions.serializationAdapters,\n ...(router.options.serializationAdapters || []),\n ],\n },\n basepath: ROUTER_BASEPATH,\n })\n\n return router\n }\n\n // Check for server function requests first (early exit)\n if (SERVER_FN_BASE && url.pathname.startsWith(SERVER_FN_BASE)) {\n const serverFnId = url.pathname\n .slice(SERVER_FN_BASE.length)\n .split('/')[0]\n\n if (!serverFnId) {\n throw new Error('Invalid server action param for serverFnId')\n }\n\n const serverFnHandler = async ({ context }: TODO) => {\n return runWithStartContext(\n {\n getRouter,\n startOptions: requestStartOptions,\n contextAfterGlobalMiddlewares: context,\n request,\n executedRequestMiddlewares,\n },\n () =>\n handleServerAction({\n request,\n context: requestOpts?.context,\n serverFnId,\n }),\n )\n }\n\n const middlewares = flattenedRequestMiddlewares.map(\n (d) => d.options.server,\n )\n const ctx = await executeMiddleware([...middlewares, serverFnHandler], {\n request,\n context: createNullProtoObject(requestOpts?.context),\n })\n\n return handleRedirectResponse(ctx.response, request, getRouter)\n }\n\n // Router execution function\n const executeRouter = async (\n serverContext: TODO,\n matchedRoutes?: ReadonlyArray<AnyRoute>,\n ): Promise<Response> => {\n const acceptHeader = request.headers.get('Accept') || '*/*'\n const acceptParts = acceptHeader.split(',')\n const supportedMimeTypes = ['*/*', 'text/html']\n\n const isSupported = supportedMimeTypes.some((mimeType) =>\n acceptParts.some((part) => part.trim().startsWith(mimeType)),\n )\n\n if (!isSupported) {\n return Response.json(\n { error: 'Only HTML requests are supported here' },\n { status: 500 },\n )\n }\n\n const manifest = await getManifest(matchedRoutes)\n const routerInstance = await getRouter()\n\n attachRouterServerSsrUtils({\n router: routerInstance,\n manifest,\n })\n\n routerInstance.update({ additionalContext: { serverContext } })\n await routerInstance.load()\n\n if (routerInstance.state.redirect) {\n return routerInstance.state.redirect\n }\n\n await routerInstance.serverSsr!.dehydrate()\n\n const responseHeaders = getStartResponseHeaders({\n router: routerInstance,\n })\n cbWillCleanup = true\n\n return cb({\n request,\n router: routerInstance,\n responseHeaders,\n })\n }\n\n // Main request handler\n const requestHandlerMiddleware = async ({ context }: TODO) => {\n return runWithStartContext(\n {\n getRouter,\n startOptions: requestStartOptions,\n contextAfterGlobalMiddlewares: context,\n request,\n executedRequestMiddlewares,\n },\n async () => {\n try {\n return await handleServerRoutes({\n getRouter,\n request,\n url,\n executeRouter,\n context,\n executedRequestMiddlewares,\n })\n } catch (err) {\n if (err instanceof Response) {\n return err\n }\n throw err\n }\n },\n )\n }\n\n const middlewares = flattenedRequestMiddlewares.map(\n (d) => d.options.server,\n )\n const ctx = await executeMiddleware(\n [...middlewares, requestHandlerMiddleware],\n { request, context: createNullProtoObject(requestOpts?.context) },\n )\n\n return handleRedirectResponse(ctx.response, request, getRouter)\n } finally {\n if (router && !cbWillCleanup) {\n // Clean up router SSR state if it was set up but won't be cleaned up by the callback\n // (e.g., in redirect cases or early returns before the callback is invoked).\n // When the callback runs, it handles cleanup (either via transformStreamWithRouter\n // for streaming, or directly in renderRouterToString for non-streaming).\n router.serverSsr?.cleanup()\n }\n router = null\n }\n }\n\n return requestHandler(startRequestResolver)\n}\n\nasync function handleRedirectResponse(\n response: Response,\n request: Request,\n getRouter: () => Promise<AnyRouter>,\n): Promise<Response> {\n if (!isRedirect(response)) {\n return response\n }\n\n if (isResolvedRedirect(response)) {\n if (request.headers.get('x-tsr-serverFn') === 'true') {\n return Response.json(\n { ...response.options, isSerializedRedirect: true },\n { headers: response.headers },\n )\n }\n return response\n }\n\n const opts = response.options\n if (opts.to && typeof opts.to === 'string' && !opts.to.startsWith('/')) {\n throw new Error(\n `Server side redirects must use absolute paths via the 'href' or 'to' options. The redirect() method's \"to\" property accepts an internal path only. Use the \"href\" property to provide an external URL. Received: ${JSON.stringify(opts)}`,\n )\n }\n\n if (\n ['params', 'search', 'hash'].some(\n (d) => typeof (opts as TODO)[d] === 'function',\n )\n ) {\n throw new Error(\n `Server side redirects must use static search, params, and hash values and do not support functional values. Received functional values for: ${Object.keys(\n opts,\n )\n .filter((d) => typeof (opts as TODO)[d] === 'function')\n .map((d) => `\"${d}\"`)\n .join(', ')}`,\n )\n }\n\n const router = await getRouter()\n const redirect = router.resolveRedirect(response)\n\n if (request.headers.get('x-tsr-serverFn') === 'true') {\n return Response.json(\n { ...response.options, isSerializedRedirect: true },\n { headers: response.headers },\n )\n }\n\n return redirect\n}\n\nasync function handleServerRoutes({\n getRouter,\n request,\n url,\n executeRouter,\n context,\n executedRequestMiddlewares,\n}: {\n getRouter: () => Promise<AnyRouter>\n request: Request\n url: URL\n executeRouter: (\n serverContext: any,\n matchedRoutes?: ReadonlyArray<AnyRoute>,\n ) => Promise<Response>\n context: any\n executedRequestMiddlewares: Set<AnyRequestMiddleware>\n}): Promise<Response> {\n const router = await getRouter()\n const rewrittenUrl = executeRewriteInput(router.rewrite, url)\n const pathname = rewrittenUrl.pathname\n // this will perform a fuzzy match, however for server routes we need an exact match\n // if the route is not an exact match, executeRouter will handle rendering the app router\n // the match will be cached internally, so no extra work is done during the app router render\n const { matchedRoutes, foundRoute, routeParams } =\n router.getMatchedRoutes(pathname)\n\n const isExactMatch = foundRoute && routeParams['**'] === undefined\n\n // Collect and dedupe route middlewares\n const routeMiddlewares: Array<AnyMiddlewareServerFn> = []\n\n // Collect middleware from matched routes, filtering out those already executed\n // in the request phase\n for (const route of matchedRoutes) {\n const serverMiddleware = route.options.server?.middleware as\n | Array<AnyRequestMiddleware>\n | undefined\n if (serverMiddleware) {\n const flattened = flattenMiddlewares(serverMiddleware)\n for (const m of flattened) {\n if (!executedRequestMiddlewares.has(m)) {\n routeMiddlewares.push(m.options.server)\n }\n }\n }\n }\n\n // Add handler middleware if exact match\n const server = foundRoute?.options.server\n if (server?.handlers && isExactMatch) {\n const handlers =\n typeof server.handlers === 'function'\n ? server.handlers({ createHandlers: (d: any) => d })\n : server.handlers\n\n const requestMethod = request.method.toUpperCase() as RouteMethod\n const handler = handlers[requestMethod] ?? handlers['ANY']\n\n if (handler) {\n const mayDefer = !!foundRoute.options.component\n\n if (typeof handler === 'function') {\n routeMiddlewares.push(handlerToMiddleware(handler, mayDefer))\n } else {\n if (handler.middleware?.length) {\n const handlerMiddlewares = flattenMiddlewares(handler.middleware)\n for (const m of handlerMiddlewares) {\n routeMiddlewares.push(m.options.server)\n }\n }\n if (handler.handler) {\n routeMiddlewares.push(handlerToMiddleware(handler.handler, mayDefer))\n }\n }\n }\n }\n\n // Final middleware: execute router with matched routes for dev styles\n routeMiddlewares.push((ctx: TODO) =>\n executeRouter(ctx.context, matchedRoutes),\n )\n\n const ctx = await executeMiddleware(routeMiddlewares, {\n request,\n context,\n params: routeParams,\n pathname,\n })\n\n return ctx.response\n}\n"],"names":["middlewares","ctx"],"mappings":"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"}
{"version":3,"file":"createStartHandler.js","sources":["../../src/createStartHandler.ts"],"sourcesContent":["import { createMemoryHistory } from '@tanstack/history'\nimport {\n createNullProtoObject,\n flattenMiddlewares,\n mergeHeaders,\n safeObjectMerge,\n} from '@tanstack/start-client-core'\nimport {\n executeRewriteInput,\n isRedirect,\n isResolvedRedirect,\n} from '@tanstack/router-core'\nimport {\n attachRouterServerSsrUtils,\n getNormalizedURL,\n getOrigin,\n} from '@tanstack/router-core/ssr/server'\nimport { runWithStartContext } from '@tanstack/start-storage-context'\nimport { requestHandler } from './request-response'\nimport { getStartManifest } from './router-manifest'\nimport { handleServerAction } from './server-functions-handler'\nimport {\n buildManifestWithClientEntry,\n resolveTransformConfig,\n transformManifestUrls,\n} from './transformAssetUrls'\n\nimport { HEADERS } from './constants'\nimport { ServerFunctionSerializationAdapter } from './serializer/ServerFunctionSerializationAdapter'\nimport type {\n AnyFunctionMiddleware,\n AnyRequestMiddleware,\n AnyStartInstanceOptions,\n RouteMethod,\n RouteMethodHandlerFn,\n RouterEntry,\n StartEntry,\n} from '@tanstack/start-client-core'\nimport type { RequestHandler } from './request-handler'\nimport type {\n AnyRoute,\n AnyRouter,\n Manifest,\n Register,\n} from '@tanstack/router-core'\nimport type { HandlerCallback } from '@tanstack/router-core/ssr/server'\nimport type {\n StartManifestWithClientEntry,\n TransformAssetUrls,\n TransformAssetUrlsFn,\n} from './transformAssetUrls'\n\ntype TODO = any\n\ntype AnyMiddlewareServerFn =\n | AnyRequestMiddleware['options']['server']\n | AnyFunctionMiddleware['options']['server']\n\nexport interface CreateStartHandlerOptions {\n handler: HandlerCallback<AnyRouter>\n /**\n * Transform asset URLs at runtime, e.g. to prepend a CDN prefix.\n *\n * **String** — a URL prefix prepended to every asset URL (cached by default):\n * ```ts\n * createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssetUrls: 'https://cdn.example.com',\n * })\n * ```\n *\n * **Callback** — receives `{ url, type }` and returns a new URL\n * (cached by default — runs once on first request):\n * ```ts\n * createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssetUrls: ({ url, type }) => {\n * return `https://cdn.example.com${url}`\n * },\n * })\n * ```\n *\n * **Object** — for explicit cache control:\n * ```ts\n * createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssetUrls: {\n * transform: ({ url }) => {\n * const region = getRequest().headers.get('x-region') || 'us'\n * return `https://cdn-${region}.example.com${url}`\n * },\n * cache: false, // transform per-request\n * },\n * })\n * ```\n *\n * `type` is one of `'modulepreload' | 'stylesheet' | 'clientEntry'`.\n *\n * By default, the transformed manifest is cached after the first request\n * (`cache: true`). Set `cache: false` for per-request transforms.\n *\n * If you're using a cached transform, you can optionally set `warmup: true`\n * (object form only) to compute the transformed manifest in the background at\n * server startup.\n *\n * Note: This only transforms URLs managed by TanStack Start's manifest\n * (JS preloads, CSS links, and the client entry script). For asset imports\n * used directly in components (e.g. `import logo from './logo.svg'`),\n * configure Vite's `experimental.renderBuiltUrl` in your vite.config.ts.\n */\n transformAssetUrls?: TransformAssetUrls\n}\n\nfunction getStartResponseHeaders(opts: { router: AnyRouter }) {\n const headers = mergeHeaders(\n {\n 'Content-Type': 'text/html; charset=utf-8',\n },\n ...opts.router.state.matches.map((match) => {\n return match.headers\n }),\n )\n return headers\n}\n\n// Cached entries - promises stored immediately to prevent concurrent imports\n// that can cause race conditions during module initialization\nlet entriesPromise:\n | Promise<{\n startEntry: StartEntry\n routerEntry: RouterEntry\n }>\n | undefined\nlet baseManifestPromise: Promise<StartManifestWithClientEntry> | undefined\n\n/**\n * Cached final manifest (with client entry script tag). In production,\n * this is computed once and reused for every request when caching is enabled.\n */\nlet cachedFinalManifestPromise: Promise<Manifest> | undefined\n\nasync function loadEntries() {\n // @ts-ignore when building, we currently don't respect tsconfig.ts' `include` so we are not picking up the .d.ts from start-client-core\n const routerEntry = (await import('#tanstack-router-entry')) as RouterEntry\n // @ts-ignore when building, we currently don't respect tsconfig.ts' `include` so we are not picking up the .d.ts from start-client-core\n const startEntry = (await import('#tanstack-start-entry')) as StartEntry\n return { startEntry, routerEntry }\n}\n\nfunction getEntries() {\n if (!entriesPromise) {\n entriesPromise = loadEntries()\n }\n return entriesPromise\n}\n\n/**\n * Returns the raw manifest data (without client entry script tag baked in).\n * In dev mode, always returns fresh data. In prod, cached.\n */\nfunction getBaseManifest(\n matchedRoutes?: ReadonlyArray<AnyRoute>,\n): Promise<StartManifestWithClientEntry> {\n // In dev mode, always get fresh manifest (no caching) to include route-specific dev styles\n if (process.env.TSS_DEV_SERVER === 'true') {\n return getStartManifest(matchedRoutes)\n }\n // In prod, cache the base manifest\n if (!baseManifestPromise) {\n baseManifestPromise = getStartManifest()\n }\n return baseManifestPromise\n}\n\n/**\n * Resolves a final Manifest for a given request.\n *\n * - No transform: builds client entry script tag and returns (cached in prod).\n * - Cached transform: transforms all URLs + builds script tag, caches result.\n * - Per-request transform: deep-clones base manifest, transforms per-request.\n */\nasync function resolveManifest(\n matchedRoutes: ReadonlyArray<AnyRoute> | undefined,\n transformFn: TransformAssetUrlsFn | undefined,\n cache: boolean,\n): Promise<Manifest> {\n const base = await getBaseManifest(matchedRoutes)\n\n const computeFinalManifest = async () => {\n return transformFn\n ? await transformManifestUrls(base, transformFn, { clone: !cache })\n : buildManifestWithClientEntry(base)\n }\n\n // In dev, always compute fresh to include route-specific dev styles.\n if (process.env.TSS_DEV_SERVER === 'true') {\n return computeFinalManifest()\n }\n\n // In prod, cache unless we're explicitly doing per-request transforms.\n if (!transformFn || cache) {\n if (!cachedFinalManifestPromise) {\n cachedFinalManifestPromise = computeFinalManifest()\n }\n return cachedFinalManifestPromise\n }\n\n // Per-request transform — deep-clone and transform every time.\n return computeFinalManifest()\n}\n\n// Pre-computed constants\nconst ROUTER_BASEPATH = process.env.TSS_ROUTER_BASEPATH || '/'\nconst SERVER_FN_BASE = process.env.TSS_SERVER_FN_BASE\nconst IS_PRERENDERING = process.env.TSS_PRERENDERING === 'true'\nconst IS_SHELL_ENV = process.env.TSS_SHELL === 'true'\nconst IS_DEV = process.env.NODE_ENV === 'development'\n\n// Reusable error messages\nconst ERR_NO_RESPONSE = IS_DEV\n ? `It looks like you forgot to return a response from your server route handler. If you want to defer to the app router, make sure to have a component set in this route.`\n : 'Internal Server Error'\n\nconst ERR_NO_DEFER = IS_DEV\n ? `You cannot defer to the app router if there is no component defined on this route.`\n : 'Internal Server Error'\n\nfunction throwRouteHandlerError(): never {\n throw new Error(ERR_NO_RESPONSE)\n}\n\nfunction throwIfMayNotDefer(): never {\n throw new Error(ERR_NO_DEFER)\n}\n\n/**\n * Check if a value is a special response (Response or Redirect)\n */\nfunction isSpecialResponse(value: unknown): value is Response {\n return value instanceof Response || isRedirect(value)\n}\n\n/**\n * Normalize middleware result to context shape\n */\nfunction handleCtxResult(result: TODO) {\n if (isSpecialResponse(result)) {\n return { response: result }\n }\n return result\n}\n\n/**\n * Execute a middleware chain\n */\nfunction executeMiddleware(middlewares: Array<TODO>, ctx: TODO): Promise<TODO> {\n let index = -1\n\n const next = async (nextCtx?: TODO): Promise<TODO> => {\n // Merge context if provided using safeObjectMerge for prototype pollution prevention\n if (nextCtx) {\n if (nextCtx.context) {\n ctx.context = safeObjectMerge(ctx.context, nextCtx.context)\n }\n // Copy own properties except context (Object.keys returns only own enumerable properties)\n for (const key of Object.keys(nextCtx)) {\n if (key !== 'context') {\n ctx[key] = nextCtx[key]\n }\n }\n }\n\n index++\n const middleware = middlewares[index]\n if (!middleware) return ctx\n\n let result: TODO\n try {\n result = await middleware({ ...ctx, next })\n } catch (err) {\n if (isSpecialResponse(err)) {\n ctx.response = err\n return ctx\n }\n throw err\n }\n\n const normalized = handleCtxResult(result)\n if (normalized) {\n if (normalized.response !== undefined) {\n ctx.response = normalized.response\n }\n if (normalized.context) {\n ctx.context = safeObjectMerge(ctx.context, normalized.context)\n }\n }\n\n return ctx\n }\n\n return next()\n}\n\n/**\n * Wrap a route handler as middleware\n */\nfunction handlerToMiddleware(\n handler: RouteMethodHandlerFn<any, AnyRoute, any, any, any, any, any>,\n mayDefer: boolean = false,\n): TODO {\n if (mayDefer) {\n return handler\n }\n return async (ctx: TODO) => {\n const response = await handler({ ...ctx, next: throwIfMayNotDefer })\n if (!response) {\n throwRouteHandlerError()\n }\n return response\n }\n}\n\n/**\n * Creates the TanStack Start request handler.\n *\n * @example Backwards-compatible usage (handler callback only):\n * ```ts\n * export default createStartHandler(defaultStreamHandler)\n * ```\n *\n * @example With CDN URL rewriting:\n * ```ts\n * export default createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssetUrls: 'https://cdn.example.com',\n * })\n * ```\n *\n * @example With per-request URL rewriting:\n * ```ts\n * export default createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssetUrls: {\n * transform: ({ url }) => {\n * const cdnBase = getRequest().headers.get('x-cdn-base') || ''\n * return `${cdnBase}${url}`\n * },\n * cache: false,\n * },\n * })\n * ```\n */\nexport function createStartHandler<TRegister = Register>(\n cbOrOptions: HandlerCallback<AnyRouter> | CreateStartHandlerOptions,\n): RequestHandler<TRegister> {\n // Normalize the overloaded argument\n const cb: HandlerCallback<AnyRouter> =\n typeof cbOrOptions === 'function' ? cbOrOptions : cbOrOptions.handler\n const transformAssetUrlsOption: TransformAssetUrls | undefined =\n typeof cbOrOptions === 'function'\n ? undefined\n : cbOrOptions.transformAssetUrls\n\n const warmupTransformManifest =\n !!transformAssetUrlsOption &&\n typeof transformAssetUrlsOption === 'object' &&\n transformAssetUrlsOption.warmup === true\n\n // Pre-resolve the transform function and cache flag\n const resolvedTransformConfig = transformAssetUrlsOption\n ? resolveTransformConfig(transformAssetUrlsOption)\n : undefined\n const cache = resolvedTransformConfig ? resolvedTransformConfig.cache : true\n\n // Memoize a single createTransform() result when caching is enabled.\n let cachedCreateTransformPromise: Promise<TransformAssetUrlsFn> | undefined\n\n const getTransformFn = async (\n opts: { warmup: true } | { warmup: false; request: Request },\n ): Promise<TransformAssetUrlsFn | undefined> => {\n if (!resolvedTransformConfig) return undefined\n if (resolvedTransformConfig.type === 'createTransform') {\n if (cache) {\n if (!cachedCreateTransformPromise) {\n cachedCreateTransformPromise = Promise.resolve(\n resolvedTransformConfig.createTransform(opts),\n )\n }\n return cachedCreateTransformPromise\n }\n return resolvedTransformConfig.createTransform(opts)\n }\n return resolvedTransformConfig.transformFn\n }\n\n // Background warmup for cached transforms (production only)\n if (\n warmupTransformManifest &&\n cache &&\n process.env.TSS_DEV_SERVER !== 'true' &&\n !cachedFinalManifestPromise\n ) {\n // NOTE: Do not call resolveManifest() here.\n // resolveManifest() reads from cachedFinalManifestPromise, and since we set\n // cachedFinalManifestPromise to this warmup promise, that would create a\n // self-referential promise and hang forever.\n const warmupPromise = (async () => {\n const base = await getBaseManifest(undefined)\n const transformFn = await getTransformFn({ warmup: true })\n return transformFn\n ? await transformManifestUrls(base, transformFn, { clone: false })\n : buildManifestWithClientEntry(base)\n })()\n cachedFinalManifestPromise = warmupPromise\n warmupPromise.catch(() => {\n // If warmup fails, allow the next request to retry.\n if (cachedFinalManifestPromise === warmupPromise) {\n cachedFinalManifestPromise = undefined\n }\n cachedCreateTransformPromise = undefined\n })\n }\n\n const startRequestResolver: RequestHandler<Register> = async (\n request,\n requestOpts,\n ) => {\n let router: AnyRouter | null = null as AnyRouter | null\n let cbWillCleanup = false as boolean\n\n try {\n // normalizing and sanitizing the pathname here for server, so we always deal with the same format during SSR.\n // during normalization paths like '//posts' are flattened to '/posts'.\n // in these cases we would prefer to redirect to the new path\n const { url, handledProtocolRelativeURL } = getNormalizedURL(request.url)\n const href = url.pathname + url.search + url.hash\n const origin = getOrigin(request)\n\n if (handledProtocolRelativeURL) {\n return Response.redirect(url, 308)\n }\n\n const entries = await getEntries()\n const startOptions: AnyStartInstanceOptions =\n (await entries.startEntry.startInstance?.getOptions()) ||\n ({} as AnyStartInstanceOptions)\n\n const serializationAdapters = [\n ...(startOptions.serializationAdapters || []),\n ServerFunctionSerializationAdapter,\n ]\n\n const requestStartOptions = {\n ...startOptions,\n serializationAdapters,\n }\n\n // Flatten request middlewares once\n const flattenedRequestMiddlewares = startOptions.requestMiddleware\n ? flattenMiddlewares(startOptions.requestMiddleware)\n : []\n\n // Create set for deduplication\n const executedRequestMiddlewares = new Set<TODO>(\n flattenedRequestMiddlewares,\n )\n\n // Memoized router getter\n const getRouter = async (): Promise<AnyRouter> => {\n if (router) return router\n\n router = await entries.routerEntry.getRouter()\n\n let isShell = IS_SHELL_ENV\n if (IS_PRERENDERING && !isShell) {\n isShell = request.headers.get(HEADERS.TSS_SHELL) === 'true'\n }\n\n const history = createMemoryHistory({\n initialEntries: [href],\n })\n\n router.update({\n history,\n isShell,\n isPrerendering: IS_PRERENDERING,\n origin: router.options.origin ?? origin,\n ...{\n defaultSsr: requestStartOptions.defaultSsr,\n serializationAdapters: [\n ...requestStartOptions.serializationAdapters,\n ...(router.options.serializationAdapters || []),\n ],\n },\n basepath: ROUTER_BASEPATH,\n })\n\n return router\n }\n\n // Check for server function requests first (early exit)\n if (SERVER_FN_BASE && url.pathname.startsWith(SERVER_FN_BASE)) {\n const serverFnId = url.pathname\n .slice(SERVER_FN_BASE.length)\n .split('/')[0]\n\n if (!serverFnId) {\n throw new Error('Invalid server action param for serverFnId')\n }\n\n const serverFnHandler = async ({ context }: TODO) => {\n return runWithStartContext(\n {\n getRouter,\n startOptions: requestStartOptions,\n contextAfterGlobalMiddlewares: context,\n request,\n executedRequestMiddlewares,\n },\n () =>\n handleServerAction({\n request,\n context: requestOpts?.context,\n serverFnId,\n }),\n )\n }\n\n const middlewares = flattenedRequestMiddlewares.map(\n (d) => d.options.server,\n )\n const ctx = await executeMiddleware([...middlewares, serverFnHandler], {\n request,\n context: createNullProtoObject(requestOpts?.context),\n })\n\n return handleRedirectResponse(ctx.response, request, getRouter)\n }\n\n // Router execution function\n const executeRouter = async (\n serverContext: TODO,\n matchedRoutes?: ReadonlyArray<AnyRoute>,\n ): Promise<Response> => {\n const acceptHeader = request.headers.get('Accept') || '*/*'\n const acceptParts = acceptHeader.split(',')\n const supportedMimeTypes = ['*/*', 'text/html']\n\n const isSupported = supportedMimeTypes.some((mimeType) =>\n acceptParts.some((part) => part.trim().startsWith(mimeType)),\n )\n\n if (!isSupported) {\n return Response.json(\n { error: 'Only HTML requests are supported here' },\n { status: 500 },\n )\n }\n\n const manifest = await resolveManifest(\n matchedRoutes,\n await getTransformFn({ warmup: false, request }),\n cache,\n )\n const routerInstance = await getRouter()\n\n attachRouterServerSsrUtils({\n router: routerInstance,\n manifest,\n })\n\n routerInstance.update({ additionalContext: { serverContext } })\n await routerInstance.load()\n\n if (routerInstance.state.redirect) {\n return routerInstance.state.redirect\n }\n\n await routerInstance.serverSsr!.dehydrate()\n\n const responseHeaders = getStartResponseHeaders({\n router: routerInstance,\n })\n cbWillCleanup = true\n\n return cb({\n request,\n router: routerInstance,\n responseHeaders,\n })\n }\n\n // Main request handler\n const requestHandlerMiddleware = async ({ context }: TODO) => {\n return runWithStartContext(\n {\n getRouter,\n startOptions: requestStartOptions,\n contextAfterGlobalMiddlewares: context,\n request,\n executedRequestMiddlewares,\n },\n async () => {\n try {\n return await handleServerRoutes({\n getRouter,\n request,\n url,\n executeRouter,\n context,\n executedRequestMiddlewares,\n })\n } catch (err) {\n if (err instanceof Response) {\n return err\n }\n throw err\n }\n },\n )\n }\n\n const middlewares = flattenedRequestMiddlewares.map(\n (d) => d.options.server,\n )\n const ctx = await executeMiddleware(\n [...middlewares, requestHandlerMiddleware],\n { request, context: createNullProtoObject(requestOpts?.context) },\n )\n\n return handleRedirectResponse(ctx.response, request, getRouter)\n } finally {\n if (router && !cbWillCleanup) {\n // Clean up router SSR state if it was set up but won't be cleaned up by the callback\n // (e.g., in redirect cases or early returns before the callback is invoked).\n // When the callback runs, it handles cleanup (either via transformStreamWithRouter\n // for streaming, or directly in renderRouterToString for non-streaming).\n router.serverSsr?.cleanup()\n }\n router = null\n }\n }\n\n return requestHandler(startRequestResolver)\n}\n\nasync function handleRedirectResponse(\n response: Response,\n request: Request,\n getRouter: () => Promise<AnyRouter>,\n): Promise<Response> {\n if (!isRedirect(response)) {\n return response\n }\n\n if (isResolvedRedirect(response)) {\n if (request.headers.get('x-tsr-serverFn') === 'true') {\n return Response.json(\n { ...response.options, isSerializedRedirect: true },\n { headers: response.headers },\n )\n }\n return response\n }\n\n const opts = response.options\n if (opts.to && typeof opts.to === 'string' && !opts.to.startsWith('/')) {\n throw new Error(\n `Server side redirects must use absolute paths via the 'href' or 'to' options. The redirect() method's \"to\" property accepts an internal path only. Use the \"href\" property to provide an external URL. Received: ${JSON.stringify(opts)}`,\n )\n }\n\n if (\n ['params', 'search', 'hash'].some(\n (d) => typeof (opts as TODO)[d] === 'function',\n )\n ) {\n throw new Error(\n `Server side redirects must use static search, params, and hash values and do not support functional values. Received functional values for: ${Object.keys(\n opts,\n )\n .filter((d) => typeof (opts as TODO)[d] === 'function')\n .map((d) => `\"${d}\"`)\n .join(', ')}`,\n )\n }\n\n const router = await getRouter()\n const redirect = router.resolveRedirect(response)\n\n if (request.headers.get('x-tsr-serverFn') === 'true') {\n return Response.json(\n { ...response.options, isSerializedRedirect: true },\n { headers: response.headers },\n )\n }\n\n return redirect\n}\n\nasync function handleServerRoutes({\n getRouter,\n request,\n url,\n executeRouter,\n context,\n executedRequestMiddlewares,\n}: {\n getRouter: () => Promise<AnyRouter>\n request: Request\n url: URL\n executeRouter: (\n serverContext: any,\n matchedRoutes?: ReadonlyArray<AnyRoute>,\n ) => Promise<Response>\n context: any\n executedRequestMiddlewares: Set<AnyRequestMiddleware>\n}): Promise<Response> {\n const router = await getRouter()\n const rewrittenUrl = executeRewriteInput(router.rewrite, url)\n const pathname = rewrittenUrl.pathname\n // this will perform a fuzzy match, however for server routes we need an exact match\n // if the route is not an exact match, executeRouter will handle rendering the app router\n // the match will be cached internally, so no extra work is done during the app router render\n const { matchedRoutes, foundRoute, routeParams } =\n router.getMatchedRoutes(pathname)\n\n const isExactMatch = foundRoute && routeParams['**'] === undefined\n\n // Collect and dedupe route middlewares\n const routeMiddlewares: Array<AnyMiddlewareServerFn> = []\n\n // Collect middleware from matched routes, filtering out those already executed\n // in the request phase\n for (const route of matchedRoutes) {\n const serverMiddleware = route.options.server?.middleware as\n | Array<AnyRequestMiddleware>\n | undefined\n if (serverMiddleware) {\n const flattened = flattenMiddlewares(serverMiddleware)\n for (const m of flattened) {\n if (!executedRequestMiddlewares.has(m)) {\n routeMiddlewares.push(m.options.server)\n }\n }\n }\n }\n\n // Add handler middleware if exact match\n const server = foundRoute?.options.server\n if (server?.handlers && isExactMatch) {\n const handlers =\n typeof server.handlers === 'function'\n ? server.handlers({ createHandlers: (d: any) => d })\n : server.handlers\n\n const requestMethod = request.method.toUpperCase() as RouteMethod\n const handler = handlers[requestMethod] ?? handlers['ANY']\n\n if (handler) {\n const mayDefer = !!foundRoute.options.component\n\n if (typeof handler === 'function') {\n routeMiddlewares.push(handlerToMiddleware(handler, mayDefer))\n } else {\n if (handler.middleware?.length) {\n const handlerMiddlewares = flattenMiddlewares(handler.middleware)\n for (const m of handlerMiddlewares) {\n routeMiddlewares.push(m.options.server)\n }\n }\n if (handler.handler) {\n routeMiddlewares.push(handlerToMiddleware(handler.handler, mayDefer))\n }\n }\n }\n }\n\n // Final middleware: execute router with matched routes for dev styles\n routeMiddlewares.push((ctx: TODO) =>\n executeRouter(ctx.context, matchedRoutes),\n )\n\n const ctx = await executeMiddleware(routeMiddlewares, {\n request,\n context,\n params: routeParams,\n pathname,\n })\n\n return ctx.response\n}\n"],"names":["middlewares","ctx"],"mappings":"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"}
export { createStartHandler } from './createStartHandler.js';
export type { CreateStartHandlerOptions } from './createStartHandler.js';
export type { TransformAssetUrls, TransformAssetUrlsFn, TransformAssetUrlsContext, TransformAssetUrlsOptions, AssetUrlType, } from './transformAssetUrls.js';
export { attachRouterServerSsrUtils, createRequestHandler, defineHandlerCallback, transformReadableStreamWithRouter, transformPipeableStreamWithRouter, } from '@tanstack/router-core/ssr/server';

@@ -3,0 +5,0 @@ export type { HandlerCallback } from '@tanstack/router-core/ssr/server';

@@ -1,4 +0,5 @@

import { AnyRoute, RouterManagedTag } from '@tanstack/router-core';
import { AnyRoute } from '@tanstack/router-core';
import { StartManifestWithClientEntry } from './transformAssetUrls.js';
/**
* @description Returns the router manifest that should be sent to the client.
* @description Returns the router manifest data that should be sent to the client.
* This includes only the assets and preloads for the current route and any

@@ -8,12 +9,8 @@ * special assets that are needed for the client. It does not include relationships

*
* The client entry URL is returned separately so that it can be transformed
* (e.g. for CDN rewriting) before being embedded into the `<script>` tag.
*
* @param matchedRoutes - In dev mode, the matched routes are used to build
* the dev styles URL for route-scoped CSS collection.
*/
export declare function getStartManifest(matchedRoutes?: ReadonlyArray<AnyRoute>): Promise<{
routes: {
[k: string]: {
preloads?: Array<string>;
assets?: Array<RouterManagedTag>;
};
};
}>;
export declare function getStartManifest(matchedRoutes?: ReadonlyArray<AnyRoute>): Promise<StartManifestWithClientEntry>;

@@ -19,17 +19,9 @@ import { rootRouteId, buildDevStylesUrl } from "@tanstack/router-core";

}
let script = `import('${startManifest.clientEntry}')`;
let injectedHeadScripts;
if (process.env.TSS_DEV_SERVER === "true") {
const { injectedHeadScripts } = await import("tanstack-start-injected-head-scripts:v");
if (injectedHeadScripts) {
script = `${injectedHeadScripts + ";"}${script}`;
const mod = await import("tanstack-start-injected-head-scripts:v");
if (mod.injectedHeadScripts) {
injectedHeadScripts = mod.injectedHeadScripts;
}
}
rootRoute.assets.push({
tag: "script",
attrs: {
type: "module",
async: true
},
children: script
});
const manifest = {

@@ -55,3 +47,7 @@ routes: Object.fromEntries(

};
return manifest;
return {
manifest,
clientEntry: startManifest.clientEntry,
injectedHeadScripts
};
}

@@ -58,0 +54,0 @@ export {

@@ -1,1 +0,1 @@

{"version":3,"file":"router-manifest.js","sources":["../../src/router-manifest.ts"],"sourcesContent":["import { buildDevStylesUrl, rootRouteId } from '@tanstack/router-core'\nimport type { AnyRoute, RouterManagedTag } from '@tanstack/router-core'\n\n// Pre-computed constant for dev styles URL\nconst ROUTER_BASEPATH = process.env.TSS_ROUTER_BASEPATH || '/'\n\n/**\n * @description Returns the router manifest that should be sent to the client.\n * This includes only the assets and preloads for the current route and any\n * special assets that are needed for the client. It does not include relationships\n * between routes or any other data that is not needed for the client.\n *\n * @param matchedRoutes - In dev mode, the matched routes are used to build\n * the dev styles URL for route-scoped CSS collection.\n */\nexport async function getStartManifest(\n matchedRoutes?: ReadonlyArray<AnyRoute>,\n) {\n const { tsrStartManifest } = await import('tanstack-start-manifest:v')\n const startManifest = tsrStartManifest()\n\n const rootRoute = (startManifest.routes[rootRouteId] =\n startManifest.routes[rootRouteId] || {})\n\n rootRoute.assets = rootRoute.assets || []\n\n // Inject dev styles link in dev mode\n if (process.env.TSS_DEV_SERVER === 'true' && matchedRoutes) {\n const matchedRouteIds = matchedRoutes.map((route) => route.id)\n rootRoute.assets.push({\n tag: 'link',\n attrs: {\n rel: 'stylesheet',\n href: buildDevStylesUrl(ROUTER_BASEPATH, matchedRouteIds),\n 'data-tanstack-router-dev-styles': 'true',\n },\n })\n }\n\n let script = `import('${startManifest.clientEntry}')`\n if (process.env.TSS_DEV_SERVER === 'true') {\n const { injectedHeadScripts } =\n await import('tanstack-start-injected-head-scripts:v')\n if (injectedHeadScripts) {\n script = `${injectedHeadScripts + ';'}${script}`\n }\n }\n rootRoute.assets.push({\n tag: 'script',\n attrs: {\n type: 'module',\n async: true,\n },\n children: script,\n })\n\n const manifest = {\n routes: Object.fromEntries(\n Object.entries(startManifest.routes).flatMap(([k, v]) => {\n const result = {} as {\n preloads?: Array<string>\n assets?: Array<RouterManagedTag>\n }\n let hasData = false\n if (v.preloads && v.preloads.length > 0) {\n result['preloads'] = v.preloads\n hasData = true\n }\n if (v.assets && v.assets.length > 0) {\n result['assets'] = v.assets\n hasData = true\n }\n if (!hasData) {\n return []\n }\n return [[k, result]]\n }),\n ),\n }\n\n // Strip out anything that isn't needed for the client\n return manifest\n}\n"],"names":[],"mappings":";AAIA,MAAM,kBAAkB,QAAQ,IAAI,uBAAuB;AAW3D,eAAsB,iBACpB,eACA;AACA,QAAM,EAAE,iBAAA,IAAqB,MAAM,OAAO,2BAA2B;AACrE,QAAM,gBAAgB,iBAAA;AAEtB,QAAM,YAAa,cAAc,OAAO,WAAW,IACjD,cAAc,OAAO,WAAW,KAAK,CAAA;AAEvC,YAAU,SAAS,UAAU,UAAU,CAAA;AAGvC,MAAI,QAAQ,IAAI,mBAAmB,UAAU,eAAe;AAC1D,UAAM,kBAAkB,cAAc,IAAI,CAAC,UAAU,MAAM,EAAE;AAC7D,cAAU,OAAO,KAAK;AAAA,MACpB,KAAK;AAAA,MACL,OAAO;AAAA,QACL,KAAK;AAAA,QACL,MAAM,kBAAkB,iBAAiB,eAAe;AAAA,QACxD,mCAAmC;AAAA,MAAA;AAAA,IACrC,CACD;AAAA,EACH;AAEA,MAAI,SAAS,WAAW,cAAc,WAAW;AACjD,MAAI,QAAQ,IAAI,mBAAmB,QAAQ;AACzC,UAAM,EAAE,oBAAA,IACN,MAAM,OAAO,wCAAwC;AACvD,QAAI,qBAAqB;AACvB,eAAS,GAAG,sBAAsB,GAAG,GAAG,MAAM;AAAA,IAChD;AAAA,EACF;AACA,YAAU,OAAO,KAAK;AAAA,IACpB,KAAK;AAAA,IACL,OAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,IAAA;AAAA,IAET,UAAU;AAAA,EAAA,CACX;AAED,QAAM,WAAW;AAAA,IACf,QAAQ,OAAO;AAAA,MACb,OAAO,QAAQ,cAAc,MAAM,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,MAAM;AACvD,cAAM,SAAS,CAAA;AAIf,YAAI,UAAU;AACd,YAAI,EAAE,YAAY,EAAE,SAAS,SAAS,GAAG;AACvC,iBAAO,UAAU,IAAI,EAAE;AACvB,oBAAU;AAAA,QACZ;AACA,YAAI,EAAE,UAAU,EAAE,OAAO,SAAS,GAAG;AACnC,iBAAO,QAAQ,IAAI,EAAE;AACrB,oBAAU;AAAA,QACZ;AACA,YAAI,CAAC,SAAS;AACZ,iBAAO,CAAA;AAAA,QACT;AACA,eAAO,CAAC,CAAC,GAAG,MAAM,CAAC;AAAA,MACrB,CAAC;AAAA,IAAA;AAAA,EACH;AAIF,SAAO;AACT;"}
{"version":3,"file":"router-manifest.js","sources":["../../src/router-manifest.ts"],"sourcesContent":["import { buildDevStylesUrl, rootRouteId } from '@tanstack/router-core'\nimport type { AnyRoute, RouterManagedTag } from '@tanstack/router-core'\nimport type { StartManifestWithClientEntry } from './transformAssetUrls'\n\n// Pre-computed constant for dev styles URL\nconst ROUTER_BASEPATH = process.env.TSS_ROUTER_BASEPATH || '/'\n\n/**\n * @description Returns the router manifest data that should be sent to the client.\n * This includes only the assets and preloads for the current route and any\n * special assets that are needed for the client. It does not include relationships\n * between routes or any other data that is not needed for the client.\n *\n * The client entry URL is returned separately so that it can be transformed\n * (e.g. for CDN rewriting) before being embedded into the `<script>` tag.\n *\n * @param matchedRoutes - In dev mode, the matched routes are used to build\n * the dev styles URL for route-scoped CSS collection.\n */\nexport async function getStartManifest(\n matchedRoutes?: ReadonlyArray<AnyRoute>,\n): Promise<StartManifestWithClientEntry> {\n const { tsrStartManifest } = await import('tanstack-start-manifest:v')\n const startManifest = tsrStartManifest()\n\n const rootRoute = (startManifest.routes[rootRouteId] =\n startManifest.routes[rootRouteId] || {})\n\n rootRoute.assets = rootRoute.assets || []\n\n // Inject dev styles link in dev mode\n if (process.env.TSS_DEV_SERVER === 'true' && matchedRoutes) {\n const matchedRouteIds = matchedRoutes.map((route) => route.id)\n rootRoute.assets.push({\n tag: 'link',\n attrs: {\n rel: 'stylesheet',\n href: buildDevStylesUrl(ROUTER_BASEPATH, matchedRouteIds),\n 'data-tanstack-router-dev-styles': 'true',\n },\n })\n }\n\n // Collect injected head scripts in dev mode (returned separately so we can\n // build the client entry script tag after URL transforms are applied)\n let injectedHeadScripts: string | undefined\n if (process.env.TSS_DEV_SERVER === 'true') {\n const mod = await import('tanstack-start-injected-head-scripts:v')\n if (mod.injectedHeadScripts) {\n injectedHeadScripts = mod.injectedHeadScripts\n }\n }\n\n const manifest = {\n routes: Object.fromEntries(\n Object.entries(startManifest.routes).flatMap(([k, v]) => {\n const result = {} as {\n preloads?: Array<string>\n assets?: Array<RouterManagedTag>\n }\n let hasData = false\n if (v.preloads && v.preloads.length > 0) {\n result['preloads'] = v.preloads\n hasData = true\n }\n if (v.assets && v.assets.length > 0) {\n result['assets'] = v.assets\n hasData = true\n }\n if (!hasData) {\n return []\n }\n return [[k, result]]\n }),\n ),\n }\n\n return {\n manifest,\n clientEntry: startManifest.clientEntry,\n injectedHeadScripts,\n }\n}\n"],"names":[],"mappings":";AAKA,MAAM,kBAAkB,QAAQ,IAAI,uBAAuB;AAc3D,eAAsB,iBACpB,eACuC;AACvC,QAAM,EAAE,iBAAA,IAAqB,MAAM,OAAO,2BAA2B;AACrE,QAAM,gBAAgB,iBAAA;AAEtB,QAAM,YAAa,cAAc,OAAO,WAAW,IACjD,cAAc,OAAO,WAAW,KAAK,CAAA;AAEvC,YAAU,SAAS,UAAU,UAAU,CAAA;AAGvC,MAAI,QAAQ,IAAI,mBAAmB,UAAU,eAAe;AAC1D,UAAM,kBAAkB,cAAc,IAAI,CAAC,UAAU,MAAM,EAAE;AAC7D,cAAU,OAAO,KAAK;AAAA,MACpB,KAAK;AAAA,MACL,OAAO;AAAA,QACL,KAAK;AAAA,QACL,MAAM,kBAAkB,iBAAiB,eAAe;AAAA,QACxD,mCAAmC;AAAA,MAAA;AAAA,IACrC,CACD;AAAA,EACH;AAIA,MAAI;AACJ,MAAI,QAAQ,IAAI,mBAAmB,QAAQ;AACzC,UAAM,MAAM,MAAM,OAAO,wCAAwC;AACjE,QAAI,IAAI,qBAAqB;AAC3B,4BAAsB,IAAI;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,WAAW;AAAA,IACf,QAAQ,OAAO;AAAA,MACb,OAAO,QAAQ,cAAc,MAAM,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,MAAM;AACvD,cAAM,SAAS,CAAA;AAIf,YAAI,UAAU;AACd,YAAI,EAAE,YAAY,EAAE,SAAS,SAAS,GAAG;AACvC,iBAAO,UAAU,IAAI,EAAE;AACvB,oBAAU;AAAA,QACZ;AACA,YAAI,EAAE,UAAU,EAAE,OAAO,SAAS,GAAG;AACnC,iBAAO,QAAQ,IAAI,EAAE;AACrB,oBAAU;AAAA,QACZ;AACA,YAAI,CAAC,SAAS;AACZ,iBAAO,CAAA;AAAA,QACT;AACA,eAAO,CAAC,CAAC,GAAG,MAAM,CAAC;AAAA,MACrB,CAAC;AAAA,IAAA;AAAA,EACH;AAGF,SAAO;AAAA,IACL;AAAA,IACA,aAAa,cAAc;AAAA,IAC3B;AAAA,EAAA;AAEJ;"}
{
"name": "@tanstack/start-server-core",
"version": "1.158.4",
"version": "1.159.0",
"description": "Modern and scalable routing for React applications",

@@ -5,0 +5,0 @@ "author": "Tanner Linsley",

@@ -22,2 +22,7 @@ import { createMemoryHistory } from '@tanstack/history'

import { handleServerAction } from './server-functions-handler'
import {
buildManifestWithClientEntry,
resolveTransformConfig,
transformManifestUrls,
} from './transformAssetUrls'

@@ -43,2 +48,7 @@ import { HEADERS } from './constants'

import type { HandlerCallback } from '@tanstack/router-core/ssr/server'
import type {
StartManifestWithClientEntry,
TransformAssetUrls,
TransformAssetUrlsFn,
} from './transformAssetUrls'

@@ -51,2 +61,57 @@ type TODO = any

export interface CreateStartHandlerOptions {
handler: HandlerCallback<AnyRouter>
/**
* Transform asset URLs at runtime, e.g. to prepend a CDN prefix.
*
* **String** — a URL prefix prepended to every asset URL (cached by default):
* ```ts
* createStartHandler({
* handler: defaultStreamHandler,
* transformAssetUrls: 'https://cdn.example.com',
* })
* ```
*
* **Callback** — receives `{ url, type }` and returns a new URL
* (cached by default — runs once on first request):
* ```ts
* createStartHandler({
* handler: defaultStreamHandler,
* transformAssetUrls: ({ url, type }) => {
* return `https://cdn.example.com${url}`
* },
* })
* ```
*
* **Object** — for explicit cache control:
* ```ts
* createStartHandler({
* handler: defaultStreamHandler,
* transformAssetUrls: {
* transform: ({ url }) => {
* const region = getRequest().headers.get('x-region') || 'us'
* return `https://cdn-${region}.example.com${url}`
* },
* cache: false, // transform per-request
* },
* })
* ```
*
* `type` is one of `'modulepreload' | 'stylesheet' | 'clientEntry'`.
*
* By default, the transformed manifest is cached after the first request
* (`cache: true`). Set `cache: false` for per-request transforms.
*
* If you're using a cached transform, you can optionally set `warmup: true`
* (object form only) to compute the transformed manifest in the background at
* server startup.
*
* Note: This only transforms URLs managed by TanStack Start's manifest
* (JS preloads, CSS links, and the client entry script). For asset imports
* used directly in components (e.g. `import logo from './logo.svg'`),
* configure Vite's `experimental.renderBuiltUrl` in your vite.config.ts.
*/
transformAssetUrls?: TransformAssetUrls
}
function getStartResponseHeaders(opts: { router: AnyRouter }) {

@@ -72,4 +137,10 @@ const headers = mergeHeaders(

| undefined
let manifestPromise: Promise<Manifest> | undefined
let baseManifestPromise: Promise<StartManifestWithClientEntry> | undefined
/**
* Cached final manifest (with client entry script tag). In production,
* this is computed once and reused for every request when caching is enabled.
*/
let cachedFinalManifestPromise: Promise<Manifest> | undefined
async function loadEntries() {

@@ -90,3 +161,9 @@ // @ts-ignore when building, we currently don't respect tsconfig.ts' `include` so we are not picking up the .d.ts from start-client-core

function getManifest(matchedRoutes?: ReadonlyArray<AnyRoute>) {
/**
* Returns the raw manifest data (without client entry script tag baked in).
* In dev mode, always returns fresh data. In prod, cached.
*/
function getBaseManifest(
matchedRoutes?: ReadonlyArray<AnyRoute>,
): Promise<StartManifestWithClientEntry> {
// In dev mode, always get fresh manifest (no caching) to include route-specific dev styles

@@ -96,9 +173,46 @@ if (process.env.TSS_DEV_SERVER === 'true') {

}
// In prod, cache the manifest
if (!manifestPromise) {
manifestPromise = getStartManifest()
// In prod, cache the base manifest
if (!baseManifestPromise) {
baseManifestPromise = getStartManifest()
}
return manifestPromise
return baseManifestPromise
}
/**
* Resolves a final Manifest for a given request.
*
* - No transform: builds client entry script tag and returns (cached in prod).
* - Cached transform: transforms all URLs + builds script tag, caches result.
* - Per-request transform: deep-clones base manifest, transforms per-request.
*/
async function resolveManifest(
matchedRoutes: ReadonlyArray<AnyRoute> | undefined,
transformFn: TransformAssetUrlsFn | undefined,
cache: boolean,
): Promise<Manifest> {
const base = await getBaseManifest(matchedRoutes)
const computeFinalManifest = async () => {
return transformFn
? await transformManifestUrls(base, transformFn, { clone: !cache })
: buildManifestWithClientEntry(base)
}
// In dev, always compute fresh to include route-specific dev styles.
if (process.env.TSS_DEV_SERVER === 'true') {
return computeFinalManifest()
}
// In prod, cache unless we're explicitly doing per-request transforms.
if (!transformFn || cache) {
if (!cachedFinalManifestPromise) {
cachedFinalManifestPromise = computeFinalManifest()
}
return cachedFinalManifestPromise
}
// Per-request transform — deep-clone and transform every time.
return computeFinalManifest()
}
// Pre-computed constants

@@ -215,5 +329,103 @@ const ROUTER_BASEPATH = process.env.TSS_ROUTER_BASEPATH || '/'

/**
* Creates the TanStack Start request handler.
*
* @example Backwards-compatible usage (handler callback only):
* ```ts
* export default createStartHandler(defaultStreamHandler)
* ```
*
* @example With CDN URL rewriting:
* ```ts
* export default createStartHandler({
* handler: defaultStreamHandler,
* transformAssetUrls: 'https://cdn.example.com',
* })
* ```
*
* @example With per-request URL rewriting:
* ```ts
* export default createStartHandler({
* handler: defaultStreamHandler,
* transformAssetUrls: {
* transform: ({ url }) => {
* const cdnBase = getRequest().headers.get('x-cdn-base') || ''
* return `${cdnBase}${url}`
* },
* cache: false,
* },
* })
* ```
*/
export function createStartHandler<TRegister = Register>(
cb: HandlerCallback<AnyRouter>,
cbOrOptions: HandlerCallback<AnyRouter> | CreateStartHandlerOptions,
): RequestHandler<TRegister> {
// Normalize the overloaded argument
const cb: HandlerCallback<AnyRouter> =
typeof cbOrOptions === 'function' ? cbOrOptions : cbOrOptions.handler
const transformAssetUrlsOption: TransformAssetUrls | undefined =
typeof cbOrOptions === 'function'
? undefined
: cbOrOptions.transformAssetUrls
const warmupTransformManifest =
!!transformAssetUrlsOption &&
typeof transformAssetUrlsOption === 'object' &&
transformAssetUrlsOption.warmup === true
// Pre-resolve the transform function and cache flag
const resolvedTransformConfig = transformAssetUrlsOption
? resolveTransformConfig(transformAssetUrlsOption)
: undefined
const cache = resolvedTransformConfig ? resolvedTransformConfig.cache : true
// Memoize a single createTransform() result when caching is enabled.
let cachedCreateTransformPromise: Promise<TransformAssetUrlsFn> | undefined
const getTransformFn = async (
opts: { warmup: true } | { warmup: false; request: Request },
): Promise<TransformAssetUrlsFn | undefined> => {
if (!resolvedTransformConfig) return undefined
if (resolvedTransformConfig.type === 'createTransform') {
if (cache) {
if (!cachedCreateTransformPromise) {
cachedCreateTransformPromise = Promise.resolve(
resolvedTransformConfig.createTransform(opts),
)
}
return cachedCreateTransformPromise
}
return resolvedTransformConfig.createTransform(opts)
}
return resolvedTransformConfig.transformFn
}
// Background warmup for cached transforms (production only)
if (
warmupTransformManifest &&
cache &&
process.env.TSS_DEV_SERVER !== 'true' &&
!cachedFinalManifestPromise
) {
// NOTE: Do not call resolveManifest() here.
// resolveManifest() reads from cachedFinalManifestPromise, and since we set
// cachedFinalManifestPromise to this warmup promise, that would create a
// self-referential promise and hang forever.
const warmupPromise = (async () => {
const base = await getBaseManifest(undefined)
const transformFn = await getTransformFn({ warmup: true })
return transformFn
? await transformManifestUrls(base, transformFn, { clone: false })
: buildManifestWithClientEntry(base)
})()
cachedFinalManifestPromise = warmupPromise
warmupPromise.catch(() => {
// If warmup fails, allow the next request to retry.
if (cachedFinalManifestPromise === warmupPromise) {
cachedFinalManifestPromise = undefined
}
cachedCreateTransformPromise = undefined
})
}
const startRequestResolver: RequestHandler<Register> = async (

@@ -355,3 +567,7 @@ request,

const manifest = await getManifest(matchedRoutes)
const manifest = await resolveManifest(
matchedRoutes,
await getTransformFn({ warmup: false, request }),
cache,
)
const routerInstance = await getRouter()

@@ -358,0 +574,0 @@

export { createStartHandler } from './createStartHandler'
export type { CreateStartHandlerOptions } from './createStartHandler'
export type {
TransformAssetUrls,
TransformAssetUrlsFn,
TransformAssetUrlsContext,
TransformAssetUrlsOptions,
AssetUrlType,
} from './transformAssetUrls'
export {

@@ -4,0 +13,0 @@ attachRouterServerSsrUtils,

import { buildDevStylesUrl, rootRouteId } from '@tanstack/router-core'
import type { AnyRoute, RouterManagedTag } from '@tanstack/router-core'
import type { StartManifestWithClientEntry } from './transformAssetUrls'

@@ -8,3 +9,3 @@ // Pre-computed constant for dev styles URL

/**
* @description Returns the router manifest that should be sent to the client.
* @description Returns the router manifest data that should be sent to the client.
* This includes only the assets and preloads for the current route and any

@@ -14,2 +15,5 @@ * special assets that are needed for the client. It does not include relationships

*
* The client entry URL is returned separately so that it can be transformed
* (e.g. for CDN rewriting) before being embedded into the `<script>` tag.
*
* @param matchedRoutes - In dev mode, the matched routes are used to build

@@ -20,3 +24,3 @@ * the dev styles URL for route-scoped CSS collection.

matchedRoutes?: ReadonlyArray<AnyRoute>,
) {
): Promise<StartManifestWithClientEntry> {
const { tsrStartManifest } = await import('tanstack-start-manifest:v')

@@ -43,18 +47,11 @@ const startManifest = tsrStartManifest()

let script = `import('${startManifest.clientEntry}')`
// Collect injected head scripts in dev mode (returned separately so we can
// build the client entry script tag after URL transforms are applied)
let injectedHeadScripts: string | undefined
if (process.env.TSS_DEV_SERVER === 'true') {
const { injectedHeadScripts } =
await import('tanstack-start-injected-head-scripts:v')
if (injectedHeadScripts) {
script = `${injectedHeadScripts + ';'}${script}`
const mod = await import('tanstack-start-injected-head-scripts:v')
if (mod.injectedHeadScripts) {
injectedHeadScripts = mod.injectedHeadScripts
}
}
rootRoute.assets.push({
tag: 'script',
attrs: {
type: 'module',
async: true,
},
children: script,
})

@@ -85,4 +82,7 @@ const manifest = {

// Strip out anything that isn't needed for the client
return manifest
return {
manifest,
clientEntry: startManifest.clientEntry,
injectedHeadScripts,
}
}