@webtypen/webframez-react 0.0.25 → 0.0.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.
package/dist/router.js CHANGED
@@ -11,6 +11,62 @@ import fs from "node:fs";
11
11
  import Module from "node:module";
12
12
  import path from "node:path";
13
13
 
14
+ // src/head.ts
15
+ var ABSOLUTE_ASSET_URL_PATTERN = /^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/|#)/;
16
+ function normalizeHeadBasename(value) {
17
+ const trimmed = value?.trim();
18
+ if (!trimmed) {
19
+ return void 0;
20
+ }
21
+ if (trimmed === "/") {
22
+ return "/";
23
+ }
24
+ return trimmed.replace(/\/+$/, "");
25
+ }
26
+ function resolveHeadAssetUrl(assetUrl, basename) {
27
+ const normalizedAssetUrl = assetUrl.trim();
28
+ const normalizedBasename = normalizeHeadBasename(basename);
29
+ if (!normalizedAssetUrl || !normalizedBasename) {
30
+ return normalizedAssetUrl;
31
+ }
32
+ if (ABSOLUTE_ASSET_URL_PATTERN.test(normalizedAssetUrl)) {
33
+ return normalizedAssetUrl;
34
+ }
35
+ if (normalizedBasename !== "/" && (normalizedAssetUrl === normalizedBasename || normalizedAssetUrl.startsWith(`${normalizedBasename}/`))) {
36
+ return normalizedAssetUrl;
37
+ }
38
+ if (normalizedBasename === "/") {
39
+ return normalizedAssetUrl.startsWith("/") ? normalizedAssetUrl : `/${normalizedAssetUrl}`;
40
+ }
41
+ if (normalizedAssetUrl.startsWith("/")) {
42
+ return `${normalizedBasename}${normalizedAssetUrl}`;
43
+ }
44
+ return `${normalizedBasename}/${normalizedAssetUrl}`;
45
+ }
46
+ function normalizeHeadConfig(head, inheritedBasename) {
47
+ if (!head) {
48
+ return void 0;
49
+ }
50
+ const effectiveBasename = normalizeHeadBasename(head.basename) ?? normalizeHeadBasename(inheritedBasename);
51
+ const normalizedHead = {
52
+ ...head,
53
+ ...effectiveBasename ? { basename: effectiveBasename } : {}
54
+ };
55
+ if (normalizedHead.favicon) {
56
+ normalizedHead.favicon = resolveHeadAssetUrl(
57
+ normalizedHead.favicon,
58
+ effectiveBasename
59
+ );
60
+ }
61
+ if (normalizedHead.links) {
62
+ normalizedHead.links = normalizedHead.links.map((link) => ({
63
+ ...link,
64
+ href: resolveHeadAssetUrl(link.href, effectiveBasename)
65
+ }));
66
+ }
67
+ return normalizedHead;
68
+ }
69
+
14
70
  // src/router-runtime.tsx
15
71
  import React from "react";
16
72
  var ROUTE_CHILDREN_TAG = "webframez-route-children";
@@ -226,7 +282,8 @@ function mergeHead(...configs) {
226
282
  meta: [],
227
283
  links: []
228
284
  };
229
- for (const config of configs) {
285
+ for (const candidate of configs) {
286
+ const config = normalizeHeadConfig(candidate, merged.basename);
230
287
  if (!config) {
231
288
  continue;
232
289
  }
@@ -236,6 +293,9 @@ function mergeHead(...configs) {
236
293
  if (config.description) {
237
294
  merged.description = config.description;
238
295
  }
296
+ if (config.basename) {
297
+ merged.basename = config.basename;
298
+ }
239
299
  if (config.favicon) {
240
300
  merged.favicon = config.favicon;
241
301
  }
@@ -249,22 +309,23 @@ function mergeHead(...configs) {
249
309
  return merged;
250
310
  }
251
311
  function renderHeadToString(head) {
312
+ const normalizedHead = normalizeHeadConfig(head) ?? head;
252
313
  const tags = [];
253
- if (head.description) {
314
+ if (normalizedHead.description) {
254
315
  tags.push(
255
- `<meta ${createManagedAttributes()} name="description" content="${escapeHtml(head.description)}" />`
316
+ `<meta ${createManagedAttributes()} name="description" content="${escapeHtml(normalizedHead.description)}" />`
256
317
  );
257
318
  }
258
- if (head.favicon) {
319
+ if (normalizedHead.favicon) {
259
320
  tags.push(
260
- `<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(head.favicon)}" />`
321
+ `<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(normalizedHead.favicon)}" />`
261
322
  );
262
323
  }
263
- for (const meta of head.meta ?? []) {
324
+ for (const meta of normalizedHead.meta ?? []) {
264
325
  const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
265
326
  tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
266
327
  }
267
- for (const link of head.links ?? []) {
328
+ for (const link of normalizedHead.links ?? []) {
268
329
  const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
269
330
  tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
270
331
  }
@@ -480,6 +541,12 @@ function createFileRouter(options) {
480
541
  params: {},
481
542
  searchParams: input.searchParams,
482
543
  cookies: input.cookies ?? {},
544
+ request: input.request ?? {
545
+ host: null,
546
+ pathname,
547
+ originalPathname: pathname,
548
+ headers: {}
549
+ },
483
550
  abort: (options2) => {
484
551
  throw createAbort(options2);
485
552
  }
package/dist/types.d.ts CHANGED
@@ -2,6 +2,14 @@ import type { ReactNode } from "react";
2
2
 
3
3
  export type RouteParams = Record<string, string | string[]>;
4
4
  export type RouteSearchParams = Record<string, string | string[]>;
5
+ export type RouteRequestHeaders = Record<string, string | string[] | undefined>;
6
+
7
+ export type RouteRequestContext = {
8
+ host: string | null;
9
+ pathname: string;
10
+ originalPathname: string;
11
+ headers: RouteRequestHeaders;
12
+ };
5
13
 
6
14
  export type AbortRouteOptions = {
7
15
  status?: number;
@@ -9,11 +17,12 @@ export type AbortRouteOptions = {
9
17
  payload?: unknown;
10
18
  };
11
19
 
12
- export type RouteContext<TData = unknown> = {
20
+ export type RouteContext<TData = any> = {
13
21
  pathname: string;
14
22
  params: RouteParams;
15
23
  searchParams: RouteSearchParams;
16
24
  cookies: Record<string, string>;
25
+ request: RouteRequestContext;
17
26
  data?: TData;
18
27
  abort: (options?: AbortRouteOptions) => never;
19
28
  };
@@ -22,11 +31,11 @@ export type InferPageData<TDataResolver> =
22
31
  TDataResolver extends (...args: any[]) => any
23
32
  ? Awaited<ReturnType<TDataResolver>>
24
33
  : TDataResolver;
25
- export type PageProps<TData = unknown> = RouteContext<InferPageData<TData>>;
34
+ export type PageProps<TData = any> = RouteContext<InferPageData<TData>>;
26
35
  export type PagePropsFromData<TDataResolver extends (...args: any[]) => any> =
27
36
  PageProps<InferPageData<TDataResolver>>;
28
37
 
29
- export type ErrorPageProps<TData = unknown> = RouteContext<TData> & {
38
+ export type ErrorPageProps<TData = any> = RouteContext<TData> & {
30
39
  statusCode: number;
31
40
  message: string;
32
41
  payload?: unknown;
@@ -51,6 +60,7 @@ export type HeadLinkTag = {
51
60
  export type HeadConfig = {
52
61
  title?: string;
53
62
  description?: string;
63
+ basename?: string;
54
64
  favicon?: string;
55
65
  meta?: HeadMetaTag[];
56
66
  links?: HeadLinkTag[];
@@ -65,18 +75,18 @@ export type HeadResolver<TContext = RouteContext> = (
65
75
  context: TContext
66
76
  ) => HeadConfig | Promise<HeadConfig>;
67
77
 
68
- export type PageDataResolver<TData = unknown> = (
78
+ export type PageDataResolver<TData = any> = (
69
79
  context: RouteContext
70
80
  ) => TData | Promise<TData>;
71
81
 
72
82
  export type RouteMiddlewareResult = Record<string, unknown> | undefined | void;
73
- export type RouteMiddlewareResolver<TData = unknown> = (
83
+ export type RouteMiddlewareResolver<TData = any> = (
74
84
  context: RouteContext<TData>
75
85
  ) => RouteMiddlewareResult | Promise<RouteMiddlewareResult>;
76
86
  export type RouteMiddlewareRegistry = Record<string, RouteMiddlewareResolver<any>>;
77
87
  export type RouteMiddlewareConfig = string | string[];
78
88
 
79
- export type PageModule<TData = unknown> = {
89
+ export type PageModule<TData = any> = {
80
90
  default: (props: PageProps<TData>) => ReactNode | Promise<ReactNode>;
81
91
  Head?: HeadResolver<PageProps<TData>>;
82
92
  Data?: PageDataResolver<TData>;
@@ -153,6 +153,62 @@ var import_node_fs = __toESM(require("node:fs"), 1);
153
153
  var import_node_module2 = __toESM(require("node:module"), 1);
154
154
  var import_node_path2 = __toESM(require("node:path"), 1);
155
155
 
156
+ // src/head.ts
157
+ var ABSOLUTE_ASSET_URL_PATTERN = /^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/|#)/;
158
+ function normalizeHeadBasename(value) {
159
+ const trimmed = value?.trim();
160
+ if (!trimmed) {
161
+ return void 0;
162
+ }
163
+ if (trimmed === "/") {
164
+ return "/";
165
+ }
166
+ return trimmed.replace(/\/+$/, "");
167
+ }
168
+ function resolveHeadAssetUrl(assetUrl, basename) {
169
+ const normalizedAssetUrl = assetUrl.trim();
170
+ const normalizedBasename = normalizeHeadBasename(basename);
171
+ if (!normalizedAssetUrl || !normalizedBasename) {
172
+ return normalizedAssetUrl;
173
+ }
174
+ if (ABSOLUTE_ASSET_URL_PATTERN.test(normalizedAssetUrl)) {
175
+ return normalizedAssetUrl;
176
+ }
177
+ if (normalizedBasename !== "/" && (normalizedAssetUrl === normalizedBasename || normalizedAssetUrl.startsWith(`${normalizedBasename}/`))) {
178
+ return normalizedAssetUrl;
179
+ }
180
+ if (normalizedBasename === "/") {
181
+ return normalizedAssetUrl.startsWith("/") ? normalizedAssetUrl : `/${normalizedAssetUrl}`;
182
+ }
183
+ if (normalizedAssetUrl.startsWith("/")) {
184
+ return `${normalizedBasename}${normalizedAssetUrl}`;
185
+ }
186
+ return `${normalizedBasename}/${normalizedAssetUrl}`;
187
+ }
188
+ function normalizeHeadConfig(head, inheritedBasename) {
189
+ if (!head) {
190
+ return void 0;
191
+ }
192
+ const effectiveBasename = normalizeHeadBasename(head.basename) ?? normalizeHeadBasename(inheritedBasename);
193
+ const normalizedHead = {
194
+ ...head,
195
+ ...effectiveBasename ? { basename: effectiveBasename } : {}
196
+ };
197
+ if (normalizedHead.favicon) {
198
+ normalizedHead.favicon = resolveHeadAssetUrl(
199
+ normalizedHead.favicon,
200
+ effectiveBasename
201
+ );
202
+ }
203
+ if (normalizedHead.links) {
204
+ normalizedHead.links = normalizedHead.links.map((link) => ({
205
+ ...link,
206
+ href: resolveHeadAssetUrl(link.href, effectiveBasename)
207
+ }));
208
+ }
209
+ return normalizedHead;
210
+ }
211
+
156
212
  // src/router-runtime.tsx
157
213
  var import_react = __toESM(require("react"), 1);
158
214
  var ROUTE_CHILDREN_TAG = "webframez-route-children";
@@ -368,7 +424,8 @@ function mergeHead(...configs) {
368
424
  meta: [],
369
425
  links: []
370
426
  };
371
- for (const config of configs) {
427
+ for (const candidate of configs) {
428
+ const config = normalizeHeadConfig(candidate, merged.basename);
372
429
  if (!config) {
373
430
  continue;
374
431
  }
@@ -378,6 +435,9 @@ function mergeHead(...configs) {
378
435
  if (config.description) {
379
436
  merged.description = config.description;
380
437
  }
438
+ if (config.basename) {
439
+ merged.basename = config.basename;
440
+ }
381
441
  if (config.favicon) {
382
442
  merged.favicon = config.favicon;
383
443
  }
@@ -391,22 +451,23 @@ function mergeHead(...configs) {
391
451
  return merged;
392
452
  }
393
453
  function renderHeadToString(head) {
454
+ const normalizedHead = normalizeHeadConfig(head) ?? head;
394
455
  const tags = [];
395
- if (head.description) {
456
+ if (normalizedHead.description) {
396
457
  tags.push(
397
- `<meta ${createManagedAttributes()} name="description" content="${escapeHtml(head.description)}" />`
458
+ `<meta ${createManagedAttributes()} name="description" content="${escapeHtml(normalizedHead.description)}" />`
398
459
  );
399
460
  }
400
- if (head.favicon) {
461
+ if (normalizedHead.favicon) {
401
462
  tags.push(
402
- `<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(head.favicon)}" />`
463
+ `<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(normalizedHead.favicon)}" />`
403
464
  );
404
465
  }
405
- for (const meta of head.meta ?? []) {
466
+ for (const meta of normalizedHead.meta ?? []) {
406
467
  const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
407
468
  tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
408
469
  }
409
- for (const link of head.links ?? []) {
470
+ for (const link of normalizedHead.links ?? []) {
410
471
  const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
411
472
  tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
412
473
  }
@@ -622,6 +683,12 @@ function createFileRouter(options) {
622
683
  params: {},
623
684
  searchParams: input.searchParams,
624
685
  cookies: input.cookies ?? {},
686
+ request: input.request ?? {
687
+ host: null,
688
+ pathname,
689
+ originalPathname: pathname,
690
+ headers: {}
691
+ },
625
692
  abort: (options2) => {
626
693
  throw createAbort(options2);
627
694
  }
@@ -849,6 +916,60 @@ function normalizeBasePath(basePath) {
849
916
  const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
850
917
  return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
851
918
  }
919
+ function normalizeRequestHostValue(value) {
920
+ const trimmed = value.trim();
921
+ if (!trimmed) {
922
+ return null;
923
+ }
924
+ const firstValue = trimmed.split(",")[0]?.trim() || "";
925
+ if (!firstValue) {
926
+ return null;
927
+ }
928
+ if (firstValue.startsWith("[")) {
929
+ const closingIndex = firstValue.indexOf("]");
930
+ if (closingIndex > 0) {
931
+ return firstValue.slice(1, closingIndex).toLowerCase();
932
+ }
933
+ }
934
+ return firstValue.replace(/:\d+$/, "").toLowerCase();
935
+ }
936
+ function getRequestHost(req) {
937
+ const candidates = [
938
+ req.headers["x-forwarded-host"],
939
+ req.headers["x-original-host"],
940
+ req.headers.host
941
+ ];
942
+ for (const candidate of candidates) {
943
+ if (Array.isArray(candidate)) {
944
+ for (const singleValue of candidate) {
945
+ if (typeof singleValue !== "string") {
946
+ continue;
947
+ }
948
+ const normalized2 = normalizeRequestHostValue(singleValue);
949
+ if (normalized2) {
950
+ return normalized2;
951
+ }
952
+ }
953
+ continue;
954
+ }
955
+ if (typeof candidate !== "string") {
956
+ continue;
957
+ }
958
+ const normalized = normalizeRequestHostValue(candidate);
959
+ if (normalized) {
960
+ return normalized;
961
+ }
962
+ }
963
+ return null;
964
+ }
965
+ function createRouteRequestContext(req, pathname, originalPathname) {
966
+ return {
967
+ host: getRequestHost(req),
968
+ pathname,
969
+ originalPathname,
970
+ headers: req.headers
971
+ };
972
+ }
852
973
  function stripBasePath(pathname, basePath) {
853
974
  if (!basePath) {
854
975
  return pathname;
@@ -1194,13 +1315,19 @@ function createNodeRequestHandler(options) {
1194
1315
  }
1195
1316
  if (url.pathname === rscPath) {
1196
1317
  const pathname = stripBasePath(url.searchParams.get("path") || "/", basePath);
1318
+ const requestContext = createRouteRequestContext(
1319
+ req,
1320
+ pathname,
1321
+ url.searchParams.get("path") || "/"
1322
+ );
1197
1323
  const search = new URLSearchParams(url.searchParams.get("search") || "");
1198
1324
  const resolved2 = await withRequestBasename(
1199
1325
  basePath,
1200
1326
  () => router.resolve({
1201
1327
  pathname,
1202
1328
  searchParams: parseSearchParams(search),
1203
- cookies: requestCookies
1329
+ cookies: requestCookies,
1330
+ request: requestContext
1204
1331
  })
1205
1332
  );
1206
1333
  const payload = {
@@ -1240,7 +1367,12 @@ function createNodeRequestHandler(options) {
1240
1367
  () => router.resolve({
1241
1368
  pathname: stripBasePath(url.pathname, basePath),
1242
1369
  searchParams: parseSearchParams(url.searchParams),
1243
- cookies: requestCookies
1370
+ cookies: requestCookies,
1371
+ request: createRouteRequestContext(
1372
+ req,
1373
+ stripBasePath(url.pathname, basePath),
1374
+ url.pathname
1375
+ )
1244
1376
  })
1245
1377
  );
1246
1378
  const initialPayload = {
@@ -1284,7 +1416,7 @@ function createNodeRequestHandler(options) {
1284
1416
  rscEndpoint: rscPath,
1285
1417
  rootHtml: createInitialHtmlErrorMarkup("Initial HTML render failed."),
1286
1418
  initialFlightData,
1287
- basename: basePath,
1419
+ basename: resolved?.head?.basename ?? basePath,
1288
1420
  liveReloadPath: liveReloadPath || void 0,
1289
1421
  liveReloadServerId: liveReloadPath ? devServerId : void 0
1290
1422
  })
@@ -1303,7 +1435,7 @@ function createNodeRequestHandler(options) {
1303
1435
  rscEndpoint: rscPath,
1304
1436
  rootHtml,
1305
1437
  initialFlightData,
1306
- basename: basePath,
1438
+ basename: resolved.head.basename ?? basePath,
1307
1439
  liveReloadPath: liveReloadPath || void 0,
1308
1440
  liveReloadServerId: liveReloadPath ? devServerId : void 0
1309
1441
  })
@@ -125,6 +125,62 @@ import fs from "node:fs";
125
125
  import Module from "node:module";
126
126
  import path2 from "node:path";
127
127
 
128
+ // src/head.ts
129
+ var ABSOLUTE_ASSET_URL_PATTERN = /^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/|#)/;
130
+ function normalizeHeadBasename(value) {
131
+ const trimmed = value?.trim();
132
+ if (!trimmed) {
133
+ return void 0;
134
+ }
135
+ if (trimmed === "/") {
136
+ return "/";
137
+ }
138
+ return trimmed.replace(/\/+$/, "");
139
+ }
140
+ function resolveHeadAssetUrl(assetUrl, basename) {
141
+ const normalizedAssetUrl = assetUrl.trim();
142
+ const normalizedBasename = normalizeHeadBasename(basename);
143
+ if (!normalizedAssetUrl || !normalizedBasename) {
144
+ return normalizedAssetUrl;
145
+ }
146
+ if (ABSOLUTE_ASSET_URL_PATTERN.test(normalizedAssetUrl)) {
147
+ return normalizedAssetUrl;
148
+ }
149
+ if (normalizedBasename !== "/" && (normalizedAssetUrl === normalizedBasename || normalizedAssetUrl.startsWith(`${normalizedBasename}/`))) {
150
+ return normalizedAssetUrl;
151
+ }
152
+ if (normalizedBasename === "/") {
153
+ return normalizedAssetUrl.startsWith("/") ? normalizedAssetUrl : `/${normalizedAssetUrl}`;
154
+ }
155
+ if (normalizedAssetUrl.startsWith("/")) {
156
+ return `${normalizedBasename}${normalizedAssetUrl}`;
157
+ }
158
+ return `${normalizedBasename}/${normalizedAssetUrl}`;
159
+ }
160
+ function normalizeHeadConfig(head, inheritedBasename) {
161
+ if (!head) {
162
+ return void 0;
163
+ }
164
+ const effectiveBasename = normalizeHeadBasename(head.basename) ?? normalizeHeadBasename(inheritedBasename);
165
+ const normalizedHead = {
166
+ ...head,
167
+ ...effectiveBasename ? { basename: effectiveBasename } : {}
168
+ };
169
+ if (normalizedHead.favicon) {
170
+ normalizedHead.favicon = resolveHeadAssetUrl(
171
+ normalizedHead.favicon,
172
+ effectiveBasename
173
+ );
174
+ }
175
+ if (normalizedHead.links) {
176
+ normalizedHead.links = normalizedHead.links.map((link) => ({
177
+ ...link,
178
+ href: resolveHeadAssetUrl(link.href, effectiveBasename)
179
+ }));
180
+ }
181
+ return normalizedHead;
182
+ }
183
+
128
184
  // src/router-runtime.tsx
129
185
  import React from "react";
130
186
  var ROUTE_CHILDREN_TAG = "webframez-route-children";
@@ -340,7 +396,8 @@ function mergeHead(...configs) {
340
396
  meta: [],
341
397
  links: []
342
398
  };
343
- for (const config of configs) {
399
+ for (const candidate of configs) {
400
+ const config = normalizeHeadConfig(candidate, merged.basename);
344
401
  if (!config) {
345
402
  continue;
346
403
  }
@@ -350,6 +407,9 @@ function mergeHead(...configs) {
350
407
  if (config.description) {
351
408
  merged.description = config.description;
352
409
  }
410
+ if (config.basename) {
411
+ merged.basename = config.basename;
412
+ }
353
413
  if (config.favicon) {
354
414
  merged.favicon = config.favicon;
355
415
  }
@@ -363,22 +423,23 @@ function mergeHead(...configs) {
363
423
  return merged;
364
424
  }
365
425
  function renderHeadToString(head) {
426
+ const normalizedHead = normalizeHeadConfig(head) ?? head;
366
427
  const tags = [];
367
- if (head.description) {
428
+ if (normalizedHead.description) {
368
429
  tags.push(
369
- `<meta ${createManagedAttributes()} name="description" content="${escapeHtml(head.description)}" />`
430
+ `<meta ${createManagedAttributes()} name="description" content="${escapeHtml(normalizedHead.description)}" />`
370
431
  );
371
432
  }
372
- if (head.favicon) {
433
+ if (normalizedHead.favicon) {
373
434
  tags.push(
374
- `<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(head.favicon)}" />`
435
+ `<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(normalizedHead.favicon)}" />`
375
436
  );
376
437
  }
377
- for (const meta of head.meta ?? []) {
438
+ for (const meta of normalizedHead.meta ?? []) {
378
439
  const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
379
440
  tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
380
441
  }
381
- for (const link of head.links ?? []) {
442
+ for (const link of normalizedHead.links ?? []) {
382
443
  const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
383
444
  tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
384
445
  }
@@ -594,6 +655,12 @@ function createFileRouter(options) {
594
655
  params: {},
595
656
  searchParams: input.searchParams,
596
657
  cookies: input.cookies ?? {},
658
+ request: input.request ?? {
659
+ host: null,
660
+ pathname,
661
+ originalPathname: pathname,
662
+ headers: {}
663
+ },
597
664
  abort: (options2) => {
598
665
  throw createAbort(options2);
599
666
  }
@@ -821,6 +888,60 @@ function normalizeBasePath(basePath) {
821
888
  const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
822
889
  return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
823
890
  }
891
+ function normalizeRequestHostValue(value) {
892
+ const trimmed = value.trim();
893
+ if (!trimmed) {
894
+ return null;
895
+ }
896
+ const firstValue = trimmed.split(",")[0]?.trim() || "";
897
+ if (!firstValue) {
898
+ return null;
899
+ }
900
+ if (firstValue.startsWith("[")) {
901
+ const closingIndex = firstValue.indexOf("]");
902
+ if (closingIndex > 0) {
903
+ return firstValue.slice(1, closingIndex).toLowerCase();
904
+ }
905
+ }
906
+ return firstValue.replace(/:\d+$/, "").toLowerCase();
907
+ }
908
+ function getRequestHost(req) {
909
+ const candidates = [
910
+ req.headers["x-forwarded-host"],
911
+ req.headers["x-original-host"],
912
+ req.headers.host
913
+ ];
914
+ for (const candidate of candidates) {
915
+ if (Array.isArray(candidate)) {
916
+ for (const singleValue of candidate) {
917
+ if (typeof singleValue !== "string") {
918
+ continue;
919
+ }
920
+ const normalized2 = normalizeRequestHostValue(singleValue);
921
+ if (normalized2) {
922
+ return normalized2;
923
+ }
924
+ }
925
+ continue;
926
+ }
927
+ if (typeof candidate !== "string") {
928
+ continue;
929
+ }
930
+ const normalized = normalizeRequestHostValue(candidate);
931
+ if (normalized) {
932
+ return normalized;
933
+ }
934
+ }
935
+ return null;
936
+ }
937
+ function createRouteRequestContext(req, pathname, originalPathname) {
938
+ return {
939
+ host: getRequestHost(req),
940
+ pathname,
941
+ originalPathname,
942
+ headers: req.headers
943
+ };
944
+ }
824
945
  function stripBasePath(pathname, basePath) {
825
946
  if (!basePath) {
826
947
  return pathname;
@@ -1166,13 +1287,19 @@ function createNodeRequestHandler(options) {
1166
1287
  }
1167
1288
  if (url.pathname === rscPath) {
1168
1289
  const pathname = stripBasePath(url.searchParams.get("path") || "/", basePath);
1290
+ const requestContext = createRouteRequestContext(
1291
+ req,
1292
+ pathname,
1293
+ url.searchParams.get("path") || "/"
1294
+ );
1169
1295
  const search = new URLSearchParams(url.searchParams.get("search") || "");
1170
1296
  const resolved2 = await withRequestBasename(
1171
1297
  basePath,
1172
1298
  () => router.resolve({
1173
1299
  pathname,
1174
1300
  searchParams: parseSearchParams(search),
1175
- cookies: requestCookies
1301
+ cookies: requestCookies,
1302
+ request: requestContext
1176
1303
  })
1177
1304
  );
1178
1305
  const payload = {
@@ -1212,7 +1339,12 @@ function createNodeRequestHandler(options) {
1212
1339
  () => router.resolve({
1213
1340
  pathname: stripBasePath(url.pathname, basePath),
1214
1341
  searchParams: parseSearchParams(url.searchParams),
1215
- cookies: requestCookies
1342
+ cookies: requestCookies,
1343
+ request: createRouteRequestContext(
1344
+ req,
1345
+ stripBasePath(url.pathname, basePath),
1346
+ url.pathname
1347
+ )
1216
1348
  })
1217
1349
  );
1218
1350
  const initialPayload = {
@@ -1256,7 +1388,7 @@ function createNodeRequestHandler(options) {
1256
1388
  rscEndpoint: rscPath,
1257
1389
  rootHtml: createInitialHtmlErrorMarkup("Initial HTML render failed."),
1258
1390
  initialFlightData,
1259
- basename: basePath,
1391
+ basename: resolved?.head?.basename ?? basePath,
1260
1392
  liveReloadPath: liveReloadPath || void 0,
1261
1393
  liveReloadServerId: liveReloadPath ? devServerId : void 0
1262
1394
  })
@@ -1275,7 +1407,7 @@ function createNodeRequestHandler(options) {
1275
1407
  rscEndpoint: rscPath,
1276
1408
  rootHtml,
1277
1409
  initialFlightData,
1278
- basename: basePath,
1410
+ basename: resolved.head.basename ?? basePath,
1279
1411
  liveReloadPath: liveReloadPath || void 0,
1280
1412
  liveReloadServerId: liveReloadPath ? devServerId : void 0
1281
1413
  })