@tanstack/start-server-core 1.169.5 → 1.169.6
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.
|
@@ -8,7 +8,7 @@ import { ServerFunctionSerializationAdapter } from "./serializer/ServerFunctionS
|
|
|
8
8
|
import { createMemoryHistory } from "@tanstack/history";
|
|
9
9
|
import { createCsrfMiddleware, createNullProtoObject, csrfSymbol, flattenMiddlewares, mergeHeaders, safeObjectMerge } from "@tanstack/start-client-core";
|
|
10
10
|
import { executeRewriteInput, isRedirect, isResolvedRedirect } from "@tanstack/router-core";
|
|
11
|
-
import { attachRouterServerSsrUtils, getNormalizedURL, getOrigin } from "@tanstack/router-core/ssr/server";
|
|
11
|
+
import { attachRouterServerSsrUtils, getNormalizedURL, getOrigin, isSsrResponse, normalizeSsrResponse, replaceSsrResponse, stripSsrResponseBody } from "@tanstack/router-core/ssr/server";
|
|
12
12
|
import { getStartContext, runWithStartContext } from "@tanstack/start-storage-context";
|
|
13
13
|
//#region src/createStartHandler.ts
|
|
14
14
|
function getStartResponseHeaders(opts) {
|
|
@@ -90,18 +90,48 @@ function isSpecialResponse(value) {
|
|
|
90
90
|
* Normalize middleware result to context shape
|
|
91
91
|
*/
|
|
92
92
|
function handleCtxResult(result) {
|
|
93
|
-
if (isSpecialResponse(result)) return { response: result };
|
|
93
|
+
if (isSsrResponse(result) || isSpecialResponse(result)) return { response: result };
|
|
94
94
|
return result;
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* Execute a middleware chain
|
|
98
98
|
*/
|
|
99
|
-
function executeMiddleware(middlewares, ctx) {
|
|
99
|
+
async function executeMiddleware(middlewares, ctx) {
|
|
100
100
|
let index = -1;
|
|
101
|
+
let streamResponse;
|
|
102
|
+
const setResponse = (response) => {
|
|
103
|
+
if (isSsrResponse(response)) {
|
|
104
|
+
if (response.serverSsrCleanup === "stream") streamResponse = response;
|
|
105
|
+
ctx.response = response.response;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
ctx.response = response;
|
|
109
|
+
};
|
|
110
|
+
const disposeStreamResponse = async (reason) => {
|
|
111
|
+
const response = streamResponse;
|
|
112
|
+
if (!response) return;
|
|
113
|
+
streamResponse = void 0;
|
|
114
|
+
const currentResponse = ctx.response;
|
|
115
|
+
if (currentResponse === response.response || currentResponse instanceof Response && response.response.body !== null && currentResponse.body === response.response.body) ctx.response = void 0;
|
|
116
|
+
await response.dispose(reason);
|
|
117
|
+
};
|
|
118
|
+
const getFinalResponse = async () => {
|
|
119
|
+
const response = ctx.response;
|
|
120
|
+
if (!response) throwRouteHandlerError();
|
|
121
|
+
if (!streamResponse) return response;
|
|
122
|
+
if (response === streamResponse.response) return streamResponse;
|
|
123
|
+
if (streamResponse.response.body !== null && response.body === streamResponse.response.body) return {
|
|
124
|
+
...streamResponse,
|
|
125
|
+
response
|
|
126
|
+
};
|
|
127
|
+
await disposeStreamResponse("middleware response replaced");
|
|
128
|
+
return response;
|
|
129
|
+
};
|
|
101
130
|
const next = async (nextCtx) => {
|
|
102
131
|
if (nextCtx) {
|
|
103
132
|
if (nextCtx.context) ctx.context = safeObjectMerge(ctx.context, nextCtx.context);
|
|
104
|
-
for (const key of Object.keys(nextCtx)) if (key
|
|
133
|
+
for (const key of Object.keys(nextCtx)) if (key === "response") setResponse(nextCtx.response);
|
|
134
|
+
else if (key !== "context") ctx[key] = nextCtx[key];
|
|
105
135
|
}
|
|
106
136
|
index++;
|
|
107
137
|
const middleware = middlewares[index];
|
|
@@ -114,19 +144,24 @@ function executeMiddleware(middlewares, ctx) {
|
|
|
114
144
|
});
|
|
115
145
|
} catch (err) {
|
|
116
146
|
if (isSpecialResponse(err)) {
|
|
117
|
-
|
|
147
|
+
setResponse(err);
|
|
118
148
|
return ctx;
|
|
119
149
|
}
|
|
150
|
+
await disposeStreamResponse("middleware error");
|
|
120
151
|
throw err;
|
|
121
152
|
}
|
|
122
153
|
const normalized = handleCtxResult(result);
|
|
123
154
|
if (normalized) {
|
|
124
|
-
if (normalized.response !== void 0)
|
|
155
|
+
if (normalized.response !== void 0) setResponse(normalized.response);
|
|
125
156
|
if (normalized.context) ctx.context = safeObjectMerge(ctx.context, normalized.context);
|
|
126
157
|
}
|
|
127
158
|
return ctx;
|
|
128
159
|
};
|
|
129
|
-
|
|
160
|
+
await next();
|
|
161
|
+
return {
|
|
162
|
+
ctx,
|
|
163
|
+
response: await getFinalResponse()
|
|
164
|
+
};
|
|
130
165
|
}
|
|
131
166
|
/**
|
|
132
167
|
* Wrap a route handler as middleware
|
|
@@ -183,7 +218,7 @@ function createStartHandler(cbOrOptions) {
|
|
|
183
218
|
if (process.env.TSS_DEV_SERVER !== "true") finalManifestResolver.warmup({ getBaseManifest: () => getBaseManifest(void 0) });
|
|
184
219
|
const startRequestResolver = async (request, requestOpts) => {
|
|
185
220
|
let router = null;
|
|
186
|
-
let
|
|
221
|
+
let responseOwnsCleanup = false;
|
|
187
222
|
try {
|
|
188
223
|
const { url, handledProtocolRelativeURL } = getNormalizedURL(request.url);
|
|
189
224
|
const href = url.pathname + url.search + url.hash;
|
|
@@ -240,16 +275,19 @@ function createStartHandler(cbOrOptions) {
|
|
|
240
275
|
serverFnId
|
|
241
276
|
}));
|
|
242
277
|
};
|
|
243
|
-
|
|
278
|
+
const { response: middlewareResponse } = await executeMiddleware([...flattenedRequestMiddlewares.map((d) => d.options.server), serverFnHandler], {
|
|
244
279
|
request,
|
|
245
280
|
pathname: url.pathname,
|
|
246
281
|
handlerType: "serverFn",
|
|
247
282
|
context: createNullProtoObject(requestOpts?.context)
|
|
248
|
-
})
|
|
283
|
+
});
|
|
284
|
+
const result = await handleRedirectResponse(middlewareResponse, request, getRouter);
|
|
285
|
+
responseOwnsCleanup = result.serverSsrCleanup === "stream";
|
|
286
|
+
return result.response;
|
|
249
287
|
}
|
|
250
288
|
const executeRouter = async (serverContext, matchedRoutes) => {
|
|
251
289
|
const acceptParts = (request.headers.get("Accept") || "*/*").split(",");
|
|
252
|
-
if (!["*/*", "text/html"].some((mimeType) => acceptParts.some((part) => part.trim().startsWith(mimeType)))) return Response.json({ error: "Only HTML requests are supported here" }, { status: 500 });
|
|
290
|
+
if (!["*/*", "text/html"].some((mimeType) => acceptParts.some((part) => part.trim().startsWith(mimeType)))) return normalizeSsrResponse(Response.json({ error: "Only HTML requests are supported here" }, { status: 500 }));
|
|
253
291
|
const manifest = await resolveManifestForRequest({
|
|
254
292
|
request,
|
|
255
293
|
requestInlineCss: requestOpts?.inlineCss,
|
|
@@ -271,18 +309,17 @@ function createStartHandler(cbOrOptions) {
|
|
|
271
309
|
});
|
|
272
310
|
routerInstance.update({ additionalContext: { serverContext } });
|
|
273
311
|
await routerInstance.load();
|
|
274
|
-
if (routerInstance.state.redirect) return routerInstance.state.redirect;
|
|
312
|
+
if (routerInstance.state.redirect) return normalizeSsrResponse(routerInstance.state.redirect);
|
|
275
313
|
earlyHints?.collectDynamic(routerInstance.stores.matches.get());
|
|
276
314
|
const ctx = getStartContext({ throwIfNotFound: false });
|
|
277
315
|
await routerInstance.serverSsr.dehydrate({ requestAssets: ctx?.requestAssets });
|
|
278
316
|
const responseHeaders = getStartResponseHeaders({ router: routerInstance });
|
|
279
317
|
earlyHints?.appendResponseHeaders(responseHeaders);
|
|
280
|
-
|
|
281
|
-
return cb({
|
|
318
|
+
return normalizeSsrResponse(await cb({
|
|
282
319
|
request,
|
|
283
320
|
router: routerInstance,
|
|
284
321
|
responseHeaders
|
|
285
|
-
});
|
|
322
|
+
}));
|
|
286
323
|
};
|
|
287
324
|
const requestHandlerMiddleware = async ({ context }) => {
|
|
288
325
|
return runWithStartContext({
|
|
@@ -308,41 +345,45 @@ function createStartHandler(cbOrOptions) {
|
|
|
308
345
|
}
|
|
309
346
|
});
|
|
310
347
|
};
|
|
311
|
-
|
|
348
|
+
const { response: middlewareResponse } = await executeMiddleware([...flattenedRequestMiddlewares.map((d) => d.options.server), requestHandlerMiddleware], {
|
|
312
349
|
request,
|
|
313
350
|
pathname: url.pathname,
|
|
314
351
|
handlerType: "router",
|
|
315
352
|
context: createNullProtoObject(requestOpts?.context)
|
|
316
|
-
})
|
|
353
|
+
});
|
|
354
|
+
const response = await handleRedirectResponse(middlewareResponse, request, getRouter);
|
|
355
|
+
responseOwnsCleanup = response.serverSsrCleanup === "stream";
|
|
356
|
+
return response.response;
|
|
317
357
|
} finally {
|
|
318
|
-
if (router && !
|
|
358
|
+
if (router?.serverSsr && !responseOwnsCleanup) router.serverSsr.cleanup();
|
|
319
359
|
router = null;
|
|
320
360
|
}
|
|
321
361
|
};
|
|
322
362
|
return requestHandler(startRequestResolver);
|
|
323
363
|
}
|
|
324
364
|
async function handleRedirectResponse(response, request, getRouter) {
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
365
|
+
const ssrResponse = normalizeSsrResponse(response);
|
|
366
|
+
if (!isRedirect(ssrResponse.response)) return ssrResponse;
|
|
367
|
+
if (isResolvedRedirect(ssrResponse.response)) {
|
|
368
|
+
if (request.headers.get("x-tsr-serverFn") === "true") return replaceSsrResponse(ssrResponse, Response.json({
|
|
369
|
+
...ssrResponse.response.options,
|
|
329
370
|
isSerializedRedirect: true
|
|
330
|
-
}, { headers: response.headers });
|
|
331
|
-
return
|
|
371
|
+
}, { headers: ssrResponse.response.headers }), "redirect response replaced");
|
|
372
|
+
return ssrResponse;
|
|
332
373
|
}
|
|
333
|
-
const opts = response.options;
|
|
374
|
+
const opts = ssrResponse.response.options;
|
|
334
375
|
if (opts.to && typeof opts.to === "string" && !opts.to.startsWith("/")) throw new Error(`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)}`);
|
|
335
376
|
if ([
|
|
336
377
|
"params",
|
|
337
378
|
"search",
|
|
338
379
|
"hash"
|
|
339
380
|
].some((d) => typeof opts[d] === "function")) throw new Error(`Server side redirects must use static search, params, and hash values and do not support functional values. Received functional values for: ${Object.keys(opts).filter((d) => typeof opts[d] === "function").map((d) => `"${d}"`).join(", ")}`);
|
|
340
|
-
const redirect = (await getRouter()).resolveRedirect(response);
|
|
341
|
-
if (request.headers.get("x-tsr-serverFn") === "true") return Response.json({
|
|
342
|
-
...response.options,
|
|
381
|
+
const redirect = (await getRouter()).resolveRedirect(ssrResponse.response);
|
|
382
|
+
if (request.headers.get("x-tsr-serverFn") === "true") return replaceSsrResponse(ssrResponse, Response.json({
|
|
383
|
+
...ssrResponse.response.options,
|
|
343
384
|
isSerializedRedirect: true
|
|
344
|
-
}, { headers: response.headers });
|
|
345
|
-
return redirect;
|
|
385
|
+
}, { headers: ssrResponse.response.headers }), "redirect response replaced");
|
|
386
|
+
return replaceSsrResponse(ssrResponse, redirect, "redirect response replaced");
|
|
346
387
|
}
|
|
347
388
|
async function handleServerRoutes({ getRouter, request, url, executeRouter, context, executedRequestMiddlewares }) {
|
|
348
389
|
const router = await getRouter();
|
|
@@ -376,8 +417,8 @@ async function handleServerRoutes({ getRouter, request, url, executeRouter, cont
|
|
|
376
417
|
}
|
|
377
418
|
}
|
|
378
419
|
}
|
|
379
|
-
routeMiddlewares.push((ctx) => executeRouter(ctx.context, matchedRoutes));
|
|
380
|
-
const ctx = await executeMiddleware(routeMiddlewares, {
|
|
420
|
+
routeMiddlewares.push(((ctx) => executeRouter(ctx.context, matchedRoutes)));
|
|
421
|
+
const { ctx, response } = await executeMiddleware(routeMiddlewares, {
|
|
381
422
|
request,
|
|
382
423
|
context,
|
|
383
424
|
params: routeParams,
|
|
@@ -386,10 +427,9 @@ async function handleServerRoutes({ getRouter, request, url, executeRouter, cont
|
|
|
386
427
|
});
|
|
387
428
|
if (isHeadFallback) {
|
|
388
429
|
if (!ctx.response) throwRouteHandlerError();
|
|
389
|
-
|
|
390
|
-
return new Response(null, resolved);
|
|
430
|
+
return stripSsrResponseBody(await handleRedirectResponse(response, request, getRouter), "HEAD body stripped");
|
|
391
431
|
}
|
|
392
|
-
return
|
|
432
|
+
return normalizeSsrResponse(response);
|
|
393
433
|
}
|
|
394
434
|
//#endregion
|
|
395
435
|
export { createStartHandler };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createStartHandler.js","names":[],"sources":["../../src/createStartHandler.ts"],"sourcesContent":["import { createMemoryHistory } from '@tanstack/history'\nimport {\n createCsrfMiddleware,\n createNullProtoObject,\n csrfSymbol,\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 { createEarlyHintsCollector } from './early-hints'\nimport {\n createCachedBaseManifestLoader,\n createFinalManifestResolver,\n} from './finalManifest'\n\nimport { HEADERS } from './constants'\nimport { ServerFunctionSerializationAdapter } from './serializer/ServerFunctionSerializationAdapter'\nimport type {\n AnyFunctionMiddleware,\n AnyRequestMiddleware,\n AnyStartInstanceOptions,\n RouteMethod,\n RouteMethodHandlerFn,\n RouterEntry,\n StartEntry,\n} from '@tanstack/start-client-core'\nimport type { RequestHandler } from './request-handler'\nimport type {\n AnyRoute,\n AnyRouter,\n AnySerializationAdapter,\n Register,\n} from '@tanstack/router-core'\nimport type { HandlerCallback } from '@tanstack/router-core/ssr/server'\nimport type { FinalManifestOptions } from './finalManifest'\n\ntype TODO = any\n\ntype AnyMiddlewareServerFn =\n | AnyRequestMiddleware['options']['server']\n | AnyFunctionMiddleware['options']['server']\n\nexport interface CreateStartHandlerOptions extends FinalManifestOptions {\n handler: HandlerCallback<AnyRouter>\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\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 hasWarnedMissingCsrfMiddleware = false\nconst defaultCsrfMiddleware = createCsrfMiddleware({\n filter: (ctx) => ctx.handlerType === 'serverFn',\n})\nconst getCachedBaseManifest = createCachedBaseManifestLoader(() =>\n getStartManifest(),\n)\nconst getProdBaseManifest: typeof getStartManifest = () =>\n getCachedBaseManifest()\nconst getBaseManifest =\n process.env.TSS_DEV_SERVER === 'true' ? getStartManifest : getProdBaseManifest\nconst createEarlyHintsForRequest: typeof createEarlyHintsCollector =\n process.env.TSS_DEV_SERVER === 'true'\n ? () => undefined\n : createEarlyHintsCollector\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\nfunction hasCsrfMiddleware(\n middlewares: Array<AnyRequestMiddleware | AnyFunctionMiddleware>,\n): boolean {\n return middlewares.some((middleware) => csrfSymbol in middleware)\n}\n\nfunction warnMissingCsrfMiddlewareOnce() {\n if (hasWarnedMissingCsrfMiddleware) return\n hasWarnedMissingCsrfMiddleware = true\n\n console.warn(`TanStack Start server functions are not protected by the CSRF middleware.\n\nServer functions are same-origin RPC endpoints and should be protected from cross-site requests.\n\nAdd the CSRF middleware in src/start.ts:\n\n const csrfMiddleware = createCsrfMiddleware({\n filter: (ctx) => ctx.handlerType === 'serverFn',\n })\n\n export const startInstance = createStart(() => ({\n requestMiddleware: [csrfMiddleware],\n }))\n\nIf you intentionally handle CSRF another way, disable this warning:\n\n tanstackStart({\n serverFns: {\n disableCsrfMiddlewareWarning: true,\n },\n })`)\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 const handlerOptions: FinalManifestOptions =\n typeof cbOrOptions === 'function' ? {} : cbOrOptions\n const cb: HandlerCallback<AnyRouter> =\n typeof cbOrOptions === 'function' ? cbOrOptions : cbOrOptions.handler\n const finalManifestResolver = createFinalManifestResolver({\n ...handlerOptions,\n cacheCreateTransform: process.env.TSS_DEV_SERVER !== 'true',\n })\n const resolveManifestForRequest =\n process.env.TSS_DEV_SERVER === 'true'\n ? finalManifestResolver.resolveUncached\n : finalManifestResolver.resolveCached\n\n if (process.env.TSS_DEV_SERVER !== 'true') {\n finalManifestResolver.warmup({\n getBaseManifest: () => getBaseManifest(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 hasStartInstance = !!entries.startEntry.startInstance\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 requestMiddleware: hasStartInstance\n ? startOptions.requestMiddleware\n : [defaultCsrfMiddleware],\n serializationAdapters,\n }\n\n // Flatten request middlewares once\n const flattenedRequestMiddlewares = requestStartOptions.requestMiddleware\n ? flattenMiddlewares(requestStartOptions.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 if (\n process.env.NODE_ENV !== 'production' &&\n process.env.TSS_DISABLE_CSRF_MIDDLEWARE_WARNING !== 'true' &&\n !hasCsrfMiddleware(flattenedRequestMiddlewares)\n ) {\n warnMissingCsrfMiddlewareOnce()\n }\n\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 handlerType: 'serverFn',\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 resolveManifestForRequest({\n request,\n requestInlineCss: requestOpts?.inlineCss,\n getBaseManifest: () => getBaseManifest(matchedRoutes),\n })\n\n const earlyHints = createEarlyHintsForRequest({\n onEarlyHints: requestOpts?.onEarlyHints,\n responseLinkHeader: requestOpts?.responseLinkHeader,\n })\n\n earlyHints?.collectStatic({ manifest, matchedRoutes })\n\n const routerInstance = await getRouter()\n\n attachRouterServerSsrUtils({\n router: routerInstance,\n manifest,\n getRequestAssets: () =>\n getStartContext({ throwIfNotFound: false })?.requestAssets,\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 earlyHints?.collectDynamic(routerInstance.stores.matches.get())\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 earlyHints?.appendResponseHeaders(responseHeaders)\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 handlerType: 'router',\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 let isHeadFallback = false\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 // Per RFC 9110 §9.3.2, HEAD must return the same header fields as GET.\n // Priority for HEAD: explicit HEAD handler → GET → ANY (last resort).\n const handler =\n requestMethod === 'HEAD'\n ? (handlers['HEAD'] ?? handlers['GET'] ?? handlers['ANY'])\n : (handlers[requestMethod] ?? handlers['ANY'])\n isHeadFallback =\n requestMethod === 'HEAD' && handler !== undefined && !handlers['HEAD']\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 handlerType: 'router',\n })\n\n // RFC 9110 §9.3.2: HEAD must carry the same header fields as GET but no body.\n // Resolve any redirect before stripping so the Location header survives.\n if (isHeadFallback) {\n if (!ctx.response) {\n throwRouteHandlerError()\n }\n\n const resolved = await handleRedirectResponse(\n ctx.response,\n request,\n getRouter,\n )\n return new Response(null, resolved)\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 createCsrfMiddleware,\n createNullProtoObject,\n csrfSymbol,\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 isSsrResponse,\n normalizeSsrResponse,\n replaceSsrResponse,\n stripSsrResponseBody,\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 { createEarlyHintsCollector } from './early-hints'\nimport {\n createCachedBaseManifestLoader,\n createFinalManifestResolver,\n} from './finalManifest'\n\nimport { HEADERS } from './constants'\nimport { ServerFunctionSerializationAdapter } from './serializer/ServerFunctionSerializationAdapter'\nimport type {\n AnyFunctionMiddleware,\n AnyRequestMiddleware,\n AnyStartInstanceOptions,\n RouteMethod,\n RouteMethodHandlerFn,\n RouterEntry,\n StartEntry,\n} from '@tanstack/start-client-core'\nimport type { RequestHandler } from './request-handler'\nimport type {\n AnyRoute,\n AnyRouter,\n AnySerializationAdapter,\n Register,\n} from '@tanstack/router-core'\nimport type {\n HandlerCallback,\n HandlerCallbackResult,\n SsrResponse,\n} from '@tanstack/router-core/ssr/server'\nimport type { FinalManifestOptions } from './finalManifest'\n\ntype TODO = any\n\ntype AnyMiddlewareServerFn =\n | AnyRequestMiddleware['options']['server']\n | AnyFunctionMiddleware['options']['server']\n\nexport interface CreateStartHandlerOptions extends FinalManifestOptions {\n handler: HandlerCallback<AnyRouter>\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\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 hasWarnedMissingCsrfMiddleware = false\nconst defaultCsrfMiddleware = createCsrfMiddleware({\n filter: (ctx) => ctx.handlerType === 'serverFn',\n})\nconst getCachedBaseManifest = createCachedBaseManifestLoader(() =>\n getStartManifest(),\n)\nconst getProdBaseManifest: typeof getStartManifest = () =>\n getCachedBaseManifest()\nconst getBaseManifest =\n process.env.TSS_DEV_SERVER === 'true' ? getStartManifest : getProdBaseManifest\nconst createEarlyHintsForRequest: typeof createEarlyHintsCollector =\n process.env.TSS_DEV_SERVER === 'true'\n ? () => undefined\n : createEarlyHintsCollector\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\nfunction hasCsrfMiddleware(\n middlewares: Array<AnyRequestMiddleware | AnyFunctionMiddleware>,\n): boolean {\n return middlewares.some((middleware) => csrfSymbol in middleware)\n}\n\nfunction warnMissingCsrfMiddlewareOnce() {\n if (hasWarnedMissingCsrfMiddleware) return\n hasWarnedMissingCsrfMiddleware = true\n\n console.warn(`TanStack Start server functions are not protected by the CSRF middleware.\n\nServer functions are same-origin RPC endpoints and should be protected from cross-site requests.\n\nAdd the CSRF middleware in src/start.ts:\n\n const csrfMiddleware = createCsrfMiddleware({\n filter: (ctx) => ctx.handlerType === 'serverFn',\n })\n\n export const startInstance = createStart(() => ({\n requestMiddleware: [csrfMiddleware],\n }))\n\nIf you intentionally handle CSRF another way, disable this warning:\n\n tanstackStart({\n serverFns: {\n disableCsrfMiddlewareWarning: true,\n },\n })`)\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 (isSsrResponse(result) || isSpecialResponse(result)) {\n return { response: result }\n }\n return result\n}\n\n/**\n * Execute a middleware chain\n */\nasync function executeMiddleware(\n middlewares: Array<TODO>,\n ctx: TODO,\n): Promise<{ ctx: TODO; response: HandlerCallbackResult }> {\n let index = -1\n let streamResponse:\n | Extract<SsrResponse, { serverSsrCleanup: 'stream' }>\n | undefined\n\n const setResponse = (response: TODO) => {\n if (isSsrResponse(response)) {\n if (response.serverSsrCleanup === 'stream') {\n streamResponse = response\n }\n ctx.response = response.response\n return\n }\n\n ctx.response = response\n }\n\n const disposeStreamResponse = async (reason: string) => {\n const response = streamResponse\n if (!response) {\n return\n }\n\n streamResponse = undefined\n const currentResponse = ctx.response\n if (\n currentResponse === response.response ||\n (currentResponse instanceof Response &&\n response.response.body !== null &&\n currentResponse.body === response.response.body)\n ) {\n ctx.response = undefined\n }\n await response.dispose(reason)\n }\n\n const getFinalResponse = async (): Promise<HandlerCallbackResult> => {\n const response = ctx.response\n if (!response) {\n throwRouteHandlerError()\n }\n\n if (!streamResponse) {\n return response\n }\n\n if (response === streamResponse.response) {\n return streamResponse\n }\n\n if (\n streamResponse.response.body !== null &&\n response.body === streamResponse.response.body\n ) {\n return { ...streamResponse, response }\n }\n\n await disposeStreamResponse('middleware response replaced')\n return response\n }\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 === 'response') {\n setResponse(nextCtx.response)\n } else 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 setResponse(err)\n return ctx\n }\n await disposeStreamResponse('middleware error')\n throw err\n }\n\n const normalized = handleCtxResult(result)\n if (normalized) {\n if (normalized.response !== undefined) {\n setResponse(normalized.response)\n }\n if (normalized.context) {\n ctx.context = safeObjectMerge(ctx.context, normalized.context)\n }\n }\n\n return ctx\n }\n\n await next()\n return { ctx, response: await getFinalResponse() }\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 const handlerOptions: FinalManifestOptions =\n typeof cbOrOptions === 'function' ? {} : cbOrOptions\n const cb: HandlerCallback<AnyRouter> =\n typeof cbOrOptions === 'function' ? cbOrOptions : cbOrOptions.handler\n const finalManifestResolver = createFinalManifestResolver({\n ...handlerOptions,\n cacheCreateTransform: process.env.TSS_DEV_SERVER !== 'true',\n })\n const resolveManifestForRequest =\n process.env.TSS_DEV_SERVER === 'true'\n ? finalManifestResolver.resolveUncached\n : finalManifestResolver.resolveCached\n\n if (process.env.TSS_DEV_SERVER !== 'true') {\n finalManifestResolver.warmup({\n getBaseManifest: () => getBaseManifest(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 responseOwnsCleanup = 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 hasStartInstance = !!entries.startEntry.startInstance\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 requestMiddleware: hasStartInstance\n ? startOptions.requestMiddleware\n : [defaultCsrfMiddleware],\n serializationAdapters,\n }\n\n // Flatten request middlewares once\n const flattenedRequestMiddlewares = requestStartOptions.requestMiddleware\n ? flattenMiddlewares(requestStartOptions.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 if (\n process.env.NODE_ENV !== 'production' &&\n process.env.TSS_DISABLE_CSRF_MIDDLEWARE_WARNING !== 'true' &&\n !hasCsrfMiddleware(flattenedRequestMiddlewares)\n ) {\n warnMissingCsrfMiddlewareOnce()\n }\n\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 { response: middlewareResponse } = await executeMiddleware(\n [...middlewares, serverFnHandler],\n {\n request,\n pathname: url.pathname,\n handlerType: 'serverFn',\n context: createNullProtoObject(requestOpts?.context),\n },\n )\n\n const result = await handleRedirectResponse(\n middlewareResponse,\n request,\n getRouter,\n )\n responseOwnsCleanup = result.serverSsrCleanup === 'stream'\n return result.response\n }\n\n // Router execution function\n const executeRouter = async (\n serverContext: TODO,\n matchedRoutes?: ReadonlyArray<AnyRoute>,\n ): Promise<SsrResponse> => {\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 normalizeSsrResponse(\n Response.json(\n { error: 'Only HTML requests are supported here' },\n { status: 500 },\n ),\n )\n }\n\n const manifest = await resolveManifestForRequest({\n request,\n requestInlineCss: requestOpts?.inlineCss,\n getBaseManifest: () => getBaseManifest(matchedRoutes),\n })\n\n const earlyHints = createEarlyHintsForRequest({\n onEarlyHints: requestOpts?.onEarlyHints,\n responseLinkHeader: requestOpts?.responseLinkHeader,\n })\n\n earlyHints?.collectStatic({ manifest, matchedRoutes })\n\n const routerInstance = await getRouter()\n\n attachRouterServerSsrUtils({\n router: routerInstance,\n manifest,\n getRequestAssets: () =>\n getStartContext({ throwIfNotFound: false })?.requestAssets,\n })\n\n routerInstance.update({ additionalContext: { serverContext } })\n await routerInstance.load()\n\n if (routerInstance.state.redirect) {\n return normalizeSsrResponse(routerInstance.state.redirect)\n }\n\n earlyHints?.collectDynamic(routerInstance.stores.matches.get())\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 earlyHints?.appendResponseHeaders(responseHeaders)\n const response = await cb({\n request,\n router: routerInstance,\n responseHeaders,\n })\n return normalizeSsrResponse(response)\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 { response: middlewareResponse } = await executeMiddleware(\n [...middlewares, requestHandlerMiddleware],\n {\n request,\n pathname: url.pathname,\n handlerType: 'router',\n context: createNullProtoObject(requestOpts?.context),\n },\n )\n\n const response = await handleRedirectResponse(\n middlewareResponse,\n request,\n getRouter,\n )\n responseOwnsCleanup = response.serverSsrCleanup === 'stream'\n return response.response\n } finally {\n if (router?.serverSsr && !responseOwnsCleanup) {\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 // Transformed streaming response bodies clean up when consumed/cancelled.\n router.serverSsr.cleanup()\n }\n router = null\n }\n }\n\n return requestHandler(startRequestResolver)\n}\n\nasync function handleRedirectResponse(\n response: HandlerCallbackResult,\n request: Request,\n getRouter: () => Promise<AnyRouter>,\n): Promise<SsrResponse> {\n const ssrResponse = normalizeSsrResponse(response)\n if (!isRedirect(ssrResponse.response)) {\n return ssrResponse\n }\n\n if (isResolvedRedirect(ssrResponse.response)) {\n if (request.headers.get('x-tsr-serverFn') === 'true') {\n return replaceSsrResponse(\n ssrResponse,\n Response.json(\n { ...ssrResponse.response.options, isSerializedRedirect: true },\n { headers: ssrResponse.response.headers },\n ),\n 'redirect response replaced',\n )\n }\n return ssrResponse\n }\n\n const opts = ssrResponse.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(ssrResponse.response)\n\n if (request.headers.get('x-tsr-serverFn') === 'true') {\n return replaceSsrResponse(\n ssrResponse,\n Response.json(\n { ...ssrResponse.response.options, isSerializedRedirect: true },\n { headers: ssrResponse.response.headers },\n ),\n 'redirect response replaced',\n )\n }\n\n return replaceSsrResponse(ssrResponse, redirect, 'redirect response replaced')\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<SsrResponse>\n context: any\n executedRequestMiddlewares: Set<AnyRequestMiddleware>\n}): Promise<SsrResponse> {\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 let isHeadFallback = false\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 // Per RFC 9110 §9.3.2, HEAD must return the same header fields as GET.\n // Priority for HEAD: explicit HEAD handler → GET → ANY (last resort).\n const handler =\n requestMethod === 'HEAD'\n ? (handlers['HEAD'] ?? handlers['GET'] ?? handlers['ANY'])\n : (handlers[requestMethod] ?? handlers['ANY'])\n isHeadFallback =\n requestMethod === 'HEAD' && handler !== undefined && !handlers['HEAD']\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)) as TODO)\n\n const { ctx, response } = await executeMiddleware(routeMiddlewares, {\n request,\n context,\n params: routeParams,\n pathname,\n handlerType: 'router',\n })\n\n // RFC 9110 §9.3.2: HEAD must carry the same header fields as GET but no body.\n // Resolve any redirect before stripping so the Location header survives.\n if (isHeadFallback) {\n if (!ctx.response) {\n throwRouteHandlerError()\n }\n\n const resolved = await handleRedirectResponse(response, request, getRouter)\n return stripSsrResponseBody(resolved, 'HEAD body stripped')\n }\n\n return normalizeSsrResponse(response)\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/start-server-core",
|
|
3
|
-
"version": "1.169.
|
|
3
|
+
"version": "1.169.6",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
"h3-v2": "npm:h3@2.0.1-rc.20",
|
|
71
71
|
"seroval": "^1.5.4",
|
|
72
72
|
"@tanstack/history": "1.162.0",
|
|
73
|
-
"@tanstack/
|
|
74
|
-
"@tanstack/
|
|
75
|
-
"@tanstack/start-
|
|
73
|
+
"@tanstack/start-storage-context": "1.167.9",
|
|
74
|
+
"@tanstack/router-core": "1.171.7",
|
|
75
|
+
"@tanstack/start-client-core": "1.170.5"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@standard-schema/spec": "^1.0.0",
|
|
@@ -16,6 +16,10 @@ import {
|
|
|
16
16
|
attachRouterServerSsrUtils,
|
|
17
17
|
getNormalizedURL,
|
|
18
18
|
getOrigin,
|
|
19
|
+
isSsrResponse,
|
|
20
|
+
normalizeSsrResponse,
|
|
21
|
+
replaceSsrResponse,
|
|
22
|
+
stripSsrResponseBody,
|
|
19
23
|
} from '@tanstack/router-core/ssr/server'
|
|
20
24
|
import {
|
|
21
25
|
getStartContext,
|
|
@@ -48,7 +52,11 @@ import type {
|
|
|
48
52
|
AnySerializationAdapter,
|
|
49
53
|
Register,
|
|
50
54
|
} from '@tanstack/router-core'
|
|
51
|
-
import type {
|
|
55
|
+
import type {
|
|
56
|
+
HandlerCallback,
|
|
57
|
+
HandlerCallbackResult,
|
|
58
|
+
SsrResponse,
|
|
59
|
+
} from '@tanstack/router-core/ssr/server'
|
|
52
60
|
import type { FinalManifestOptions } from './finalManifest'
|
|
53
61
|
|
|
54
62
|
type TODO = any
|
|
@@ -194,7 +202,7 @@ function isSpecialResponse(value: unknown): value is Response {
|
|
|
194
202
|
* Normalize middleware result to context shape
|
|
195
203
|
*/
|
|
196
204
|
function handleCtxResult(result: TODO) {
|
|
197
|
-
if (isSpecialResponse(result)) {
|
|
205
|
+
if (isSsrResponse(result) || isSpecialResponse(result)) {
|
|
198
206
|
return { response: result }
|
|
199
207
|
}
|
|
200
208
|
return result
|
|
@@ -203,8 +211,70 @@ function handleCtxResult(result: TODO) {
|
|
|
203
211
|
/**
|
|
204
212
|
* Execute a middleware chain
|
|
205
213
|
*/
|
|
206
|
-
function executeMiddleware(
|
|
214
|
+
async function executeMiddleware(
|
|
215
|
+
middlewares: Array<TODO>,
|
|
216
|
+
ctx: TODO,
|
|
217
|
+
): Promise<{ ctx: TODO; response: HandlerCallbackResult }> {
|
|
207
218
|
let index = -1
|
|
219
|
+
let streamResponse:
|
|
220
|
+
| Extract<SsrResponse, { serverSsrCleanup: 'stream' }>
|
|
221
|
+
| undefined
|
|
222
|
+
|
|
223
|
+
const setResponse = (response: TODO) => {
|
|
224
|
+
if (isSsrResponse(response)) {
|
|
225
|
+
if (response.serverSsrCleanup === 'stream') {
|
|
226
|
+
streamResponse = response
|
|
227
|
+
}
|
|
228
|
+
ctx.response = response.response
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
ctx.response = response
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const disposeStreamResponse = async (reason: string) => {
|
|
236
|
+
const response = streamResponse
|
|
237
|
+
if (!response) {
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
streamResponse = undefined
|
|
242
|
+
const currentResponse = ctx.response
|
|
243
|
+
if (
|
|
244
|
+
currentResponse === response.response ||
|
|
245
|
+
(currentResponse instanceof Response &&
|
|
246
|
+
response.response.body !== null &&
|
|
247
|
+
currentResponse.body === response.response.body)
|
|
248
|
+
) {
|
|
249
|
+
ctx.response = undefined
|
|
250
|
+
}
|
|
251
|
+
await response.dispose(reason)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const getFinalResponse = async (): Promise<HandlerCallbackResult> => {
|
|
255
|
+
const response = ctx.response
|
|
256
|
+
if (!response) {
|
|
257
|
+
throwRouteHandlerError()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!streamResponse) {
|
|
261
|
+
return response
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (response === streamResponse.response) {
|
|
265
|
+
return streamResponse
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (
|
|
269
|
+
streamResponse.response.body !== null &&
|
|
270
|
+
response.body === streamResponse.response.body
|
|
271
|
+
) {
|
|
272
|
+
return { ...streamResponse, response }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
await disposeStreamResponse('middleware response replaced')
|
|
276
|
+
return response
|
|
277
|
+
}
|
|
208
278
|
|
|
209
279
|
const next = async (nextCtx?: TODO): Promise<TODO> => {
|
|
210
280
|
// Merge context if provided using safeObjectMerge for prototype pollution prevention
|
|
@@ -214,7 +284,9 @@ function executeMiddleware(middlewares: Array<TODO>, ctx: TODO): Promise<TODO> {
|
|
|
214
284
|
}
|
|
215
285
|
// Copy own properties except context (Object.keys returns only own enumerable properties)
|
|
216
286
|
for (const key of Object.keys(nextCtx)) {
|
|
217
|
-
if (key
|
|
287
|
+
if (key === 'response') {
|
|
288
|
+
setResponse(nextCtx.response)
|
|
289
|
+
} else if (key !== 'context') {
|
|
218
290
|
ctx[key] = nextCtx[key]
|
|
219
291
|
}
|
|
220
292
|
}
|
|
@@ -229,16 +301,17 @@ function executeMiddleware(middlewares: Array<TODO>, ctx: TODO): Promise<TODO> {
|
|
|
229
301
|
result = await middleware({ ...ctx, next })
|
|
230
302
|
} catch (err) {
|
|
231
303
|
if (isSpecialResponse(err)) {
|
|
232
|
-
|
|
304
|
+
setResponse(err)
|
|
233
305
|
return ctx
|
|
234
306
|
}
|
|
307
|
+
await disposeStreamResponse('middleware error')
|
|
235
308
|
throw err
|
|
236
309
|
}
|
|
237
310
|
|
|
238
311
|
const normalized = handleCtxResult(result)
|
|
239
312
|
if (normalized) {
|
|
240
313
|
if (normalized.response !== undefined) {
|
|
241
|
-
|
|
314
|
+
setResponse(normalized.response)
|
|
242
315
|
}
|
|
243
316
|
if (normalized.context) {
|
|
244
317
|
ctx.context = safeObjectMerge(ctx.context, normalized.context)
|
|
@@ -248,7 +321,8 @@ function executeMiddleware(middlewares: Array<TODO>, ctx: TODO): Promise<TODO> {
|
|
|
248
321
|
return ctx
|
|
249
322
|
}
|
|
250
323
|
|
|
251
|
-
|
|
324
|
+
await next()
|
|
325
|
+
return { ctx, response: await getFinalResponse() }
|
|
252
326
|
}
|
|
253
327
|
|
|
254
328
|
/**
|
|
@@ -327,7 +401,7 @@ export function createStartHandler<TRegister = Register>(
|
|
|
327
401
|
requestOpts,
|
|
328
402
|
) => {
|
|
329
403
|
let router: AnyRouter | null = null as AnyRouter | null
|
|
330
|
-
let
|
|
404
|
+
let responseOwnsCleanup = false as boolean
|
|
331
405
|
|
|
332
406
|
try {
|
|
333
407
|
// normalizing and sanitizing the pathname here for server, so we always deal with the same format during SSR.
|
|
@@ -447,21 +521,30 @@ export function createStartHandler<TRegister = Register>(
|
|
|
447
521
|
const middlewares = flattenedRequestMiddlewares.map(
|
|
448
522
|
(d) => d.options.server,
|
|
449
523
|
)
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
524
|
+
const { response: middlewareResponse } = await executeMiddleware(
|
|
525
|
+
[...middlewares, serverFnHandler],
|
|
526
|
+
{
|
|
527
|
+
request,
|
|
528
|
+
pathname: url.pathname,
|
|
529
|
+
handlerType: 'serverFn',
|
|
530
|
+
context: createNullProtoObject(requestOpts?.context),
|
|
531
|
+
},
|
|
532
|
+
)
|
|
456
533
|
|
|
457
|
-
|
|
534
|
+
const result = await handleRedirectResponse(
|
|
535
|
+
middlewareResponse,
|
|
536
|
+
request,
|
|
537
|
+
getRouter,
|
|
538
|
+
)
|
|
539
|
+
responseOwnsCleanup = result.serverSsrCleanup === 'stream'
|
|
540
|
+
return result.response
|
|
458
541
|
}
|
|
459
542
|
|
|
460
543
|
// Router execution function
|
|
461
544
|
const executeRouter = async (
|
|
462
545
|
serverContext: TODO,
|
|
463
546
|
matchedRoutes?: ReadonlyArray<AnyRoute>,
|
|
464
|
-
): Promise<
|
|
547
|
+
): Promise<SsrResponse> => {
|
|
465
548
|
const acceptHeader = request.headers.get('Accept') || '*/*'
|
|
466
549
|
const acceptParts = acceptHeader.split(',')
|
|
467
550
|
const supportedMimeTypes = ['*/*', 'text/html']
|
|
@@ -471,9 +554,11 @@ export function createStartHandler<TRegister = Register>(
|
|
|
471
554
|
)
|
|
472
555
|
|
|
473
556
|
if (!isSupported) {
|
|
474
|
-
return
|
|
475
|
-
|
|
476
|
-
|
|
557
|
+
return normalizeSsrResponse(
|
|
558
|
+
Response.json(
|
|
559
|
+
{ error: 'Only HTML requests are supported here' },
|
|
560
|
+
{ status: 500 },
|
|
561
|
+
),
|
|
477
562
|
)
|
|
478
563
|
}
|
|
479
564
|
|
|
@@ -503,7 +588,7 @@ export function createStartHandler<TRegister = Register>(
|
|
|
503
588
|
await routerInstance.load()
|
|
504
589
|
|
|
505
590
|
if (routerInstance.state.redirect) {
|
|
506
|
-
return routerInstance.state.redirect
|
|
591
|
+
return normalizeSsrResponse(routerInstance.state.redirect)
|
|
507
592
|
}
|
|
508
593
|
|
|
509
594
|
earlyHints?.collectDynamic(routerInstance.stores.matches.get())
|
|
@@ -518,13 +603,12 @@ export function createStartHandler<TRegister = Register>(
|
|
|
518
603
|
router: routerInstance,
|
|
519
604
|
})
|
|
520
605
|
earlyHints?.appendResponseHeaders(responseHeaders)
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
return cb({
|
|
606
|
+
const response = await cb({
|
|
524
607
|
request,
|
|
525
608
|
router: routerInstance,
|
|
526
609
|
responseHeaders,
|
|
527
610
|
})
|
|
611
|
+
return normalizeSsrResponse(response)
|
|
528
612
|
}
|
|
529
613
|
|
|
530
614
|
// Main request handler
|
|
@@ -561,7 +645,7 @@ export function createStartHandler<TRegister = Register>(
|
|
|
561
645
|
const middlewares = flattenedRequestMiddlewares.map(
|
|
562
646
|
(d) => d.options.server,
|
|
563
647
|
)
|
|
564
|
-
const
|
|
648
|
+
const { response: middlewareResponse } = await executeMiddleware(
|
|
565
649
|
[...middlewares, requestHandlerMiddleware],
|
|
566
650
|
{
|
|
567
651
|
request,
|
|
@@ -571,14 +655,19 @@ export function createStartHandler<TRegister = Register>(
|
|
|
571
655
|
},
|
|
572
656
|
)
|
|
573
657
|
|
|
574
|
-
|
|
658
|
+
const response = await handleRedirectResponse(
|
|
659
|
+
middlewareResponse,
|
|
660
|
+
request,
|
|
661
|
+
getRouter,
|
|
662
|
+
)
|
|
663
|
+
responseOwnsCleanup = response.serverSsrCleanup === 'stream'
|
|
664
|
+
return response.response
|
|
575
665
|
} finally {
|
|
576
|
-
if (router && !
|
|
666
|
+
if (router?.serverSsr && !responseOwnsCleanup) {
|
|
577
667
|
// Clean up router SSR state if it was set up but won't be cleaned up by the callback
|
|
578
668
|
// (e.g., in redirect cases or early returns before the callback is invoked).
|
|
579
|
-
//
|
|
580
|
-
|
|
581
|
-
router.serverSsr?.cleanup()
|
|
669
|
+
// Transformed streaming response bodies clean up when consumed/cancelled.
|
|
670
|
+
router.serverSsr.cleanup()
|
|
582
671
|
}
|
|
583
672
|
router = null
|
|
584
673
|
}
|
|
@@ -588,25 +677,30 @@ export function createStartHandler<TRegister = Register>(
|
|
|
588
677
|
}
|
|
589
678
|
|
|
590
679
|
async function handleRedirectResponse(
|
|
591
|
-
response:
|
|
680
|
+
response: HandlerCallbackResult,
|
|
592
681
|
request: Request,
|
|
593
682
|
getRouter: () => Promise<AnyRouter>,
|
|
594
|
-
): Promise<
|
|
595
|
-
|
|
596
|
-
|
|
683
|
+
): Promise<SsrResponse> {
|
|
684
|
+
const ssrResponse = normalizeSsrResponse(response)
|
|
685
|
+
if (!isRedirect(ssrResponse.response)) {
|
|
686
|
+
return ssrResponse
|
|
597
687
|
}
|
|
598
688
|
|
|
599
|
-
if (isResolvedRedirect(response)) {
|
|
689
|
+
if (isResolvedRedirect(ssrResponse.response)) {
|
|
600
690
|
if (request.headers.get('x-tsr-serverFn') === 'true') {
|
|
601
|
-
return
|
|
602
|
-
|
|
603
|
-
|
|
691
|
+
return replaceSsrResponse(
|
|
692
|
+
ssrResponse,
|
|
693
|
+
Response.json(
|
|
694
|
+
{ ...ssrResponse.response.options, isSerializedRedirect: true },
|
|
695
|
+
{ headers: ssrResponse.response.headers },
|
|
696
|
+
),
|
|
697
|
+
'redirect response replaced',
|
|
604
698
|
)
|
|
605
699
|
}
|
|
606
|
-
return
|
|
700
|
+
return ssrResponse
|
|
607
701
|
}
|
|
608
702
|
|
|
609
|
-
const opts = response.options
|
|
703
|
+
const opts = ssrResponse.response.options
|
|
610
704
|
if (opts.to && typeof opts.to === 'string' && !opts.to.startsWith('/')) {
|
|
611
705
|
throw new Error(
|
|
612
706
|
`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)}`,
|
|
@@ -629,16 +723,20 @@ async function handleRedirectResponse(
|
|
|
629
723
|
}
|
|
630
724
|
|
|
631
725
|
const router = await getRouter()
|
|
632
|
-
const redirect = router.resolveRedirect(response)
|
|
726
|
+
const redirect = router.resolveRedirect(ssrResponse.response)
|
|
633
727
|
|
|
634
728
|
if (request.headers.get('x-tsr-serverFn') === 'true') {
|
|
635
|
-
return
|
|
636
|
-
|
|
637
|
-
|
|
729
|
+
return replaceSsrResponse(
|
|
730
|
+
ssrResponse,
|
|
731
|
+
Response.json(
|
|
732
|
+
{ ...ssrResponse.response.options, isSerializedRedirect: true },
|
|
733
|
+
{ headers: ssrResponse.response.headers },
|
|
734
|
+
),
|
|
735
|
+
'redirect response replaced',
|
|
638
736
|
)
|
|
639
737
|
}
|
|
640
738
|
|
|
641
|
-
return redirect
|
|
739
|
+
return replaceSsrResponse(ssrResponse, redirect, 'redirect response replaced')
|
|
642
740
|
}
|
|
643
741
|
|
|
644
742
|
async function handleServerRoutes({
|
|
@@ -655,10 +753,10 @@ async function handleServerRoutes({
|
|
|
655
753
|
executeRouter: (
|
|
656
754
|
serverContext: any,
|
|
657
755
|
matchedRoutes?: ReadonlyArray<AnyRoute>,
|
|
658
|
-
) => Promise<
|
|
756
|
+
) => Promise<SsrResponse>
|
|
659
757
|
context: any
|
|
660
758
|
executedRequestMiddlewares: Set<AnyRequestMiddleware>
|
|
661
|
-
}): Promise<
|
|
759
|
+
}): Promise<SsrResponse> {
|
|
662
760
|
const router = await getRouter()
|
|
663
761
|
const rewrittenUrl = executeRewriteInput(router.rewrite, url)
|
|
664
762
|
const pathname = rewrittenUrl.pathname
|
|
@@ -728,11 +826,10 @@ async function handleServerRoutes({
|
|
|
728
826
|
}
|
|
729
827
|
|
|
730
828
|
// Final middleware: execute router with matched routes for dev styles
|
|
731
|
-
routeMiddlewares.push((ctx: TODO) =>
|
|
732
|
-
executeRouter(ctx.context, matchedRoutes)
|
|
733
|
-
)
|
|
829
|
+
routeMiddlewares.push(((ctx: TODO) =>
|
|
830
|
+
executeRouter(ctx.context, matchedRoutes)) as TODO)
|
|
734
831
|
|
|
735
|
-
const ctx = await executeMiddleware(routeMiddlewares, {
|
|
832
|
+
const { ctx, response } = await executeMiddleware(routeMiddlewares, {
|
|
736
833
|
request,
|
|
737
834
|
context,
|
|
738
835
|
params: routeParams,
|
|
@@ -747,13 +844,9 @@ async function handleServerRoutes({
|
|
|
747
844
|
throwRouteHandlerError()
|
|
748
845
|
}
|
|
749
846
|
|
|
750
|
-
const resolved = await handleRedirectResponse(
|
|
751
|
-
|
|
752
|
-
request,
|
|
753
|
-
getRouter,
|
|
754
|
-
)
|
|
755
|
-
return new Response(null, resolved)
|
|
847
|
+
const resolved = await handleRedirectResponse(response, request, getRouter)
|
|
848
|
+
return stripSsrResponseBody(resolved, 'HEAD body stripped')
|
|
756
849
|
}
|
|
757
850
|
|
|
758
|
-
return
|
|
851
|
+
return normalizeSsrResponse(response)
|
|
759
852
|
}
|