@tanstack/start-server-core 1.167.27 → 1.167.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { requestHandler } from "./request-response.js";
2
2
  import { getStartManifest } from "./router-manifest.js";
3
3
  import { handleServerAction } from "./server-functions-handler.js";
4
4
  import { adaptTransformAssetUrlsConfigToTransformAssets, buildManifestWithClientEntry, resolveTransformAssetsConfig, transformManifestAssets } from "./transformAssetUrls.js";
5
- import { collectDynamicHintsFromMatches, collectStaticHintsFromManifest, createEarlyHintsEvent } from "./early-hints.js";
5
+ import { collectDynamicHintsFromMatches, collectStaticHintsFromManifest, createEarlyHintsEvent, createResponseLinkHeaderEntries, getResponseLinkHeaderEntries } from "./early-hints.js";
6
6
  import { HEADERS } from "./constants.js";
7
7
  import { ServerFunctionSerializationAdapter } from "./serializer/ServerFunctionSerializationAdapter.js";
8
8
  import { createMemoryHistory } from "@tanstack/history";
@@ -16,18 +16,59 @@ function getStartResponseHeaders(opts) {
16
16
  return match.headers;
17
17
  }));
18
18
  }
19
- function sendEarlyHintsEvent(opts) {
20
- const event = createEarlyHintsEvent(opts);
21
- if (!event) return;
19
+ function notifyEarlyHints(phase, event, onEarlyHints) {
22
20
  try {
23
- const result = opts.onEarlyHints(event);
21
+ const result = onEarlyHints(event);
24
22
  if (result) Promise.resolve(result).catch((err) => {
25
- console.error(`Error sending ${opts.phase} early hints:`, err);
23
+ console.error(`Error sending ${phase} early hints:`, err);
26
24
  });
27
25
  } catch (err) {
28
- console.error(`Error sending ${opts.phase} early hints:`, err);
26
+ console.error(`Error sending ${phase} early hints:`, err);
29
27
  }
30
28
  }
29
+ function getResponseLinkHeaderFilter(responseLinkHeader) {
30
+ if (typeof responseLinkHeader !== "object") return;
31
+ return responseLinkHeader.filter;
32
+ }
33
+ function appendResponseLinkHeaders(opts) {
34
+ if (!opts.filter) {
35
+ for (const entry of opts.entries) opts.responseHeaders.append("Link", entry.link);
36
+ return;
37
+ }
38
+ const links = getResponseLinkHeaderEntries(opts);
39
+ for (const link of links) opts.responseHeaders.append("Link", link);
40
+ }
41
+ function collectResponseLinkHeaderEntries(opts) {
42
+ for (let index = 0; index < opts.event.hints.length; index++) opts.entries.push({
43
+ phase: opts.phase,
44
+ hint: opts.event.hints[index],
45
+ link: opts.event.links[index]
46
+ });
47
+ }
48
+ function handleCollectedEarlyHints(opts) {
49
+ const event = opts.onEarlyHints ? createEarlyHintsEvent({
50
+ phase: opts.phase,
51
+ hints: opts.hints,
52
+ sentLinks: opts.sentLinks,
53
+ sentHints: opts.sentHints
54
+ }) : void 0;
55
+ if (event) notifyEarlyHints(opts.phase, event, opts.onEarlyHints);
56
+ if (!opts.responseLinkHeaderEntries) return;
57
+ if (event) {
58
+ collectResponseLinkHeaderEntries({
59
+ phase: opts.phase,
60
+ event,
61
+ entries: opts.responseLinkHeaderEntries
62
+ });
63
+ return;
64
+ }
65
+ createResponseLinkHeaderEntries({
66
+ phase: opts.phase,
67
+ hints: opts.hints,
68
+ sentLinks: opts.sentLinks,
69
+ entries: opts.responseLinkHeaderEntries
70
+ });
71
+ }
31
72
  var entriesPromise;
32
73
  var baseManifestPromise;
33
74
  /**
@@ -289,15 +330,20 @@ function createStartHandler(cbOrOptions) {
289
330
  warmup: false,
290
331
  request
291
332
  }), cache);
292
- const onEarlyHints = process.env.TSS_DEV_SERVER === "true" ? void 0 : requestOpts?.onEarlyHints;
293
- const earlyHintsSentLinks = onEarlyHints ? /* @__PURE__ */ new Set() : void 0;
294
- const earlyHintsSentHints = onEarlyHints ? new Array() : void 0;
295
- if (onEarlyHints && earlyHintsSentLinks && earlyHintsSentHints && matchedRoutes?.length) sendEarlyHintsEvent({
333
+ const onEarlyHints = requestOpts?.onEarlyHints;
334
+ const responseLinkHeader = requestOpts?.responseLinkHeader;
335
+ const shouldCollectEarlyHints = process.env.TSS_DEV_SERVER !== "true" && (!!onEarlyHints || !!responseLinkHeader);
336
+ const sentEarlyHintLinks = shouldCollectEarlyHints ? /* @__PURE__ */ new Set() : void 0;
337
+ const sentEarlyHints = onEarlyHints ? new Array() : void 0;
338
+ const responseLinkHeaderEntries = shouldCollectEarlyHints && responseLinkHeader ? new Array() : void 0;
339
+ const responseLinkHeaderFilter = shouldCollectEarlyHints ? getResponseLinkHeaderFilter(responseLinkHeader) : void 0;
340
+ if (shouldCollectEarlyHints && sentEarlyHintLinks && matchedRoutes?.length) handleCollectedEarlyHints({
296
341
  phase: "static",
297
342
  hints: collectStaticHintsFromManifest(manifest, matchedRoutes),
343
+ sentLinks: sentEarlyHintLinks,
344
+ sentHints: sentEarlyHints,
298
345
  onEarlyHints,
299
- sentLinks: earlyHintsSentLinks,
300
- sentHints: earlyHintsSentHints
346
+ responseLinkHeaderEntries
301
347
  });
302
348
  const routerInstance = await getRouter();
303
349
  attachRouterServerSsrUtils({
@@ -309,16 +355,22 @@ function createStartHandler(cbOrOptions) {
309
355
  routerInstance.update({ additionalContext: { serverContext } });
310
356
  await routerInstance.load();
311
357
  if (routerInstance.state.redirect) return routerInstance.state.redirect;
312
- if (onEarlyHints && earlyHintsSentLinks && earlyHintsSentHints) sendEarlyHintsEvent({
358
+ if (shouldCollectEarlyHints && sentEarlyHintLinks) handleCollectedEarlyHints({
313
359
  phase: "dynamic",
314
360
  hints: collectDynamicHintsFromMatches(routerInstance.stores.matches.get()),
361
+ sentLinks: sentEarlyHintLinks,
362
+ sentHints: sentEarlyHints,
315
363
  onEarlyHints,
316
- sentLinks: earlyHintsSentLinks,
317
- sentHints: earlyHintsSentHints
364
+ responseLinkHeaderEntries
318
365
  });
319
366
  const ctx = getStartContext({ throwIfNotFound: false });
320
367
  await routerInstance.serverSsr.dehydrate({ requestAssets: ctx?.requestAssets });
321
368
  const responseHeaders = getStartResponseHeaders({ router: routerInstance });
369
+ if (responseLinkHeaderEntries?.length) appendResponseLinkHeaders({
370
+ responseHeaders,
371
+ entries: responseLinkHeaderEntries,
372
+ filter: responseLinkHeaderFilter
373
+ });
322
374
  cbWillCleanup = true;
323
375
  return cb({
324
376
  request,
@@ -1 +1 @@
1
- {"version":3,"file":"createStartHandler.js","names":[],"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 {\n getStartContext,\n runWithStartContext,\n} from '@tanstack/start-storage-context'\nimport { requestHandler } from './request-response'\nimport { getStartManifest } from './router-manifest'\nimport { handleServerAction } from './server-functions-handler'\nimport {\n adaptTransformAssetUrlsConfigToTransformAssets,\n buildManifestWithClientEntry,\n resolveTransformAssetsConfig,\n transformManifestAssets,\n} from './transformAssetUrls'\nimport {\n collectDynamicHintsFromMatches,\n collectStaticHintsFromManifest,\n createEarlyHintsEvent,\n} from './early-hints'\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 { EarlyHint, EarlyHintsPhase, OnEarlyHints } from './early-hints'\nimport type {\n AnyRoute,\n AnyRouter,\n AnySerializationAdapter,\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 TransformAssets,\n TransformAssetsFn,\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 and attributes 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 * transformAssets: 'https://cdn.example.com',\n * })\n * ```\n *\n * **Object shorthand** — a URL prefix with optional `crossOrigin`:\n * ```ts\n * createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssets: {\n * prefix: 'https://cdn.example.com',\n * crossOrigin: 'anonymous',\n * },\n * })\n * ```\n *\n * `crossOrigin` accepts a single value or a per-kind record:\n * ```ts\n * transformAssets: {\n * prefix: 'https://cdn.example.com',\n * crossOrigin: {\n * modulepreload: 'anonymous',\n * stylesheet: 'use-credentials',\n * },\n * }\n * ```\n *\n * **Callback** — receives `{ kind, url }` and returns either a string URL or\n * `{ href, crossOrigin? }` (cached by default — runs once on first request):\n * ```ts\n * createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssets: ({ kind, url }) => {\n * const href = `https://cdn.example.com${url}`\n *\n * if (kind === 'modulepreload') {\n * return { href, crossOrigin: 'anonymous' }\n * }\n *\n * return { href }\n * },\n * })\n * ```\n *\n * **Object** — for explicit cache control:\n * ```ts\n * createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssets: {\n * transform: ({ url }) => {\n * const region = getRequest().headers.get('x-region') || 'us'\n * return { href: `https://cdn-${region}.example.com${url}` }\n * },\n * cache: false,\n * },\n * })\n * ```\n *\n * `kind` is one of `'modulepreload' | 'stylesheet' | 'clientEntry'`.\n * `crossOrigin` applies to manifest-managed `<link>` assets.\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 transformAssets?: TransformAssets\n /**\n * @deprecated Use `transformAssets` instead.\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.stores.matches.get().map((match) => {\n return match.headers\n }),\n )\n return headers\n}\n\nfunction sendEarlyHintsEvent(opts: {\n phase: EarlyHintsPhase\n hints: ReadonlyArray<EarlyHint>\n onEarlyHints: OnEarlyHints\n sentLinks: Set<string>\n sentHints: Array<EarlyHint>\n}) {\n const event = createEarlyHintsEvent(opts)\n if (!event) return\n\n try {\n const result = opts.onEarlyHints(event)\n if (result) {\n void Promise.resolve(result).catch((err) => {\n console.error(`Error sending ${opts.phase} early hints:`, err)\n })\n }\n } catch (err) {\n console.error(`Error sending ${opts.phase} early hints:`, err)\n }\n}\n\ninterface PluginAdaptersEntry {\n hasPluginAdapters: boolean\n pluginSerializationAdapters: Array<AnySerializationAdapter>\n}\n\ninterface Entries {\n startEntry: StartEntry\n routerEntry: RouterEntry\n pluginAdapters: PluginAdaptersEntry\n}\n\n// Cached entries - promises stored immediately to prevent concurrent imports\n// that can cause race conditions during module initialization\nlet entriesPromise: Promise<Entries> | 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(): Promise<Entries> {\n const [routerEntry, startEntry, pluginAdapters] = await Promise.all([\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 import('#tanstack-router-entry'),\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 import('#tanstack-start-entry'),\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 import('#tanstack-start-plugin-adapters'),\n ])\n return {\n routerEntry: routerEntry as unknown as RouterEntry,\n startEntry: startEntry as unknown as StartEntry,\n pluginAdapters: pluginAdapters as unknown as PluginAdaptersEntry,\n }\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: TransformAssetsFn | undefined,\n cache: boolean,\n): Promise<Manifest> {\n const base = await getBaseManifest(matchedRoutes)\n\n const computeFinalManifest = async () => {\n return transformFn\n ? await transformManifestAssets(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 * transformAssets: 'https://cdn.example.com',\n * })\n * ```\n *\n * @example With per-request URL rewriting:\n * ```ts\n * export default createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssets: {\n * transform: ({ url }) => {\n * const cdnBase = getRequest().headers.get('x-cdn-base') || ''\n * return { href: `${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 transformAssetsOption: TransformAssets | undefined =\n typeof cbOrOptions === 'function' ? undefined : cbOrOptions.transformAssets\n const transformAssetUrlsOption: TransformAssetUrls | undefined =\n typeof cbOrOptions === 'function'\n ? undefined\n : cbOrOptions.transformAssetUrls\n\n const transformOption =\n transformAssetsOption !== undefined\n ? resolveTransformAssetsConfig(transformAssetsOption)\n : transformAssetUrlsOption !== undefined\n ? resolveTransformAssetsConfig(\n adaptTransformAssetUrlsConfigToTransformAssets(\n transformAssetUrlsOption,\n ),\n )\n : undefined\n\n const warmupTransformManifest =\n (!!transformAssetsOption &&\n typeof transformAssetsOption === 'object' &&\n 'warmup' in transformAssetsOption &&\n transformAssetsOption.warmup === true) ||\n (!!transformAssetUrlsOption &&\n typeof transformAssetUrlsOption === 'object' &&\n transformAssetUrlsOption.warmup === true)\n\n // Pre-resolve the transform function and cache flag\n const resolvedTransformConfig = transformOption\n const cache = resolvedTransformConfig ? resolvedTransformConfig.cache : true\n const shouldCacheCreateTransform =\n cache && process.env.TSS_DEV_SERVER !== 'true'\n\n // Memoize a single createTransform() result when caching is enabled outside\n // of the dev server.\n let cachedCreateTransformPromise: Promise<TransformAssetsFn> | undefined\n\n const getTransformFn = async (\n opts: { warmup: true } | { warmup: false; request: Request },\n ): Promise<TransformAssetsFn | undefined> => {\n if (!resolvedTransformConfig) return undefined\n\n if (resolvedTransformConfig.type === 'createTransform') {\n if (shouldCacheCreateTransform) {\n if (!cachedCreateTransformPromise) {\n cachedCreateTransformPromise = Promise.resolve(\n resolvedTransformConfig.createTransform(opts),\n ).catch((error) => {\n cachedCreateTransformPromise = undefined\n throw error\n })\n }\n\n return cachedCreateTransformPromise\n }\n\n return resolvedTransformConfig.createTransform(opts)\n }\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 transformManifestAssets(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 { hasPluginAdapters, pluginSerializationAdapters } =\n entries.pluginAdapters\n\n const serializationAdapters = [\n ...(startOptions.serializationAdapters || []),\n ...(hasPluginAdapters ? pluginSerializationAdapters : []),\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 handlerType: 'serverFn',\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 pathname: url.pathname,\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\n const onEarlyHints =\n process.env.TSS_DEV_SERVER === 'true'\n ? undefined\n : requestOpts?.onEarlyHints\n const earlyHintsSentLinks = onEarlyHints ? new Set<string>() : undefined\n const earlyHintsSentHints = onEarlyHints\n ? new Array<EarlyHint>()\n : undefined\n\n if (\n onEarlyHints &&\n earlyHintsSentLinks &&\n earlyHintsSentHints &&\n matchedRoutes?.length\n ) {\n sendEarlyHintsEvent({\n phase: 'static',\n hints: collectStaticHintsFromManifest(manifest, matchedRoutes),\n onEarlyHints,\n sentLinks: earlyHintsSentLinks,\n sentHints: earlyHintsSentHints,\n })\n }\n\n const routerInstance = await getRouter()\n\n attachRouterServerSsrUtils({\n router: routerInstance,\n manifest,\n getRequestAssets: () =>\n getStartContext({ throwIfNotFound: false })?.requestAssets,\n includeUnmatchedRouteAssets: false,\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 if (onEarlyHints && earlyHintsSentLinks && earlyHintsSentHints) {\n const loadedMatches = routerInstance.stores.matches.get()\n sendEarlyHintsEvent({\n phase: 'dynamic',\n hints: collectDynamicHintsFromMatches(loadedMatches),\n onEarlyHints,\n sentLinks: earlyHintsSentLinks,\n sentHints: earlyHintsSentHints,\n })\n }\n\n // Pass request-scoped assets to dehydrate for manifest injection\n const ctx = getStartContext({ throwIfNotFound: false })\n await routerInstance.serverSsr!.dehydrate({\n requestAssets: ctx?.requestAssets,\n })\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 handlerType: 'router',\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 {\n request,\n pathname: url.pathname,\n context: createNullProtoObject(requestOpts?.context),\n },\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"],"mappings":"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"}
1
+ {"version":3,"file":"createStartHandler.js","names":[],"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 {\n getStartContext,\n runWithStartContext,\n} from '@tanstack/start-storage-context'\nimport { requestHandler } from './request-response'\nimport { getStartManifest } from './router-manifest'\nimport { handleServerAction } from './server-functions-handler'\nimport {\n adaptTransformAssetUrlsConfigToTransformAssets,\n buildManifestWithClientEntry,\n resolveTransformAssetsConfig,\n transformManifestAssets,\n} from './transformAssetUrls'\nimport {\n collectDynamicHintsFromMatches,\n collectStaticHintsFromManifest,\n createEarlyHintsEvent,\n createResponseLinkHeaderEntries,\n getResponseLinkHeaderEntries,\n} from './early-hints'\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 EarlyHint,\n EarlyHintsEvent,\n EarlyHintsPhase,\n OnEarlyHints,\n ResponseLinkHeaderEntry,\n ResponseLinkHeaderFilter,\n ResponseLinkHeaderOptions,\n} from './early-hints'\nimport type {\n AnyRoute,\n AnyRouter,\n AnySerializationAdapter,\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 TransformAssets,\n TransformAssetsFn,\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 and attributes 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 * transformAssets: 'https://cdn.example.com',\n * })\n * ```\n *\n * **Object shorthand** — a URL prefix with optional `crossOrigin`:\n * ```ts\n * createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssets: {\n * prefix: 'https://cdn.example.com',\n * crossOrigin: 'anonymous',\n * },\n * })\n * ```\n *\n * `crossOrigin` accepts a single value or a per-kind record:\n * ```ts\n * transformAssets: {\n * prefix: 'https://cdn.example.com',\n * crossOrigin: {\n * modulepreload: 'anonymous',\n * stylesheet: 'use-credentials',\n * },\n * }\n * ```\n *\n * **Callback** — receives `{ kind, url }` and returns either a string URL or\n * `{ href, crossOrigin? }` (cached by default — runs once on first request):\n * ```ts\n * createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssets: ({ kind, url }) => {\n * const href = `https://cdn.example.com${url}`\n *\n * if (kind === 'modulepreload') {\n * return { href, crossOrigin: 'anonymous' }\n * }\n *\n * return { href }\n * },\n * })\n * ```\n *\n * **Object** — for explicit cache control:\n * ```ts\n * createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssets: {\n * transform: ({ url }) => {\n * const region = getRequest().headers.get('x-region') || 'us'\n * return { href: `https://cdn-${region}.example.com${url}` }\n * },\n * cache: false,\n * },\n * })\n * ```\n *\n * `kind` is one of `'modulepreload' | 'stylesheet' | 'clientEntry'`.\n * `crossOrigin` applies to manifest-managed `<link>` assets.\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 transformAssets?: TransformAssets\n /**\n * @deprecated Use `transformAssets` instead.\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.stores.matches.get().map((match) => {\n return match.headers\n }),\n )\n return headers\n}\n\nfunction notifyEarlyHints(\n phase: EarlyHintsPhase,\n event: EarlyHintsEvent,\n onEarlyHints: OnEarlyHints,\n) {\n try {\n const result = onEarlyHints(event)\n if (result) {\n void Promise.resolve(result).catch((err) => {\n console.error(`Error sending ${phase} early hints:`, err)\n })\n }\n } catch (err) {\n console.error(`Error sending ${phase} early hints:`, err)\n }\n}\n\nfunction getResponseLinkHeaderFilter(\n responseLinkHeader: boolean | ResponseLinkHeaderOptions | undefined,\n): ResponseLinkHeaderFilter | undefined {\n if (typeof responseLinkHeader !== 'object') {\n return undefined\n }\n\n return responseLinkHeader.filter\n}\n\nfunction appendResponseLinkHeaders(opts: {\n responseHeaders: Headers\n entries: ReadonlyArray<ResponseLinkHeaderEntry>\n filter?: ResponseLinkHeaderFilter\n}) {\n if (!opts.filter) {\n for (const entry of opts.entries) {\n opts.responseHeaders.append('Link', entry.link)\n }\n return\n }\n\n const links = getResponseLinkHeaderEntries(opts)\n\n for (const link of links) {\n opts.responseHeaders.append('Link', link)\n }\n}\n\nfunction collectResponseLinkHeaderEntries(opts: {\n phase: EarlyHintsPhase\n event: EarlyHintsEvent\n entries: Array<ResponseLinkHeaderEntry>\n}) {\n for (let index = 0; index < opts.event.hints.length; index++) {\n opts.entries.push({\n phase: opts.phase,\n hint: opts.event.hints[index]!,\n link: opts.event.links[index]!,\n })\n }\n}\n\nfunction handleCollectedEarlyHints(opts: {\n phase: EarlyHintsPhase\n hints: ReadonlyArray<EarlyHint>\n sentLinks: Set<string>\n sentHints?: Array<EarlyHint>\n onEarlyHints?: OnEarlyHints\n responseLinkHeaderEntries?: Array<ResponseLinkHeaderEntry>\n}) {\n const event = opts.onEarlyHints\n ? createEarlyHintsEvent({\n phase: opts.phase,\n hints: opts.hints,\n sentLinks: opts.sentLinks,\n sentHints: opts.sentHints!,\n })\n : undefined\n\n if (event) {\n notifyEarlyHints(opts.phase, event, opts.onEarlyHints!)\n }\n\n if (!opts.responseLinkHeaderEntries) return\n\n if (event) {\n collectResponseLinkHeaderEntries({\n phase: opts.phase,\n event,\n entries: opts.responseLinkHeaderEntries,\n })\n return\n }\n\n createResponseLinkHeaderEntries({\n phase: opts.phase,\n hints: opts.hints,\n sentLinks: opts.sentLinks,\n entries: opts.responseLinkHeaderEntries,\n })\n}\n\ninterface PluginAdaptersEntry {\n hasPluginAdapters: boolean\n pluginSerializationAdapters: Array<AnySerializationAdapter>\n}\n\ninterface Entries {\n startEntry: StartEntry\n routerEntry: RouterEntry\n pluginAdapters: PluginAdaptersEntry\n}\n\n// Cached entries - promises stored immediately to prevent concurrent imports\n// that can cause race conditions during module initialization\nlet entriesPromise: Promise<Entries> | 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(): Promise<Entries> {\n const [routerEntry, startEntry, pluginAdapters] = await Promise.all([\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 import('#tanstack-router-entry'),\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 import('#tanstack-start-entry'),\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 import('#tanstack-start-plugin-adapters'),\n ])\n return {\n routerEntry: routerEntry as unknown as RouterEntry,\n startEntry: startEntry as unknown as StartEntry,\n pluginAdapters: pluginAdapters as unknown as PluginAdaptersEntry,\n }\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: TransformAssetsFn | undefined,\n cache: boolean,\n): Promise<Manifest> {\n const base = await getBaseManifest(matchedRoutes)\n\n const computeFinalManifest = async () => {\n return transformFn\n ? await transformManifestAssets(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 * transformAssets: 'https://cdn.example.com',\n * })\n * ```\n *\n * @example With per-request URL rewriting:\n * ```ts\n * export default createStartHandler({\n * handler: defaultStreamHandler,\n * transformAssets: {\n * transform: ({ url }) => {\n * const cdnBase = getRequest().headers.get('x-cdn-base') || ''\n * return { href: `${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 transformAssetsOption: TransformAssets | undefined =\n typeof cbOrOptions === 'function' ? undefined : cbOrOptions.transformAssets\n const transformAssetUrlsOption: TransformAssetUrls | undefined =\n typeof cbOrOptions === 'function'\n ? undefined\n : cbOrOptions.transformAssetUrls\n\n const transformOption =\n transformAssetsOption !== undefined\n ? resolveTransformAssetsConfig(transformAssetsOption)\n : transformAssetUrlsOption !== undefined\n ? resolveTransformAssetsConfig(\n adaptTransformAssetUrlsConfigToTransformAssets(\n transformAssetUrlsOption,\n ),\n )\n : undefined\n\n const warmupTransformManifest =\n (!!transformAssetsOption &&\n typeof transformAssetsOption === 'object' &&\n 'warmup' in transformAssetsOption &&\n transformAssetsOption.warmup === true) ||\n (!!transformAssetUrlsOption &&\n typeof transformAssetUrlsOption === 'object' &&\n transformAssetUrlsOption.warmup === true)\n\n // Pre-resolve the transform function and cache flag\n const resolvedTransformConfig = transformOption\n const cache = resolvedTransformConfig ? resolvedTransformConfig.cache : true\n const shouldCacheCreateTransform =\n cache && process.env.TSS_DEV_SERVER !== 'true'\n\n // Memoize a single createTransform() result when caching is enabled outside\n // of the dev server.\n let cachedCreateTransformPromise: Promise<TransformAssetsFn> | undefined\n\n const getTransformFn = async (\n opts: { warmup: true } | { warmup: false; request: Request },\n ): Promise<TransformAssetsFn | undefined> => {\n if (!resolvedTransformConfig) return undefined\n\n if (resolvedTransformConfig.type === 'createTransform') {\n if (shouldCacheCreateTransform) {\n if (!cachedCreateTransformPromise) {\n cachedCreateTransformPromise = Promise.resolve(\n resolvedTransformConfig.createTransform(opts),\n ).catch((error) => {\n cachedCreateTransformPromise = undefined\n throw error\n })\n }\n\n return cachedCreateTransformPromise\n }\n\n return resolvedTransformConfig.createTransform(opts)\n }\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 transformManifestAssets(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 { hasPluginAdapters, pluginSerializationAdapters } =\n entries.pluginAdapters\n\n const serializationAdapters = [\n ...(startOptions.serializationAdapters || []),\n ...(hasPluginAdapters ? pluginSerializationAdapters : []),\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 handlerType: 'serverFn',\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 pathname: url.pathname,\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\n const onEarlyHints = requestOpts?.onEarlyHints\n const responseLinkHeader = requestOpts?.responseLinkHeader\n const shouldCollectEarlyHints =\n process.env.TSS_DEV_SERVER !== 'true' &&\n (!!onEarlyHints || !!responseLinkHeader)\n const sentEarlyHintLinks = shouldCollectEarlyHints\n ? new Set<string>()\n : undefined\n const sentEarlyHints = onEarlyHints ? new Array<EarlyHint>() : undefined\n const responseLinkHeaderEntries =\n shouldCollectEarlyHints && responseLinkHeader\n ? new Array<ResponseLinkHeaderEntry>()\n : undefined\n const responseLinkHeaderFilter = shouldCollectEarlyHints\n ? getResponseLinkHeaderFilter(responseLinkHeader)\n : undefined\n\n if (\n shouldCollectEarlyHints &&\n sentEarlyHintLinks &&\n matchedRoutes?.length\n ) {\n const hints = collectStaticHintsFromManifest(manifest, matchedRoutes)\n handleCollectedEarlyHints({\n phase: 'static',\n hints,\n sentLinks: sentEarlyHintLinks,\n sentHints: sentEarlyHints,\n onEarlyHints,\n responseLinkHeaderEntries,\n })\n }\n\n const routerInstance = await getRouter()\n\n attachRouterServerSsrUtils({\n router: routerInstance,\n manifest,\n getRequestAssets: () =>\n getStartContext({ throwIfNotFound: false })?.requestAssets,\n includeUnmatchedRouteAssets: false,\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 if (shouldCollectEarlyHints && sentEarlyHintLinks) {\n const loadedMatches = routerInstance.stores.matches.get()\n const hints = collectDynamicHintsFromMatches(loadedMatches)\n handleCollectedEarlyHints({\n phase: 'dynamic',\n hints,\n sentLinks: sentEarlyHintLinks,\n sentHints: sentEarlyHints,\n onEarlyHints,\n responseLinkHeaderEntries,\n })\n }\n\n // Pass request-scoped assets to dehydrate for manifest injection\n const ctx = getStartContext({ throwIfNotFound: false })\n await routerInstance.serverSsr!.dehydrate({\n requestAssets: ctx?.requestAssets,\n })\n\n const responseHeaders = getStartResponseHeaders({\n router: routerInstance,\n })\n if (responseLinkHeaderEntries?.length) {\n appendResponseLinkHeaders({\n responseHeaders,\n entries: responseLinkHeaderEntries,\n filter: responseLinkHeaderFilter,\n })\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 handlerType: 'router',\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 {\n request,\n pathname: url.pathname,\n context: createNullProtoObject(requestOpts?.context),\n },\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"],"mappings":"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"}
@@ -18,6 +18,15 @@ export type EarlyHintsEvent = {
18
18
  allLinks: Array<string>;
19
19
  };
20
20
  export type OnEarlyHints = (event: EarlyHintsEvent) => void | Promise<void>;
21
+ export type ResponseLinkHeaderEntry = {
22
+ phase: EarlyHintsPhase;
23
+ hint: EarlyHint;
24
+ link: string;
25
+ };
26
+ export type ResponseLinkHeaderFilter = (entry: ResponseLinkHeaderEntry) => boolean;
27
+ export type ResponseLinkHeaderOptions = {
28
+ filter?: ResponseLinkHeaderFilter;
29
+ };
21
30
  export declare function serializeEarlyHint(hint: EarlyHint): string;
22
31
  export declare function collectStaticHintsFromManifest(manifest: Manifest, matchedRoutes: ReadonlyArray<AnyRoute>): Array<EarlyHint>;
23
32
  export declare function collectDynamicHintsFromMatches(matches: ReadonlyArray<AnyRouteMatch>): Array<EarlyHint>;
@@ -27,3 +36,13 @@ export declare function createEarlyHintsEvent(opts: {
27
36
  sentLinks: Set<string>;
28
37
  sentHints: Array<EarlyHint>;
29
38
  }): EarlyHintsEvent | undefined;
39
+ export declare function createResponseLinkHeaderEntries(opts: {
40
+ phase: EarlyHintsPhase;
41
+ hints: ReadonlyArray<EarlyHint>;
42
+ sentLinks: Set<string>;
43
+ entries: Array<ResponseLinkHeaderEntry>;
44
+ }): void;
45
+ export declare function getResponseLinkHeaderEntries(opts: {
46
+ entries: ReadonlyArray<ResponseLinkHeaderEntry>;
47
+ filter?: ResponseLinkHeaderFilter;
48
+ }): Array<string>;
@@ -144,7 +144,30 @@ function createEarlyHintsEvent(opts) {
144
144
  allLinks: Array.from(opts.sentLinks)
145
145
  };
146
146
  }
147
+ function createResponseLinkHeaderEntries(opts) {
148
+ for (const hint of opts.hints) {
149
+ const link = serializeEarlyHint(hint);
150
+ if (opts.sentLinks.has(link)) continue;
151
+ opts.sentLinks.add(link);
152
+ opts.entries.push({
153
+ phase: opts.phase,
154
+ hint,
155
+ link
156
+ });
157
+ }
158
+ }
159
+ function getResponseLinkHeaderEntries(opts) {
160
+ if (!opts.filter) return opts.entries.map((entry) => entry.link);
161
+ try {
162
+ const links = [];
163
+ for (const entry of opts.entries) if (opts.filter(entry)) links.push(entry.link);
164
+ return links;
165
+ } catch (err) {
166
+ console.error("Error filtering response Link headers:", err);
167
+ return [];
168
+ }
169
+ }
147
170
  //#endregion
148
- export { collectDynamicHintsFromMatches, collectStaticHintsFromManifest, createEarlyHintsEvent };
171
+ export { collectDynamicHintsFromMatches, collectStaticHintsFromManifest, createEarlyHintsEvent, createResponseLinkHeaderEntries, getResponseLinkHeaderEntries };
149
172
 
150
173
  //# sourceMappingURL=early-hints.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"early-hints.js","names":[],"sources":["../../src/early-hints.ts"],"sourcesContent":["import {\n getStylesheetHref,\n resolveManifestAssetLink,\n} from '@tanstack/router-core'\nimport type {\n AnyRoute,\n AnyRouteMatch,\n AssetCrossOrigin,\n Manifest,\n RouterManagedTag,\n} from '@tanstack/router-core'\n\nexport type EarlyHint = {\n href: string\n rel: 'preload' | 'modulepreload' | 'preconnect' | 'dns-prefetch'\n as?: 'fetch' | 'font' | 'image' | 'script' | 'style' | 'track'\n crossOrigin?: AssetCrossOrigin | ''\n type?: string\n integrity?: string\n referrerPolicy?: string\n fetchPriority?: string\n}\n\nexport type EarlyHintsPhase = 'static' | 'dynamic'\n\nexport type EarlyHintsEvent = {\n phase: EarlyHintsPhase\n hints: ReadonlyArray<EarlyHint>\n links: Array<string>\n allHints: ReadonlyArray<EarlyHint>\n allLinks: Array<string>\n}\n\nexport type OnEarlyHints = (event: EarlyHintsEvent) => void | Promise<void>\n\nconst LINK_PARAM_TOKEN_RE = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/\nconst PRELOAD_AS_VALUES = new Set<EarlyHint['as']>([\n 'fetch',\n 'font',\n 'image',\n 'script',\n 'style',\n 'track',\n])\n\nfunction buildLinkParam(name: string, value: string | undefined): string {\n if (value === undefined) return name\n if (LINK_PARAM_TOKEN_RE.test(value)) return `${name}=${value}`\n return `${name}=${JSON.stringify(value)}`\n}\n\nexport function serializeEarlyHint(hint: EarlyHint): string {\n const parts = [`<${hint.href}>`, buildLinkParam('rel', hint.rel)]\n if (hint.as) parts.push(buildLinkParam('as', hint.as))\n if (hint.crossOrigin !== undefined) {\n parts.push(buildLinkParam('crossorigin', hint.crossOrigin || undefined))\n }\n if (hint.type) parts.push(buildLinkParam('type', hint.type))\n if (hint.integrity) parts.push(buildLinkParam('integrity', hint.integrity))\n if (hint.referrerPolicy) {\n parts.push(buildLinkParam('referrerpolicy', hint.referrerPolicy))\n }\n if (hint.fetchPriority) {\n parts.push(buildLinkParam('fetchpriority', hint.fetchPriority))\n }\n return parts.join('; ')\n}\n\nfunction getStringAttr(\n attrs: Record<string, any> | undefined,\n name: string,\n fallbackName?: string,\n): string | undefined {\n const value =\n attrs?.[name] ?? (fallbackName ? attrs?.[fallbackName] : undefined)\n return typeof value === 'string' ? value : undefined\n}\n\nfunction getPreloadAs(\n attrs: Record<string, any> | undefined,\n): EarlyHint['as'] | undefined {\n const as = getStringAttr(attrs, 'as')\n return as && PRELOAD_AS_VALUES.has(as as EarlyHint['as'])\n ? (as as EarlyHint['as'])\n : undefined\n}\n\nfunction addEarlyHintFetchAttrs(\n hint: EarlyHint,\n attrs: Record<string, any> | undefined,\n) {\n const crossOrigin = getStringAttr(attrs, 'crossOrigin', 'crossorigin') as\n | EarlyHint['crossOrigin']\n | undefined\n const type = getStringAttr(attrs, 'type')\n const integrity = getStringAttr(attrs, 'integrity')\n const referrerPolicy = getStringAttr(\n attrs,\n 'referrerPolicy',\n 'referrerpolicy',\n )\n const fetchPriority = getStringAttr(attrs, 'fetchPriority', 'fetchpriority')\n\n if (crossOrigin !== undefined) hint.crossOrigin = crossOrigin\n if (type) hint.type = type\n if (integrity) hint.integrity = integrity\n if (referrerPolicy) hint.referrerPolicy = referrerPolicy\n if (fetchPriority) hint.fetchPriority = fetchPriority\n}\n\nfunction linkAttrsToEarlyHint(\n attrs: Record<string, any> | undefined,\n): EarlyHint | undefined {\n const href = getStringAttr(attrs, 'href')\n const rel = getStringAttr(attrs, 'rel')\n if (!href || !rel) return undefined\n\n const relTokens = rel.split(/\\s+/)\n let hintRel: EarlyHint['rel'] | undefined\n let hintAs: EarlyHint['as'] | undefined\n\n if (relTokens.includes('modulepreload')) {\n hintRel = 'modulepreload'\n hintAs = 'script'\n } else if (relTokens.includes('stylesheet')) {\n hintRel = 'preload'\n hintAs = 'style'\n } else if (relTokens.includes('preload')) {\n hintAs = getPreloadAs(attrs)\n if (!hintAs) return undefined\n hintRel = 'preload'\n } else if (relTokens.includes('preconnect')) {\n hintRel = 'preconnect'\n hintAs = undefined\n } else if (relTokens.includes('dns-prefetch')) {\n hintRel = 'dns-prefetch'\n hintAs = undefined\n }\n\n if (!hintRel) return undefined\n\n const hint: EarlyHint = {\n href,\n rel: hintRel,\n }\n\n if (hintAs) hint.as = hintAs\n addEarlyHintFetchAttrs(hint, attrs)\n\n return hint\n}\n\nexport function collectStaticHintsFromManifest(\n manifest: Manifest,\n matchedRoutes: ReadonlyArray<AnyRoute>,\n): Array<EarlyHint> {\n const hints: Array<EarlyHint> = []\n\n for (const route of matchedRoutes) {\n const routeManifest = manifest.routes[route.id]\n if (!routeManifest) continue\n\n for (const link of routeManifest.preloads ?? []) {\n const { href, crossOrigin } = resolveManifestAssetLink(link)\n const hint: EarlyHint = { href, rel: 'modulepreload', as: 'script' }\n if (crossOrigin !== undefined) hint.crossOrigin = crossOrigin\n hints.push(hint)\n }\n\n for (const asset of routeManifest.assets ?? []) {\n if (asset.tag !== 'link') continue\n\n const stylesheetHref = getStylesheetHref(asset)\n if (stylesheetHref) {\n if (manifest.inlineCss?.styles[stylesheetHref] !== undefined) continue\n\n const hint: EarlyHint = {\n href: stylesheetHref,\n rel: 'preload',\n as: 'style',\n }\n addEarlyHintFetchAttrs(hint, asset.attrs)\n hints.push(hint)\n continue\n }\n\n const hint = linkAttrsToEarlyHint(asset.attrs)\n if (hint) {\n hints.push(hint)\n }\n }\n }\n\n return hints\n}\n\nexport function collectDynamicHintsFromMatches(\n matches: ReadonlyArray<AnyRouteMatch>,\n): Array<EarlyHint> {\n const hints: Array<EarlyHint> = []\n\n for (const match of matches) {\n const links = match.links\n if (!Array.isArray(links)) continue\n\n for (const link of links as Array<RouterManagedTag['attrs']>) {\n const hint = linkAttrsToEarlyHint(link)\n if (hint) hints.push(hint)\n }\n }\n\n return hints\n}\n\nexport function createEarlyHintsEvent(opts: {\n phase: EarlyHintsPhase\n hints: ReadonlyArray<EarlyHint>\n sentLinks: Set<string>\n sentHints: Array<EarlyHint>\n}): EarlyHintsEvent | undefined {\n const nextHints: Array<EarlyHint> = []\n const nextLinks: Array<string> = []\n\n for (const hint of opts.hints) {\n const link = serializeEarlyHint(hint)\n if (opts.sentLinks.has(link)) continue\n opts.sentLinks.add(link)\n opts.sentHints.push(hint)\n nextHints.push(hint)\n nextLinks.push(link)\n }\n\n if (!nextHints.length && opts.phase !== 'dynamic') return undefined\n\n return {\n phase: opts.phase,\n hints: nextHints,\n links: nextLinks,\n allHints: opts.sentHints.slice(),\n allLinks: Array.from(opts.sentLinks),\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"early-hints.js","names":[],"sources":["../../src/early-hints.ts"],"sourcesContent":["import {\n getStylesheetHref,\n resolveManifestAssetLink,\n} from '@tanstack/router-core'\nimport type {\n AnyRoute,\n AnyRouteMatch,\n AssetCrossOrigin,\n Manifest,\n RouterManagedTag,\n} from '@tanstack/router-core'\n\nexport type EarlyHint = {\n href: string\n rel: 'preload' | 'modulepreload' | 'preconnect' | 'dns-prefetch'\n as?: 'fetch' | 'font' | 'image' | 'script' | 'style' | 'track'\n crossOrigin?: AssetCrossOrigin | ''\n type?: string\n integrity?: string\n referrerPolicy?: string\n fetchPriority?: string\n}\n\nexport type EarlyHintsPhase = 'static' | 'dynamic'\n\nexport type EarlyHintsEvent = {\n phase: EarlyHintsPhase\n hints: ReadonlyArray<EarlyHint>\n links: Array<string>\n allHints: ReadonlyArray<EarlyHint>\n allLinks: Array<string>\n}\n\nexport type OnEarlyHints = (event: EarlyHintsEvent) => void | Promise<void>\n\nexport type ResponseLinkHeaderEntry = {\n phase: EarlyHintsPhase\n hint: EarlyHint\n link: string\n}\n\nexport type ResponseLinkHeaderFilter = (\n entry: ResponseLinkHeaderEntry,\n) => boolean\n\nexport type ResponseLinkHeaderOptions = {\n filter?: ResponseLinkHeaderFilter\n}\n\nconst LINK_PARAM_TOKEN_RE = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/\nconst PRELOAD_AS_VALUES = new Set<EarlyHint['as']>([\n 'fetch',\n 'font',\n 'image',\n 'script',\n 'style',\n 'track',\n])\n\nfunction buildLinkParam(name: string, value: string | undefined): string {\n if (value === undefined) return name\n if (LINK_PARAM_TOKEN_RE.test(value)) return `${name}=${value}`\n return `${name}=${JSON.stringify(value)}`\n}\n\nexport function serializeEarlyHint(hint: EarlyHint): string {\n const parts = [`<${hint.href}>`, buildLinkParam('rel', hint.rel)]\n if (hint.as) parts.push(buildLinkParam('as', hint.as))\n if (hint.crossOrigin !== undefined) {\n parts.push(buildLinkParam('crossorigin', hint.crossOrigin || undefined))\n }\n if (hint.type) parts.push(buildLinkParam('type', hint.type))\n if (hint.integrity) parts.push(buildLinkParam('integrity', hint.integrity))\n if (hint.referrerPolicy) {\n parts.push(buildLinkParam('referrerpolicy', hint.referrerPolicy))\n }\n if (hint.fetchPriority) {\n parts.push(buildLinkParam('fetchpriority', hint.fetchPriority))\n }\n return parts.join('; ')\n}\n\nfunction getStringAttr(\n attrs: Record<string, any> | undefined,\n name: string,\n fallbackName?: string,\n): string | undefined {\n const value =\n attrs?.[name] ?? (fallbackName ? attrs?.[fallbackName] : undefined)\n return typeof value === 'string' ? value : undefined\n}\n\nfunction getPreloadAs(\n attrs: Record<string, any> | undefined,\n): EarlyHint['as'] | undefined {\n const as = getStringAttr(attrs, 'as')\n return as && PRELOAD_AS_VALUES.has(as as EarlyHint['as'])\n ? (as as EarlyHint['as'])\n : undefined\n}\n\nfunction addEarlyHintFetchAttrs(\n hint: EarlyHint,\n attrs: Record<string, any> | undefined,\n) {\n const crossOrigin = getStringAttr(attrs, 'crossOrigin', 'crossorigin') as\n | EarlyHint['crossOrigin']\n | undefined\n const type = getStringAttr(attrs, 'type')\n const integrity = getStringAttr(attrs, 'integrity')\n const referrerPolicy = getStringAttr(\n attrs,\n 'referrerPolicy',\n 'referrerpolicy',\n )\n const fetchPriority = getStringAttr(attrs, 'fetchPriority', 'fetchpriority')\n\n if (crossOrigin !== undefined) hint.crossOrigin = crossOrigin\n if (type) hint.type = type\n if (integrity) hint.integrity = integrity\n if (referrerPolicy) hint.referrerPolicy = referrerPolicy\n if (fetchPriority) hint.fetchPriority = fetchPriority\n}\n\nfunction linkAttrsToEarlyHint(\n attrs: Record<string, any> | undefined,\n): EarlyHint | undefined {\n const href = getStringAttr(attrs, 'href')\n const rel = getStringAttr(attrs, 'rel')\n if (!href || !rel) return undefined\n\n const relTokens = rel.split(/\\s+/)\n let hintRel: EarlyHint['rel'] | undefined\n let hintAs: EarlyHint['as'] | undefined\n\n if (relTokens.includes('modulepreload')) {\n hintRel = 'modulepreload'\n hintAs = 'script'\n } else if (relTokens.includes('stylesheet')) {\n hintRel = 'preload'\n hintAs = 'style'\n } else if (relTokens.includes('preload')) {\n hintAs = getPreloadAs(attrs)\n if (!hintAs) return undefined\n hintRel = 'preload'\n } else if (relTokens.includes('preconnect')) {\n hintRel = 'preconnect'\n hintAs = undefined\n } else if (relTokens.includes('dns-prefetch')) {\n hintRel = 'dns-prefetch'\n hintAs = undefined\n }\n\n if (!hintRel) return undefined\n\n const hint: EarlyHint = {\n href,\n rel: hintRel,\n }\n\n if (hintAs) hint.as = hintAs\n addEarlyHintFetchAttrs(hint, attrs)\n\n return hint\n}\n\nexport function collectStaticHintsFromManifest(\n manifest: Manifest,\n matchedRoutes: ReadonlyArray<AnyRoute>,\n): Array<EarlyHint> {\n const hints: Array<EarlyHint> = []\n\n for (const route of matchedRoutes) {\n const routeManifest = manifest.routes[route.id]\n if (!routeManifest) continue\n\n for (const link of routeManifest.preloads ?? []) {\n const { href, crossOrigin } = resolveManifestAssetLink(link)\n const hint: EarlyHint = { href, rel: 'modulepreload', as: 'script' }\n if (crossOrigin !== undefined) hint.crossOrigin = crossOrigin\n hints.push(hint)\n }\n\n for (const asset of routeManifest.assets ?? []) {\n if (asset.tag !== 'link') continue\n\n const stylesheetHref = getStylesheetHref(asset)\n if (stylesheetHref) {\n if (manifest.inlineCss?.styles[stylesheetHref] !== undefined) continue\n\n const hint: EarlyHint = {\n href: stylesheetHref,\n rel: 'preload',\n as: 'style',\n }\n addEarlyHintFetchAttrs(hint, asset.attrs)\n hints.push(hint)\n continue\n }\n\n const hint = linkAttrsToEarlyHint(asset.attrs)\n if (hint) {\n hints.push(hint)\n }\n }\n }\n\n return hints\n}\n\nexport function collectDynamicHintsFromMatches(\n matches: ReadonlyArray<AnyRouteMatch>,\n): Array<EarlyHint> {\n const hints: Array<EarlyHint> = []\n\n for (const match of matches) {\n const links = match.links\n if (!Array.isArray(links)) continue\n\n for (const link of links as Array<RouterManagedTag['attrs']>) {\n const hint = linkAttrsToEarlyHint(link)\n if (hint) hints.push(hint)\n }\n }\n\n return hints\n}\n\nexport function createEarlyHintsEvent(opts: {\n phase: EarlyHintsPhase\n hints: ReadonlyArray<EarlyHint>\n sentLinks: Set<string>\n sentHints: Array<EarlyHint>\n}): EarlyHintsEvent | undefined {\n const nextHints: Array<EarlyHint> = []\n const nextLinks: Array<string> = []\n\n for (const hint of opts.hints) {\n const link = serializeEarlyHint(hint)\n if (opts.sentLinks.has(link)) continue\n opts.sentLinks.add(link)\n opts.sentHints.push(hint)\n nextHints.push(hint)\n nextLinks.push(link)\n }\n\n if (!nextHints.length && opts.phase !== 'dynamic') return undefined\n\n return {\n phase: opts.phase,\n hints: nextHints,\n links: nextLinks,\n allHints: opts.sentHints.slice(),\n allLinks: Array.from(opts.sentLinks),\n }\n}\n\nexport function createResponseLinkHeaderEntries(opts: {\n phase: EarlyHintsPhase\n hints: ReadonlyArray<EarlyHint>\n sentLinks: Set<string>\n entries: Array<ResponseLinkHeaderEntry>\n}) {\n for (const hint of opts.hints) {\n const link = serializeEarlyHint(hint)\n if (opts.sentLinks.has(link)) continue\n\n opts.sentLinks.add(link)\n opts.entries.push({ phase: opts.phase, hint, link })\n }\n}\n\nexport function getResponseLinkHeaderEntries(opts: {\n entries: ReadonlyArray<ResponseLinkHeaderEntry>\n filter?: ResponseLinkHeaderFilter\n}): Array<string> {\n if (!opts.filter) {\n return opts.entries.map((entry) => entry.link)\n }\n\n try {\n const links: Array<string> = []\n\n for (const entry of opts.entries) {\n if (opts.filter(entry)) {\n links.push(entry.link)\n }\n }\n\n return links\n } catch (err) {\n console.error('Error filtering response Link headers:', err)\n return []\n }\n}\n"],"mappings":"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"}
@@ -8,4 +8,4 @@ export * from './virtual-modules.js';
8
8
  export { HEADERS } from './constants.js';
9
9
  export type { RequestHandler, RequestOptions } from './request-handler.js';
10
10
  export type { SessionConfig } from './session.js';
11
- export type { EarlyHint, EarlyHintsEvent, EarlyHintsPhase, OnEarlyHints, } from './early-hints.js';
11
+ export type { EarlyHint, EarlyHintsEvent, EarlyHintsPhase, OnEarlyHints, ResponseLinkHeaderEntry, ResponseLinkHeaderFilter, ResponseLinkHeaderOptions, } from './early-hints.js';
@@ -1,4 +1,4 @@
1
- import { OnEarlyHints } from './early-hints.js';
1
+ import { OnEarlyHints, ResponseLinkHeaderOptions } from './early-hints.js';
2
2
  type BaseContext = {
3
3
  nonce?: string;
4
4
  };
@@ -30,6 +30,17 @@ type EarlyHintsOptions = {
30
30
  * ```
31
31
  */
32
32
  onEarlyHints?: OnEarlyHints;
33
+ /**
34
+ * Append collected Early Hints `Link` values to the final HTML response's
35
+ * `Link` header. This is useful as a fallback when the runtime cannot write
36
+ * `103` responses, or for CDNs that generate Early Hints from response
37
+ * `Link` headers.
38
+ *
39
+ * `true` appends all collected static and dynamic links after Start confirms
40
+ * the request is not a redirect. Use `filter` to remove links that are not
41
+ * public and cache-safe for your deployment.
42
+ */
43
+ responseLinkHeader?: boolean | ResponseLinkHeaderOptions;
33
44
  };
34
45
  export type RequestOptions<TRegister> = EarlyHintsOptions & (TRegister extends {
35
46
  server: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/start-server-core",
3
- "version": "1.167.27",
3
+ "version": "1.167.28",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -32,6 +32,8 @@ import {
32
32
  collectDynamicHintsFromMatches,
33
33
  collectStaticHintsFromManifest,
34
34
  createEarlyHintsEvent,
35
+ createResponseLinkHeaderEntries,
36
+ getResponseLinkHeaderEntries,
35
37
  } from './early-hints'
36
38
 
37
39
  import { HEADERS } from './constants'
@@ -46,7 +48,15 @@ import type {
46
48
  StartEntry,
47
49
  } from '@tanstack/start-client-core'
48
50
  import type { RequestHandler } from './request-handler'
49
- import type { EarlyHint, EarlyHintsPhase, OnEarlyHints } from './early-hints'
51
+ import type {
52
+ EarlyHint,
53
+ EarlyHintsEvent,
54
+ EarlyHintsPhase,
55
+ OnEarlyHints,
56
+ ResponseLinkHeaderEntry,
57
+ ResponseLinkHeaderFilter,
58
+ ResponseLinkHeaderOptions,
59
+ } from './early-hints'
50
60
  import type {
51
61
  AnyRoute,
52
62
  AnyRouter,
@@ -215,28 +225,106 @@ function getStartResponseHeaders(opts: { router: AnyRouter }) {
215
225
  return headers
216
226
  }
217
227
 
218
- function sendEarlyHintsEvent(opts: {
219
- phase: EarlyHintsPhase
220
- hints: ReadonlyArray<EarlyHint>
221
- onEarlyHints: OnEarlyHints
222
- sentLinks: Set<string>
223
- sentHints: Array<EarlyHint>
224
- }) {
225
- const event = createEarlyHintsEvent(opts)
226
- if (!event) return
227
-
228
+ function notifyEarlyHints(
229
+ phase: EarlyHintsPhase,
230
+ event: EarlyHintsEvent,
231
+ onEarlyHints: OnEarlyHints,
232
+ ) {
228
233
  try {
229
- const result = opts.onEarlyHints(event)
234
+ const result = onEarlyHints(event)
230
235
  if (result) {
231
236
  void Promise.resolve(result).catch((err) => {
232
- console.error(`Error sending ${opts.phase} early hints:`, err)
237
+ console.error(`Error sending ${phase} early hints:`, err)
233
238
  })
234
239
  }
235
240
  } catch (err) {
236
- console.error(`Error sending ${opts.phase} early hints:`, err)
241
+ console.error(`Error sending ${phase} early hints:`, err)
242
+ }
243
+ }
244
+
245
+ function getResponseLinkHeaderFilter(
246
+ responseLinkHeader: boolean | ResponseLinkHeaderOptions | undefined,
247
+ ): ResponseLinkHeaderFilter | undefined {
248
+ if (typeof responseLinkHeader !== 'object') {
249
+ return undefined
250
+ }
251
+
252
+ return responseLinkHeader.filter
253
+ }
254
+
255
+ function appendResponseLinkHeaders(opts: {
256
+ responseHeaders: Headers
257
+ entries: ReadonlyArray<ResponseLinkHeaderEntry>
258
+ filter?: ResponseLinkHeaderFilter
259
+ }) {
260
+ if (!opts.filter) {
261
+ for (const entry of opts.entries) {
262
+ opts.responseHeaders.append('Link', entry.link)
263
+ }
264
+ return
265
+ }
266
+
267
+ const links = getResponseLinkHeaderEntries(opts)
268
+
269
+ for (const link of links) {
270
+ opts.responseHeaders.append('Link', link)
237
271
  }
238
272
  }
239
273
 
274
+ function collectResponseLinkHeaderEntries(opts: {
275
+ phase: EarlyHintsPhase
276
+ event: EarlyHintsEvent
277
+ entries: Array<ResponseLinkHeaderEntry>
278
+ }) {
279
+ for (let index = 0; index < opts.event.hints.length; index++) {
280
+ opts.entries.push({
281
+ phase: opts.phase,
282
+ hint: opts.event.hints[index]!,
283
+ link: opts.event.links[index]!,
284
+ })
285
+ }
286
+ }
287
+
288
+ function handleCollectedEarlyHints(opts: {
289
+ phase: EarlyHintsPhase
290
+ hints: ReadonlyArray<EarlyHint>
291
+ sentLinks: Set<string>
292
+ sentHints?: Array<EarlyHint>
293
+ onEarlyHints?: OnEarlyHints
294
+ responseLinkHeaderEntries?: Array<ResponseLinkHeaderEntry>
295
+ }) {
296
+ const event = opts.onEarlyHints
297
+ ? createEarlyHintsEvent({
298
+ phase: opts.phase,
299
+ hints: opts.hints,
300
+ sentLinks: opts.sentLinks,
301
+ sentHints: opts.sentHints!,
302
+ })
303
+ : undefined
304
+
305
+ if (event) {
306
+ notifyEarlyHints(opts.phase, event, opts.onEarlyHints!)
307
+ }
308
+
309
+ if (!opts.responseLinkHeaderEntries) return
310
+
311
+ if (event) {
312
+ collectResponseLinkHeaderEntries({
313
+ phase: opts.phase,
314
+ event,
315
+ entries: opts.responseLinkHeaderEntries,
316
+ })
317
+ return
318
+ }
319
+
320
+ createResponseLinkHeaderEntries({
321
+ phase: opts.phase,
322
+ hints: opts.hints,
323
+ sentLinks: opts.sentLinks,
324
+ entries: opts.responseLinkHeaderEntries,
325
+ })
326
+ }
327
+
240
328
  interface PluginAdaptersEntry {
241
329
  hasPluginAdapters: boolean
242
330
  pluginSerializationAdapters: Array<AnySerializationAdapter>
@@ -722,27 +810,36 @@ export function createStartHandler<TRegister = Register>(
722
810
  cache,
723
811
  )
724
812
 
725
- const onEarlyHints =
726
- process.env.TSS_DEV_SERVER === 'true'
727
- ? undefined
728
- : requestOpts?.onEarlyHints
729
- const earlyHintsSentLinks = onEarlyHints ? new Set<string>() : undefined
730
- const earlyHintsSentHints = onEarlyHints
731
- ? new Array<EarlyHint>()
813
+ const onEarlyHints = requestOpts?.onEarlyHints
814
+ const responseLinkHeader = requestOpts?.responseLinkHeader
815
+ const shouldCollectEarlyHints =
816
+ process.env.TSS_DEV_SERVER !== 'true' &&
817
+ (!!onEarlyHints || !!responseLinkHeader)
818
+ const sentEarlyHintLinks = shouldCollectEarlyHints
819
+ ? new Set<string>()
820
+ : undefined
821
+ const sentEarlyHints = onEarlyHints ? new Array<EarlyHint>() : undefined
822
+ const responseLinkHeaderEntries =
823
+ shouldCollectEarlyHints && responseLinkHeader
824
+ ? new Array<ResponseLinkHeaderEntry>()
825
+ : undefined
826
+ const responseLinkHeaderFilter = shouldCollectEarlyHints
827
+ ? getResponseLinkHeaderFilter(responseLinkHeader)
732
828
  : undefined
733
829
 
734
830
  if (
735
- onEarlyHints &&
736
- earlyHintsSentLinks &&
737
- earlyHintsSentHints &&
831
+ shouldCollectEarlyHints &&
832
+ sentEarlyHintLinks &&
738
833
  matchedRoutes?.length
739
834
  ) {
740
- sendEarlyHintsEvent({
835
+ const hints = collectStaticHintsFromManifest(manifest, matchedRoutes)
836
+ handleCollectedEarlyHints({
741
837
  phase: 'static',
742
- hints: collectStaticHintsFromManifest(manifest, matchedRoutes),
838
+ hints,
839
+ sentLinks: sentEarlyHintLinks,
840
+ sentHints: sentEarlyHints,
743
841
  onEarlyHints,
744
- sentLinks: earlyHintsSentLinks,
745
- sentHints: earlyHintsSentHints,
842
+ responseLinkHeaderEntries,
746
843
  })
747
844
  }
748
845
 
@@ -763,14 +860,16 @@ export function createStartHandler<TRegister = Register>(
763
860
  return routerInstance.state.redirect
764
861
  }
765
862
 
766
- if (onEarlyHints && earlyHintsSentLinks && earlyHintsSentHints) {
863
+ if (shouldCollectEarlyHints && sentEarlyHintLinks) {
767
864
  const loadedMatches = routerInstance.stores.matches.get()
768
- sendEarlyHintsEvent({
865
+ const hints = collectDynamicHintsFromMatches(loadedMatches)
866
+ handleCollectedEarlyHints({
769
867
  phase: 'dynamic',
770
- hints: collectDynamicHintsFromMatches(loadedMatches),
868
+ hints,
869
+ sentLinks: sentEarlyHintLinks,
870
+ sentHints: sentEarlyHints,
771
871
  onEarlyHints,
772
- sentLinks: earlyHintsSentLinks,
773
- sentHints: earlyHintsSentHints,
872
+ responseLinkHeaderEntries,
774
873
  })
775
874
  }
776
875
 
@@ -783,6 +882,13 @@ export function createStartHandler<TRegister = Register>(
783
882
  const responseHeaders = getStartResponseHeaders({
784
883
  router: routerInstance,
785
884
  })
885
+ if (responseLinkHeaderEntries?.length) {
886
+ appendResponseLinkHeaders({
887
+ responseHeaders,
888
+ entries: responseLinkHeaderEntries,
889
+ filter: responseLinkHeaderFilter,
890
+ })
891
+ }
786
892
  cbWillCleanup = true
787
893
 
788
894
  return cb({
@@ -33,6 +33,20 @@ export type EarlyHintsEvent = {
33
33
 
34
34
  export type OnEarlyHints = (event: EarlyHintsEvent) => void | Promise<void>
35
35
 
36
+ export type ResponseLinkHeaderEntry = {
37
+ phase: EarlyHintsPhase
38
+ hint: EarlyHint
39
+ link: string
40
+ }
41
+
42
+ export type ResponseLinkHeaderFilter = (
43
+ entry: ResponseLinkHeaderEntry,
44
+ ) => boolean
45
+
46
+ export type ResponseLinkHeaderOptions = {
47
+ filter?: ResponseLinkHeaderFilter
48
+ }
49
+
36
50
  const LINK_PARAM_TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/
37
51
  const PRELOAD_AS_VALUES = new Set<EarlyHint['as']>([
38
52
  'fetch',
@@ -240,3 +254,42 @@ export function createEarlyHintsEvent(opts: {
240
254
  allLinks: Array.from(opts.sentLinks),
241
255
  }
242
256
  }
257
+
258
+ export function createResponseLinkHeaderEntries(opts: {
259
+ phase: EarlyHintsPhase
260
+ hints: ReadonlyArray<EarlyHint>
261
+ sentLinks: Set<string>
262
+ entries: Array<ResponseLinkHeaderEntry>
263
+ }) {
264
+ for (const hint of opts.hints) {
265
+ const link = serializeEarlyHint(hint)
266
+ if (opts.sentLinks.has(link)) continue
267
+
268
+ opts.sentLinks.add(link)
269
+ opts.entries.push({ phase: opts.phase, hint, link })
270
+ }
271
+ }
272
+
273
+ export function getResponseLinkHeaderEntries(opts: {
274
+ entries: ReadonlyArray<ResponseLinkHeaderEntry>
275
+ filter?: ResponseLinkHeaderFilter
276
+ }): Array<string> {
277
+ if (!opts.filter) {
278
+ return opts.entries.map((entry) => entry.link)
279
+ }
280
+
281
+ try {
282
+ const links: Array<string> = []
283
+
284
+ for (const entry of opts.entries) {
285
+ if (opts.filter(entry)) {
286
+ links.push(entry.link)
287
+ }
288
+ }
289
+
290
+ return links
291
+ } catch (err) {
292
+ console.error('Error filtering response Link headers:', err)
293
+ return []
294
+ }
295
+ }
package/src/index.tsx CHANGED
@@ -41,4 +41,7 @@ export type {
41
41
  EarlyHintsEvent,
42
42
  EarlyHintsPhase,
43
43
  OnEarlyHints,
44
+ ResponseLinkHeaderEntry,
45
+ ResponseLinkHeaderFilter,
46
+ ResponseLinkHeaderOptions,
44
47
  } from './early-hints'
@@ -1,4 +1,4 @@
1
- import type { OnEarlyHints } from './early-hints'
1
+ import type { OnEarlyHints, ResponseLinkHeaderOptions } from './early-hints'
2
2
 
3
3
  type BaseContext = {
4
4
  nonce?: string
@@ -32,6 +32,17 @@ type EarlyHintsOptions = {
32
32
  * ```
33
33
  */
34
34
  onEarlyHints?: OnEarlyHints
35
+ /**
36
+ * Append collected Early Hints `Link` values to the final HTML response's
37
+ * `Link` header. This is useful as a fallback when the runtime cannot write
38
+ * `103` responses, or for CDNs that generate Early Hints from response
39
+ * `Link` headers.
40
+ *
41
+ * `true` appends all collected static and dynamic links after Start confirms
42
+ * the request is not a redirect. Use `filter` to remove links that are not
43
+ * public and cache-safe for your deployment.
44
+ */
45
+ responseLinkHeader?: boolean | ResponseLinkHeaderOptions
35
46
  }
36
47
 
37
48
  export type RequestOptions<TRegister> = EarlyHintsOptions &