vinext 1.4.0 → 1.4.1

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.
Files changed (104) hide show
  1. package/README.md +20 -20
  2. package/dist/build/prerender.js +3 -2
  3. package/dist/check.js +3 -6
  4. package/dist/client/dev-error-overlay-store.d.ts +1 -0
  5. package/dist/client/dev-error-overlay.d.ts +2 -0
  6. package/dist/client/dev-error-overlay.js +19 -3
  7. package/dist/config/config-matchers.js +2 -0
  8. package/dist/config/destination-params.d.ts +2 -1
  9. package/dist/config/destination-params.js +52 -2
  10. package/dist/config/next-config.d.ts +6 -0
  11. package/dist/config/next-config.js +18 -3
  12. package/dist/entries/app-rsc-entry.js +0 -1
  13. package/dist/entries/pages-server-entry.js +3 -1
  14. package/dist/index.js +29 -20
  15. package/dist/plugins/strip-server-exports.js +2 -1
  16. package/dist/server/api-handler.js +2 -1
  17. package/dist/server/app-browser-entry.js +7 -3
  18. package/dist/server/app-browser-rsc-redirect.js +3 -3
  19. package/dist/server/app-browser-server-action-client.js +2 -2
  20. package/dist/server/app-browser-visible-commit.js +2 -2
  21. package/dist/server/app-optimistic-routing.js +2 -3
  22. package/dist/server/app-page-boundary-render.js +5 -10
  23. package/dist/server/app-page-dispatch.d.ts +0 -1
  24. package/dist/server/app-page-dispatch.js +0 -1
  25. package/dist/server/app-page-element-builder.js +3 -3
  26. package/dist/server/app-page-execution.js +9 -6
  27. package/dist/server/app-page-render.d.ts +0 -1
  28. package/dist/server/app-page-render.js +14 -14
  29. package/dist/server/app-page-response.js +1 -1
  30. package/dist/server/app-page-route-wiring.js +22 -5
  31. package/dist/server/app-page-stream.d.ts +10 -8
  32. package/dist/server/app-page-stream.js +2 -1
  33. package/dist/server/app-request-stage-independent-entry.js +1 -1
  34. package/dist/server/app-route-handler-execution.js +3 -2
  35. package/dist/server/app-route-handler-runtime.d.ts +1 -1
  36. package/dist/server/app-route-handler-runtime.js +11 -7
  37. package/dist/server/app-router-entry.js +1 -1
  38. package/dist/server/app-rsc-cache-busting.d.ts +10 -10
  39. package/dist/server/app-rsc-cache-busting.js +27 -68
  40. package/dist/server/app-rsc-error-handler.js +1 -1
  41. package/dist/server/app-rsc-errors.js +4 -1
  42. package/dist/server/app-rsc-handler.js +53 -62
  43. package/dist/server/app-rsc-request-normalization.d.ts +10 -12
  44. package/dist/server/app-rsc-request-normalization.js +11 -14
  45. package/dist/server/app-rsc-response-stage.js +4 -12
  46. package/dist/server/app-server-action-execution.js +8 -4
  47. package/dist/server/app-ssr-entry.d.ts +6 -0
  48. package/dist/server/app-ssr-entry.js +13 -8
  49. package/dist/server/app-worker-stages.d.ts +12 -3
  50. package/dist/server/app-worker-stages.js +37 -4
  51. package/dist/server/bfcache-identity.d.ts +7 -0
  52. package/dist/server/bfcache-identity.js +10 -1
  53. package/dist/server/cacheability-manifest.js +1 -2
  54. package/dist/server/dev-origin-check.d.ts +2 -1
  55. package/dist/server/dev-origin-check.js +2 -1
  56. package/dist/server/dev-server.js +2 -1
  57. package/dist/server/image-optimization.d.ts +5 -0
  58. package/dist/server/image-optimization.js +6 -1
  59. package/dist/server/isr-cache.js +4 -1
  60. package/dist/server/metadata-route-response.js +1 -1
  61. package/dist/server/middleware-matcher.js +19 -6
  62. package/dist/server/middleware-runtime.js +6 -2
  63. package/dist/server/multi-stage.d.ts +7 -3
  64. package/dist/server/pages-node-compat.d.ts +1 -1
  65. package/dist/server/pages-node-compat.js +2 -0
  66. package/dist/server/pages-page-data.js +2 -1
  67. package/dist/server/pages-page-handler.js +17 -6
  68. package/dist/server/pages-page-response.d.ts +9 -0
  69. package/dist/server/pages-page-response.js +18 -4
  70. package/dist/server/pages-request-stage-entry.js +24 -4
  71. package/dist/server/pages-router-entry.js +3 -2
  72. package/dist/server/prod-server.d.ts +2 -2
  73. package/dist/server/prod-server.js +42 -8
  74. package/dist/server/request-error-log.d.ts +9 -0
  75. package/dist/server/request-error-log.js +13 -0
  76. package/dist/server/request-pipeline.d.ts +9 -4
  77. package/dist/server/request-pipeline.js +23 -15
  78. package/dist/server/rsc-completion-metadata.js +3 -0
  79. package/dist/server/static-file-cache.d.ts +9 -0
  80. package/dist/server/static-file-cache.js +13 -2
  81. package/dist/shims/cache-request-state.d.ts +6 -6
  82. package/dist/shims/cache-request-state.js +6 -36
  83. package/dist/shims/cache-runtime.d.ts +3 -1
  84. package/dist/shims/cache-runtime.js +77 -47
  85. package/dist/shims/cache.d.ts +10 -5
  86. package/dist/shims/cache.js +35 -22
  87. package/dist/shims/client-hook-error.d.ts +2 -0
  88. package/dist/shims/client-hook-error.js +16 -1
  89. package/dist/shims/fetch-cache.js +6 -2
  90. package/dist/shims/headers.d.ts +2 -1
  91. package/dist/shims/headers.js +5 -3
  92. package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
  93. package/dist/shims/link.js +5 -10
  94. package/dist/shims/navigation.js +2 -3
  95. package/dist/shims/request-state-types.d.ts +2 -1
  96. package/dist/shims/slot.d.ts +6 -2
  97. package/dist/shims/slot.js +10 -3
  98. package/dist/shims/unified-request-context.d.ts +6 -0
  99. package/dist/shims/unified-request-context.js +1 -0
  100. package/dist/utils/cache-life-profiles.d.ts +33 -0
  101. package/dist/utils/cache-life-profiles.js +111 -0
  102. package/dist/utils/middleware-request-headers.d.ts +14 -3
  103. package/dist/utils/middleware-request-headers.js +54 -7
  104. package/package.json +1 -1
package/README.md CHANGED
@@ -659,26 +659,26 @@ Every `next/*` import is shimmed to a Vite-compatible implementation.
659
659
 
660
660
  ### Server features
661
661
 
662
- | Feature | | Notes |
663
- | ------------------------------------------ | --- | ------------------------------------------------------------------------------------------- |
664
- | SSR (Pages Router) | ✅ | Streaming, `_app`/`_document`, `__NEXT_DATA__`, hydration |
665
- | SSR (App Router) | ✅ | RSC pipeline, nested layouts, streaming, nav context for client components |
666
- | `getStaticProps` | ✅ | Props, redirect, notFound, revalidate |
667
- | `getStaticPaths` | ✅ | `fallback: false`, `true`, `"blocking"` |
668
- | `getServerSideProps` | ✅ | Full context including locale |
669
- | ISR | ✅ | Stale-while-revalidate, pluggable `CacheHandler`, background regeneration |
670
- | Server Actions (`"use server"`) | ✅ | Action execution, FormData, re-render after mutation, `redirect()` in actions |
671
- | React Server Components | ✅ | Via `@vitejs/plugin-rsc`. `"use client"` boundaries work correctly |
672
- | Streaming SSR | ✅ | Both routers |
673
- | Metadata API | ✅ | `metadata`, `generateMetadata`, `viewport`, `generateViewport`, title templates |
674
- | `generateStaticParams` | ✅ | With `dynamicParams` enforcement |
675
- | Metadata file routes | ✅ | sitemap.xml, robots.txt, manifest, favicon, OG images (static + dynamic) |
676
- | Static export (`output: 'export'`) | ✅ | Generates static HTML/JSON for all routes |
677
- | Standalone output (`output: 'standalone'`) | ✅ | Generates `dist/standalone` with `server.js`, build artifacts, and runtime deps |
678
- | `connection()` | ✅ | Forces dynamic rendering |
679
- | `"use cache"` directive | ✅ | File-level and function-level. `cacheLife()` profiles, `cacheTag()`, stale-while-revalidate |
680
- | `instrumentation.ts` | ✅ | `register()`, `onRequestError()`, and [framework tracing](docs/tracing.mdx) |
681
- | Route segment config | 🟡 | `revalidate`, `dynamic`, `dynamicParams`, `runtime` (not placement). No `preferredRegion` |
662
+ | Feature | | Notes |
663
+ | ------------------------------------------ | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
664
+ | SSR (Pages Router) | ✅ | Streaming, `_app`/`_document`, `__NEXT_DATA__`, hydration |
665
+ | SSR (App Router) | ✅ | RSC pipeline, nested layouts, streaming, nav context for client components |
666
+ | `getStaticProps` | ✅ | Props, redirect, notFound, revalidate |
667
+ | `getStaticPaths` | ✅ | `fallback: false`, `true`, `"blocking"` |
668
+ | `getServerSideProps` | ✅ | Full context including locale |
669
+ | ISR | ✅ | Stale-while-revalidate, pluggable `CacheHandler`, background regeneration |
670
+ | Server Actions (`"use server"`) | ✅ | Action execution, FormData, re-render after mutation, `redirect()` in actions |
671
+ | React Server Components | ✅ | Via `@vitejs/plugin-rsc`. `"use client"` boundaries work correctly |
672
+ | Streaming SSR | ✅ | Both routers |
673
+ | Metadata API | ✅ | `metadata`, `generateMetadata`, `viewport`, `generateViewport`, title templates |
674
+ | `generateStaticParams` | ✅ | With `dynamicParams` enforcement |
675
+ | Metadata file routes | ✅ | sitemap.xml, robots.txt, manifest, favicon, OG images (static + dynamic) |
676
+ | Static export (`output: 'export'`) | ✅ | Generates static HTML/JSON for all routes |
677
+ | Standalone output (`output: 'standalone'`) | ✅ | Generates `dist/standalone` with `server.js`, build artifacts, and runtime deps |
678
+ | `connection()` | ✅ | Forces dynamic rendering |
679
+ | `"use cache"` directive | ✅ | File-level and function-level. `cacheLife()` with built-in and next.config `cacheLife` (or legacy `experimental.cacheLife`) profiles, `cacheTag()`, stale-while-revalidate |
680
+ | `instrumentation.ts` | ✅ | `register()`, `onRequestError()`, and [framework tracing](docs/tracing.mdx) |
681
+ | Route segment config | 🟡 | `revalidate`, `dynamic`, `dynamicParams`, `runtime` (not placement). No `preferredRegion` |
682
682
 
683
683
  ### Configuration
684
684
 
@@ -608,12 +608,13 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
608
608
  fs.writeFileSync(htmlFullPath, html, "utf-8");
609
609
  outputFiles.push(htmlOutputPath);
610
610
  }
611
+ const renderedRevalidate = isStaticPropsResponse ? readPrerenderCacheLifeHeader(response.headers)?.revalidate ?? false : revalidate;
611
612
  result = {
612
613
  route: route.pattern,
613
614
  status: "rendered",
614
615
  outputFiles,
615
- revalidate,
616
- ...typeof revalidate === "number" ? { expire: config.expireTime } : {},
616
+ revalidate: renderedRevalidate,
617
+ ...typeof renderedRevalidate === "number" ? { expire: config.expireTime } : {},
617
618
  router: "pages",
618
619
  ...urlPath !== route.pattern ? { path: urlPath } : {},
619
620
  ...locale ? { locale } : {},
package/dist/check.js CHANGED
@@ -243,13 +243,10 @@ const CONFIG_SUPPORT = {
243
243
  status: "unsupported",
244
244
  detail: "ignored; configure cache adapters with vinext({ cache }) in vite.config"
245
245
  },
246
- cacheLife: {
247
- status: "unsupported",
248
- detail: "custom cacheLife profiles are ignored; only the built-in profiles are available"
249
- },
246
+ cacheLife: { status: "supported" },
250
247
  "experimental.cacheLife": {
251
- status: "unsupported",
252
- detail: "custom cacheLife profiles are ignored; only the built-in profiles are available"
248
+ status: "supported",
249
+ detail: "legacy alias of cacheLife"
253
250
  },
254
251
  skipTrailingSlashRedirect: { status: "supported" },
255
252
  "experimental.skipTrailingSlashRedirect": {
@@ -3,6 +3,7 @@ export type Source = "server" | "vite" | "uncaught" | "caught" | "window-error"
3
3
  export type ReportedError = {
4
4
  id: number;
5
5
  source: Source;
6
+ name: string | undefined;
6
7
  message: string;
7
8
  stack: string | undefined;
8
9
  ignoredStackFrames: boolean[] | undefined;
@@ -1,9 +1,11 @@
1
1
  import { ReportedError, dismissOverlay } from "./dev-error-overlay-store.js";
2
+ import { InitialDevServerErrorPayload } from "../server/dev-initial-server-error.js";
2
3
  //#region src/client/dev-error-overlay.d.ts
3
4
  export declare const DEV_ERROR_OVERLAY_HOST_ID = "__vinext_dev_error_overlay_root";
4
5
  export declare const DEV_ERROR_OVERLAY_MOUNT_ID = "__vinext_dev_error_overlay_mount";
5
6
  export declare function installDevErrorOverlay(): void;
6
7
  export declare function installReactRefreshErrorRecovery(): void;
8
+ export declare function selectUnreportedInitialDevServerErrors(payloads: readonly InitialDevServerErrorPayload[], reported: readonly ReportedError[]): InitialDevServerErrorPayload[];
7
9
  export declare function reportInitialDevServerErrors(): void;
8
10
  type ViteHmrErrorPayload = {
9
11
  err?: ViteHmrError;
@@ -11,6 +11,7 @@ const DEV_ERROR_OVERLAY_MOUNT_ID = "__vinext_dev_error_overlay_mount";
11
11
  const VITE_ERROR_HANDLER_DATA_KEY = "__vinext_vite_error_handler__";
12
12
  const REACT_REFRESH_RECOVERY_RETRY_DELAY_MS = 16;
13
13
  let reactRoot = null;
14
+ let overlayHost = null;
14
15
  let installed = false;
15
16
  const reportedErrors = /* @__PURE__ */ new WeakSet();
16
17
  function rememberReported(error) {
@@ -81,12 +82,21 @@ function tryInstallReactRefreshErrorRecovery() {
81
82
  });
82
83
  return true;
83
84
  }
85
+ function selectUnreportedInitialDevServerErrors(payloads, reported) {
86
+ const rethrownErrors = reported.filter((error) => error.source === "caught" || error.source === "uncaught" || error.source === "window-error");
87
+ return payloads.filter((payload) => {
88
+ const rethrownIndex = rethrownErrors.findIndex((error) => error.message === payload.message && (error.name || "Error") === (payload.name || "Error"));
89
+ if (rethrownIndex === -1) return true;
90
+ rethrownErrors.splice(rethrownIndex, 1);
91
+ return false;
92
+ });
93
+ }
84
94
  function reportInitialDevServerErrors() {
85
95
  if (typeof window === "undefined") return;
86
96
  const errors = window.__VINEXT_INITIAL_DEV_ERRORS__;
87
97
  if (!errors || errors.length === 0) return;
88
98
  window.__VINEXT_INITIAL_DEV_ERRORS__ = [];
89
- for (const payload of errors) {
99
+ for (const payload of selectUnreportedInitialDevServerErrors(errors, getOverlaySnapshot().errors)) {
90
100
  const error = new Error(payload.message);
91
101
  if (payload.name) error.name = payload.name;
92
102
  if (payload.stack) error.stack = payload.stack;
@@ -130,6 +140,7 @@ function reportDevError(error, options) {
130
140
  ensureMounted();
131
141
  const id = reportToOverlay({
132
142
  source: options.source,
143
+ name: error instanceof Error ? error.name : void 0,
133
144
  message,
134
145
  stack,
135
146
  ignoredStackFrames: void 0,
@@ -176,8 +187,13 @@ function stringValue(value) {
176
187
  return typeof value === "string" && value.length > 0 ? value : void 0;
177
188
  }
178
189
  function ensureMounted() {
179
- if (reactRoot) return;
190
+ if (reactRoot) {
191
+ const host = overlayHost;
192
+ if (host && !host.isConnected) (document.body ?? document.documentElement).appendChild(host);
193
+ return;
194
+ }
180
195
  const node = createDevErrorOverlayMountNode(document);
196
+ overlayHost = document.getElementById(DEV_ERROR_OVERLAY_HOST_ID);
181
197
  reactRoot = createRoot(node);
182
198
  reactRoot.render(/* @__PURE__ */ jsx(DevErrorOverlayApp, {}));
183
199
  }
@@ -1342,4 +1358,4 @@ const componentStackStyle = {
1342
1358
  color: "var(--vinext-overlay-muted)"
1343
1359
  };
1344
1360
  //#endregion
1345
- export { DEV_ERROR_OVERLAY_HOST_ID, DEV_ERROR_OVERLAY_MOUNT_ID, createDevErrorOverlayMountNode, createViteOpenInEditorUrl, devOnCaughtError, devOnUncaughtError, dismissOverlay, formatErrorInfoForClipboard, formatOverlayDisplayFile, formatViteOpenInEditorFile, installDevErrorOverlay, installReactRefreshErrorRecovery, installViteHmrErrorHandler, normalizeViteHmrError, reportInitialDevServerErrors };
1361
+ export { DEV_ERROR_OVERLAY_HOST_ID, DEV_ERROR_OVERLAY_MOUNT_ID, createDevErrorOverlayMountNode, createViteOpenInEditorUrl, devOnCaughtError, devOnUncaughtError, dismissOverlay, formatErrorInfoForClipboard, formatOverlayDisplayFile, formatViteOpenInEditorFile, installDevErrorOverlay, installReactRefreshErrorRecovery, installViteHmrErrorHandler, normalizeViteHmrError, reportInitialDevServerErrors, selectUnreportedInitialDevServerErrors };
@@ -759,8 +759,10 @@ async function proxyExternalRequest(request, externalUrl) {
759
759
  const destinationKeys = new Set(targetUrl.searchParams.keys());
760
760
  for (const [key, value] of originalUrl.searchParams) if (!destinationKeys.has(key)) targetUrl.searchParams.append(key, value);
761
761
  const headers = new Headers(request.headers);
762
+ const requestHost = headers.get("host") ?? "";
762
763
  headers.set("host", targetUrl.host);
763
764
  stripHopByHopRequestHeaders(headers);
765
+ headers.set("x-forwarded-host", requestHost);
764
766
  const keysToDelete = [];
765
767
  for (const key of headers.keys()) if (key.startsWith("x-middleware-") && !key.startsWith("x-middleware-request-")) keysToDelete.push(key);
766
768
  for (const key of keysToDelete) headers.delete(key);
@@ -25,7 +25,8 @@
25
25
  * `%XX` escapes a raw path capture already carries. A decoded `has` capture
26
26
  * carrying `#`, `/`, `?`, `@` or `:` then makes the host invalid instead of
27
27
  * moving the proxy to another host. Next.js substitutes the hostname verbatim
28
- * and can be retargeted this way; vinext is stricter.
28
+ * and can be retargeted this way; vinext is stricter. Params in a rewrite's
29
+ * path follow substituteRewritePath.
29
30
  *
30
31
  * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
31
32
  * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/server-route-utils.ts
@@ -33,7 +33,8 @@ const _compiledDestinationParamCache = /* @__PURE__ */ new Map();
33
33
  * `%XX` escapes a raw path capture already carries. A decoded `has` capture
34
34
  * carrying `#`, `/`, `?`, `@` or `:` then makes the host invalid instead of
35
35
  * moving the proxy to another host. Next.js substitutes the hostname verbatim
36
- * and can be retargeted this way; vinext is stricter.
36
+ * and can be retargeted this way; vinext is stricter. Params in a rewrite's
37
+ * path follow substituteRewritePath.
37
38
  *
38
39
  * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
39
40
  * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/server-route-utils.ts
@@ -49,7 +50,56 @@ function substituteDestinationParams(destination, params, kind) {
49
50
  const queryIndex = beforeHash.indexOf("?");
50
51
  const beforeQuery = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
51
52
  const authorityEnd = getAbsoluteUrlAuthorityEnd(beforeQuery.replace(paramRe, (token) => "x".repeat(token.length)));
52
- return `${`${replaceParams(beforeQuery.slice(0, authorityEnd), encodeAuthorityParam)}${replaceParams(beforeQuery.slice(authorityEnd), (value) => value)}`}${queryIndex === -1 ? "" : `?${replaceParams(beforeHash.slice(queryIndex + 1), encodeRewriteQueryParamValue)}`}${replaceParams(hash, (value) => value)}`;
53
+ const [path, pathHash] = substituteRewritePath(beforeQuery.slice(authorityEnd), paramRe, params, authorityEnd > 0);
54
+ const query = queryIndex === -1 ? "" : `?${replaceParams(beforeHash.slice(queryIndex + 1), encodeRewriteQueryParamValue)}`;
55
+ return `${replaceParams(beforeQuery.slice(0, authorityEnd), encodeAuthorityParam)}${path}${query}${pathHash ?? replaceParams(hash, (value) => value)}`;
56
+ }
57
+ /**
58
+ * Substitute params into a rewrite destination's path, returning the path and,
59
+ * when a param carried a `#`, the hash it starts.
60
+ *
61
+ * Next.js inserts path params verbatim, splits the result at the first `#`
62
+ * and escapes a `?` when it formats the URL. A decoded `has` capture can end
63
+ * the path with `#`, but neither `?` nor `#` can displace the destination's
64
+ * own query.
65
+ *
66
+ * An absolute destination is proxied with the URL parser, which removes tab
67
+ * and newlines, trims trailing control characters and spaces, and resolves
68
+ * `.`/`..` segments against the destination's literal path. Next.js sends a
69
+ * param's control characters and spaces percent-encoded, as vinext does, and
70
+ * its dot segments as written. A parsed URL cannot carry a `..` segment, so a
71
+ * dot segment that a param is in or next to is escaped once more (`.` as
72
+ * `%252E`, `%2e` as `%252e`) and stays inside the destination's path. The
73
+ * destination's own dot segments still resolve.
74
+ *
75
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
76
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/format-url.ts
77
+ */
78
+ function substituteRewritePath(template, paramRe, params, absolute) {
79
+ const encodeParam = absolute ? encodeAbsoluteUrlPathParam : encodePathQueryDelimiter;
80
+ const paramRanges = [];
81
+ let substituted = "";
82
+ let templateIndex = 0;
83
+ for (const match of template.matchAll(paramRe)) {
84
+ substituted += template.slice(templateIndex, match.index);
85
+ const value = encodeParam(params[match[1]]);
86
+ paramRanges.push([substituted.length, substituted.length + value.length]);
87
+ substituted += value;
88
+ templateIndex = match.index + match[0].length;
89
+ }
90
+ substituted += template.slice(templateIndex);
91
+ const [path, hash] = substituted.split("#", 2);
92
+ const pathHash = hash === void 0 ? void 0 : hash ? `#${hash}` : "";
93
+ if (!absolute) return [path, pathHash];
94
+ return [path.replace(DOT_SEGMENT_RE, (segment, offset) => paramRanges.some(([start, end]) => start <= offset + segment.length && end >= offset) ? segment.replace(/\.|%(?=2e)/gi, (char) => char === "." ? "%252E" : "%25") : segment), pathHash];
95
+ }
96
+ const DOT_SEGMENT_RE = /(?<=^|[/\\])(?:\.|%2e){1,2}(?=[/\\]|$)/gi;
97
+ function encodePathQueryDelimiter(value) {
98
+ return value.replaceAll("?", "%3F");
99
+ }
100
+ const ABSOLUTE_URL_PATH_PARAM_UNSAFE_RE = /[?\u0000- ]/g;
101
+ function encodeAbsoluteUrlPathParam(value) {
102
+ return value.replace(ABSOLUTE_URL_PATH_PARAM_UNSAFE_RE, encodeURIComponent);
53
103
  }
54
104
  function getAbsoluteUrlAuthorityEnd(value) {
55
105
  const scheme = /^[a-z][a-z0-9+.-]*:[/\\\t\n\r]*/i.exec(value);
@@ -1,3 +1,4 @@
1
+ import { CacheLifeConfig } from "../utils/cache-life-profiles.js";
1
2
  import { PHASE_PRODUCTION_BUILD } from "../shims/constants.js";
2
3
  import { PluginOption } from "vite";
3
4
  //#region src/config/next-config.d.ts
@@ -551,6 +552,11 @@ export type ResolvedNextConfig = {
551
552
  dynamic: number;
552
553
  static: number;
553
554
  };
555
+ /**
556
+ * `cacheLife` profiles (or legacy `experimental.cacheLife`) merged over the
557
+ * built-ins with Next.js's rules; read by the `cacheLife()` shim.
558
+ */
559
+ cacheLife: Record<string, CacheLifeConfig>;
554
560
  /**
555
561
  * Mirrors Next.js `experimental.useLightningcss`. When `true`, switch
556
562
  * Vite's CSS pipeline from PostCSS to lightningcss for both transforms
@@ -6,6 +6,7 @@ import { isUnknownRecord } from "../utils/record.js";
6
6
  import { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_BUILD } from "../shims/constants.js";
7
7
  import { getHtmlLimitedBotRegex } from "../utils/html-limited-bots.js";
8
8
  import { flattenPluginOptions } from "../utils/plugin-options.js";
9
+ import { resolveCacheLifeProfiles } from "../utils/cache-life-profiles.js";
9
10
  import { configSourceError } from "./config-source.js";
10
11
  import { isExternalUrl } from "../utils/external-url.js";
11
12
  import { applyLocaleToRoutes } from "./config-matchers.js";
@@ -376,7 +377,11 @@ function warnDeprecatedConfigOptions(config, root) {
376
377
  ];
377
378
  for (const [propertyPath, warning] of warnings) if (hasConfigProperty(config, propertyPath)) warnConfigOnce(warning);
378
379
  const experimental = readOptionalRecord(config.experimental);
379
- for (const key of ["skipTrailingSlashRedirect", "cacheComponents"]) if (experimental?.[key] != null) warnConfigOnce(`\`experimental.${key}\` has been moved to \`${key}\`. Please update your ${configFileName} file accordingly.`);
380
+ for (const key of [
381
+ "skipTrailingSlashRedirect",
382
+ "cacheComponents",
383
+ "cacheLife"
384
+ ]) if (experimental?.[key] != null) warnConfigOnce(`\`experimental.${key}\` has been moved to \`${key}\`. Please update your ${configFileName} file accordingly.`);
380
385
  }
381
386
  async function resolveNextConfigInput(config, phase = PHASE_DEVELOPMENT_SERVER) {
382
387
  return await resolveConfigValue(config, phase);
@@ -935,6 +940,10 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
935
940
  instrumentationServerValueInjections: {},
936
941
  clientTraceMetadata: void 0,
937
942
  staleTimes: { ...DEFAULT_STALE_TIMES },
943
+ cacheLife: resolveCacheLifeProfiles(void 0, {
944
+ expireTime: DEFAULT_EXPIRE_TIME,
945
+ staticStaleTime: DEFAULT_STALE_TIMES.static
946
+ }, warnConfigOnce),
938
947
  useLightningcss: false,
939
948
  lightningCssFeatures: {
940
949
  include: 0,
@@ -1014,6 +1023,8 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
1014
1023
  const allowedDevOrigins = Array.isArray(config.allowedDevOrigins) ? config.allowedDevOrigins : [];
1015
1024
  const experimental = readOptionalRecord(config.experimental);
1016
1025
  const cacheComponents = (experimental?.cacheComponents ?? config.cacheComponents) === true;
1026
+ const expireTime = typeof config.expireTime === "number" ? config.expireTime : DEFAULT_EXPIRE_TIME;
1027
+ const staleTimes = resolveStaleTimes(experimental);
1017
1028
  const serverActionsConfig = readOptionalRecord(experimental?.serverActions);
1018
1029
  const serverActionsAllowedOrigins = readStringArray(serverActionsConfig?.allowedOrigins);
1019
1030
  const serverActionsBodySizeLimitConfig = readOptionalBodySizeLimit(serverActionsConfig?.bodySizeLimit);
@@ -1124,7 +1135,7 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
1124
1135
  globalNotFound,
1125
1136
  serverActionsBodySizeLimit,
1126
1137
  serverActionsBodySizeLimitLabel,
1127
- expireTime: typeof config.expireTime === "number" ? config.expireTime : DEFAULT_EXPIRE_TIME,
1138
+ expireTime,
1128
1139
  reactMaxHeadersLength: typeof config.reactMaxHeadersLength === "number" ? config.reactMaxHeadersLength : DEFAULT_REACT_MAX_HEADERS_LENGTH,
1129
1140
  htmlLimitedBots,
1130
1141
  serverExternalPackages,
@@ -1144,7 +1155,11 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
1144
1155
  compilerDefine: serializeCompilerDefine(config.compiler?.define),
1145
1156
  compilerDefineServer: serializeCompilerDefine(config.compiler?.defineServer),
1146
1157
  clientTraceMetadata: Array.isArray(experimental?.clientTraceMetadata) ? experimental.clientTraceMetadata.filter((value) => typeof value === "string") : void 0,
1147
- staleTimes: resolveStaleTimes(experimental),
1158
+ staleTimes,
1159
+ cacheLife: resolveCacheLifeProfiles(experimental?.cacheLife ?? config.cacheLife, {
1160
+ expireTime,
1161
+ staticStaleTime: staleTimes.static
1162
+ }, warnConfigOnce),
1148
1163
  useLightningcss,
1149
1164
  lightningCssFeatures
1150
1165
  };
@@ -1161,7 +1161,6 @@ ${responseStageOnly ? "const __responseStageOptions = {" : "const __appRscHandle
1161
1161
  getSourceRoute(sourceRouteIndex) {
1162
1162
  return routes[sourceRouteIndex];
1163
1163
  },
1164
- hasCustomGlobalError: ${globalErrorVar ? `Boolean(${globalErrorVar}?.default)` : "false"},
1165
1164
  hasAnyGenerateStaticParams: __generateStaticParams.length > 0,
1166
1165
  hasGenerateStaticParams: __staticGeneration.hasGenerateStaticParams,
1167
1166
  hasPageDefaultExport: !!PageComponent,
@@ -21,6 +21,7 @@ const _pagesI18nPath = resolveEntryPath("../server/pages-i18n.js", import.meta.u
21
21
  const _pagesDataRoutePath = resolveEntryPath("../server/pages-data-route.js", import.meta.url);
22
22
  const _pagesDefault404Path = resolveEntryPath("../server/pages-default-404.js", import.meta.url);
23
23
  const _pagesApiRoutePath = resolveEntryPath("../server/pages-api-route.js", import.meta.url);
24
+ const _requestErrorLogPath = resolveEntryPath("../server/request-error-log.js", import.meta.url);
24
25
  const _serverGlobalsPath = resolveEntryPath("../server/server-globals.js", import.meta.url);
25
26
  const _queryUtilsPath = resolveEntryPath("../utils/query.js", import.meta.url);
26
27
  const _pagesPageHandlerPath = resolveEntryPath("../server/pages-page-handler.js", import.meta.url);
@@ -370,6 +371,7 @@ import { buildRouteTrie as _buildRouteTrie, trieMatch as _trieMatch } from ${JSO
370
371
  import { reportRequestError as _reportRequestError } from "vinext/instrumentation";
371
372
  import { resolvePagesI18nRequest } from ${JSON.stringify(_pagesI18nPath)};
372
373
  import { handlePagesApiRoute as __handlePagesApiRoute } from ${JSON.stringify(_pagesApiRoutePath)};
374
+ import { logRequestError as __logRequestError } from ${JSON.stringify(_requestErrorLogPath)};
373
375
  import { normalizePagesDataRequest as __normalizePagesDataRequest, shouldAddTrailingSlashToPagesDataPath as __shouldAddTrailingSlashToPagesDataPath, buildNextDataNotFoundResponse as __buildNextDataNotFoundResponse } from ${JSON.stringify(_pagesDataRoutePath)};
374
376
  import { buildDefaultPagesNotFoundResponse as __buildDefaultPagesNotFoundResponse } from ${JSON.stringify(_pagesDefault404Path)};
375
377
  import { createPagesPageHandler as __createPagesPageHandler } from ${JSON.stringify(_pagesPageHandlerPath)};
@@ -618,7 +620,7 @@ export async function handleApiRoute(request, url, ctx, trustedRevalidateOrigin,
618
620
  trustedRevalidateOrigin,
619
621
  url,
620
622
  reportRequestError(error, routePattern) {
621
- console.error("[vinext] API error:", error);
623
+ __logRequestError("[vinext] API error:", error);
622
624
  return _reportRequestError(
623
625
  error,
624
626
  { path: url, method: request.method, headers: Object.fromEntries(request.headers.entries()) },
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import { getReactUpgradeDeps, reactUpgradeRequiredMessage } from "./utils/react-
11
11
  import { resolvePluginReactPath } from "./utils/plugin-react-version.js";
12
12
  import { loadDotenv } from "./config/dotenv.js";
13
13
  import { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_BUILD } from "./shims/constants.js";
14
+ import { serializeCacheLifeProfiles } from "./utils/cache-life-profiles.js";
14
15
  import { VINEXT_MW_CTX_HEADER } from "./utils/protocol-headers.js";
15
16
  import { encodeMiddlewareRequestHeaders } from "./utils/middleware-request-headers.js";
16
17
  import { hasBasePath, stripBasePath } from "./utils/base-path.js";
@@ -1373,6 +1374,29 @@ function vinext(options = {}) {
1373
1374
  });
1374
1375
  const plugins = [
1375
1376
  buildLifecyclePlugins[0],
1377
+ {
1378
+ name: "vinext:dev-origin-check",
1379
+ apply: "serve",
1380
+ enforce: "pre",
1381
+ configureServer(server) {
1382
+ server.middlewares.use((req, res, next) => {
1383
+ const blockReason = validateDevRequest({
1384
+ origin: req.headers.origin,
1385
+ host: req.headers.host,
1386
+ "x-forwarded-host": req.headers["x-forwarded-host"],
1387
+ "sec-fetch-site": req.headers["sec-fetch-site"],
1388
+ "sec-fetch-mode": req.headers["sec-fetch-mode"]
1389
+ }, nextConfig?.allowedDevOrigins);
1390
+ if (blockReason) {
1391
+ console.warn(`[vinext] Blocked dev request: ${blockReason} (${req.url})`);
1392
+ res.writeHead(403, { "Content-Type": "text/plain" });
1393
+ res.end("Forbidden");
1394
+ return;
1395
+ }
1396
+ next();
1397
+ });
1398
+ }
1399
+ },
1376
1400
  createStyledJsxPlugin(earlyBaseDir),
1377
1401
  mdxProxyPlugin,
1378
1402
  reactPluginPromise,
@@ -1595,6 +1619,7 @@ function vinext(options = {}) {
1595
1619
  defines["process.env.__VINEXT_TRAILING_SLASH"] = JSON.stringify(nextConfig.trailingSlash ? "true" : "false");
1596
1620
  defines["process.env.__VINEXT_MANUAL_TRAILING_SLASH"] = JSON.stringify(nextConfig.skipTrailingSlashRedirect ? "true" : "false");
1597
1621
  defines["process.env.__NEXT_CONFIG_OUTPUT"] = JSON.stringify(nextConfig.output ?? "");
1622
+ defines["process.env.__VINEXT_CACHE_LIFE_PROFILES"] = JSON.stringify(serializeCacheLifeProfiles(nextConfig.cacheLife));
1598
1623
  defines["process.env.__VINEXT_IMAGE_REMOTE_PATTERNS"] = JSON.stringify(JSON.stringify(nextConfig.images?.remotePatterns ?? []));
1599
1624
  defines["process.env.__VINEXT_IMAGE_DOMAINS"] = JSON.stringify(JSON.stringify(nextConfig.images?.domains ?? []));
1600
1625
  {
@@ -3147,22 +3172,6 @@ export const loadServerActionClient = ${hasServerActions ? `() => import(${JSON.
3147
3172
  }
3148
3173
  return isExistingDevPublicFile(pathname);
3149
3174
  };
3150
- server.middlewares.use((req, res, next) => {
3151
- const blockReason = validateDevRequest({
3152
- origin: req.headers.origin,
3153
- host: req.headers.host,
3154
- "x-forwarded-host": req.headers["x-forwarded-host"],
3155
- "sec-fetch-site": req.headers["sec-fetch-site"],
3156
- "sec-fetch-mode": req.headers["sec-fetch-mode"]
3157
- }, nextConfig?.allowedDevOrigins);
3158
- if (blockReason) {
3159
- console.warn(`[vinext] Blocked dev request: ${blockReason} (${req.url})`);
3160
- res.writeHead(403, { "Content-Type": "text/plain" });
3161
- res.end("Forbidden");
3162
- return;
3163
- }
3164
- next();
3165
- });
3166
3175
  server.middlewares.use((req, res, next) => {
3167
3176
  if (!sendRepeatedSlashRedirect(req.url ?? "/", res)) next();
3168
3177
  });
@@ -3253,7 +3262,7 @@ export const loadServerActionClient = ${hasServerActions ? `() => import(${JSON.
3253
3262
  if (hasAppDir) server.middlewares.use((req, res, next) => {
3254
3263
  const url = req.url ?? "/";
3255
3264
  const [pathname] = url.split("?");
3256
- if (url.startsWith("/@") || url.startsWith("/__vite") || url.startsWith("/node_modules") || url.includes(".") && !pathname.endsWith(".html") && !pathname.endsWith(".rsc")) return next();
3265
+ if (url.startsWith("/@") || url.startsWith("/__vite") || url.startsWith("/node_modules") || url.includes(".") && !pathname.endsWith(".html")) return next();
3257
3266
  const _reqStart = now();
3258
3267
  const _reqWallStart = Date.now();
3259
3268
  let _compileMs;
@@ -3287,12 +3296,11 @@ export const loadServerActionClient = ${hasServerActions ? `() => import(${JSON.
3287
3296
  return _origWriteHead(statusCode, ...args);
3288
3297
  };
3289
3298
  res.on("finish", () => {
3290
- const logUrl = url.replace(/\.rsc(\?|$)/, "$1");
3291
3299
  const totalMs = now() - _reqStart;
3292
3300
  const resolvedRenderMs = _renderMs !== void 0 ? _renderMs : _compileMs !== void 0 ? Math.max(0, Math.round(totalMs - _compileMs)) : void 0;
3293
3301
  logRequest({
3294
3302
  method: req.method ?? "GET",
3295
- url: logUrl,
3303
+ url,
3296
3304
  status: res.statusCode,
3297
3305
  totalMs,
3298
3306
  compileMs: _compileMs,
@@ -3468,6 +3476,8 @@ export const loadServerActionClient = ${hasServerActions ? `() => import(${JSON.
3468
3476
  removeForwardedMiddlewareContext(req);
3469
3477
  for (const key of Object.keys(req.headers)) delete req.headers[key];
3470
3478
  for (const [key, value] of nextRequestHeaders) req.headers[key] = value;
3479
+ const setCookies = nextRequestHeaders.getSetCookie();
3480
+ if (setCookies.length > 1) req.headers["set-cookie"] = setCookies;
3471
3481
  };
3472
3482
  const devRunMiddlewareAdapter = capturedMiddlewarePath ? async (_request, _ctx, opts) => {
3473
3483
  const rawProto = process.env.VINEXT_TRUST_PROXY === "1" || (process.env.VINEXT_TRUSTED_HOSTS ?? "").split(",").some((h) => h.trim()) ? String(req.headers["x-forwarded-proto"] || "").split(",")[0].trim() : "";
@@ -3662,7 +3672,6 @@ export const loadServerActionClient = ${hasServerActions ? `() => import(${JSON.
3662
3672
  const url = req.url ?? "/";
3663
3673
  if (!hasPagesDir) return next();
3664
3674
  if (url.startsWith("/@") || url.startsWith("/__vite") || url.startsWith("/node_modules")) return next();
3665
- if (url.split("?")[0].endsWith(".rsc")) return next();
3666
3675
  if (hasCloudflarePlugin) return next();
3667
3676
  await pagesInstrumentationReady;
3668
3677
  const traceHeaders = new Headers();
@@ -89,7 +89,8 @@ function isReferenceIdentifier(node, parent) {
89
89
  if (parent.type === "ImportSpecifier" || parent.type === "ImportDefaultSpecifier" || parent.type === "ImportNamespaceSpecifier") return false;
90
90
  if (parent.type === "ExportSpecifier") return false;
91
91
  if (parent.type === "MemberExpression" && parent.property === node && !parent.computed) return false;
92
- if ((parent.type === "Property" || parent.type === "MethodDefinition") && parent.key === node && !parent.computed && !(parent.type === "Property" && parent.shorthand)) return false;
92
+ if ((parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition" || parent.type === "AccessorProperty" || parent.type === "ImportAttribute") && parent.key === node && !parent.computed && !(parent.type === "Property" && parent.shorthand)) return false;
93
+ if (parent.type === "MetaProperty") return false;
93
94
  if ((parent.type === "LabeledStatement" || parent.type === "BreakStatement" || parent.type === "ContinueStatement") && parent.label === node) return false;
94
95
  const parameters = "params" in parent ? parent.params : void 0;
95
96
  if (Array.isArray(parameters) && parameters.includes(node)) return false;
@@ -4,6 +4,7 @@ import "./server-globals.js";
4
4
  import { resolveRequestHost, resolveRequestProtocol } from "./proxy-trust.js";
5
5
  import { NextRequest } from "../shims/server.js";
6
6
  import { importModule, reportRequestError } from "./instrumentation.js";
7
+ import { logRequestError } from "./request-error-log.js";
7
8
  import { mergeRouteParamsIntoQuery, parseQueryString, urlQueryToSearchParams } from "../utils/query.js";
8
9
  import { tracePagesApiHandler } from "./pages-execution-tracing.js";
9
10
  import { DEFAULT_PAGES_API_BODY_SIZE_LIMIT, resolveBodyParserConfig } from "./pages-body-parser-config.js";
@@ -287,7 +288,7 @@ async function handleApiRoute(runner, req, res, url, apiRoutes, nextConfig) {
287
288
  res.end(e.message);
288
289
  return true;
289
290
  }
290
- console.error(e);
291
+ logRequestError(e);
291
292
  await reportRequestError(e, {
292
293
  path: url,
293
294
  method: req.method ?? "GET",
@@ -45,7 +45,7 @@ import { createHydrationCachePublication } from "./app-hydration-cache-publicati
45
45
  import { peekSettledPrefetchResponseForNavigation, prepareConsumedPrefetchResponseForPublication, preserveCommittedPrefetchExpiry, resolvePrefetchNavigationResponseUrl } from "./app-browser-prefetch-response.js";
46
46
  import { canCommitOptimisticRouteTemplate, createOptimisticRouteTemplate, createOptimisticRouteTemplateStore, getOptimisticPrefetchSourceKey, getOptimisticRouteTemplateKey, keepOptimisticStreamingMetadata, resolveOptimisticNavigationParamsForHref, resolveOptimisticNavigationPayload } from "./app-optimistic-routing.js";
47
47
  import { removeStylesheetLinksCoveredByInlineCss } from "./app-inline-css-client.js";
48
- import { createElement, startTransition, use, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
48
+ import { Fragment, createElement, startTransition, use, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
49
49
  import { createRoot, hydrateRoot } from "react-dom/client";
50
50
  import { createFromFetch, createFromReadableStream, setServerCallback } from "@vitejs/plugin-rsc/browser";
51
51
  import { flushSync } from "react-dom";
@@ -1000,6 +1000,10 @@ function registerServerActionCallback() {
1000
1000
  })).finally(releaseCacheInvalidationGuard);
1001
1001
  });
1002
1002
  }
1003
+ function ReplayInitialDevServerErrors({ report }) {
1004
+ useEffect(report, [report]);
1005
+ return null;
1006
+ }
1003
1007
  async function main() {
1004
1008
  if (!claimInitialAppRouterBootstrap()) return;
1005
1009
  loadNavigationRuntimeRouteManifest();
@@ -1011,7 +1015,7 @@ async function main() {
1011
1015
  devErrorOverlay = await import("../client/dev-error-overlay.js");
1012
1016
  devErrorOverlay.installDevErrorOverlay();
1013
1017
  devErrorOverlay.installViteHmrErrorHandler(import.meta.hot);
1014
- devErrorOverlay.reportInitialDevServerErrors();
1018
+ if (document.documentElement.id !== "__next_error__") devErrorOverlay.reportInitialDevServerErrors();
1015
1019
  }
1016
1020
  const initialRscBootstrap = getNavigationRuntime()?.bootstrap.rsc;
1017
1021
  const rscStream = await readInitialRscStream();
@@ -1098,7 +1102,7 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
1098
1102
  for (const style of errorShellStyles) style.remove();
1099
1103
  startTransition(() => {
1100
1104
  const clientRoot = createRoot(document, createRootOptions);
1101
- clientRoot.render(children);
1105
+ clientRoot.render(import.meta.env.DEV && devErrorOverlay ? createElement(Fragment, null, children, createElement(ReplayInitialDevServerErrors, { report: devErrorOverlay.reportInitialDevServerErrors })) : children);
1102
1106
  window.__VINEXT_RSC_ROOT__ = clientRoot;
1103
1107
  });
1104
1108
  } else window.__VINEXT_RSC_ROOT__ = hydrateRootInTransition({
@@ -1,5 +1,5 @@
1
1
  import { isDangerousScheme, reportBlockedDangerousNavigation } from "../shims/url-safety.js";
2
- import { resolveHardNavigationTargetFromRscResponse, stripRscCacheBustingSearchParam, stripRscSuffix } from "./app-rsc-cache-busting.js";
2
+ import { resolveHardNavigationTargetFromRscResponse, stripRscCacheBustingSearchParam, toPathAbsolutePathname } from "./app-rsc-cache-busting.js";
3
3
  //#region src/server/app-browser-rsc-redirect.ts
4
4
  const MAX_RSC_REDIRECT_DEPTH = 10;
5
5
  function blockDangerousStreamedRscRedirect(response, streamedRedirectTarget) {
@@ -11,10 +11,10 @@ function blockDangerousStreamedRscRedirect(response, streamedRedirectTarget) {
11
11
  function toVisibleAppHref(href, origin) {
12
12
  const url = new URL(href, origin);
13
13
  stripRscCacheBustingSearchParam(url);
14
- return `${stripRscSuffix(url.pathname)}${url.search}${url.hash}`;
14
+ return `${toPathAbsolutePathname(url.pathname)}${url.search}${url.hash}`;
15
15
  }
16
16
  function toStreamedRedirectVisibleAppHref(url) {
17
- return `${url.pathname}${url.search}${url.hash}`;
17
+ return `${toPathAbsolutePathname(url.pathname)}${url.search}${url.hash}`;
18
18
  }
19
19
  function resolveRedirectLifecycleHopFromTarget(options) {
20
20
  if (options.targetUrl.origin !== options.origin) return {
@@ -1,7 +1,7 @@
1
1
  import { hasBasePath } from "../utils/base-path.js";
2
2
  import { ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER } from "./headers.js";
3
3
  import { DANGEROUS_URL_BLOCK_MESSAGE, isDangerousScheme } from "../shims/url-safety.js";
4
- import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, createServerActionRequestUrl } from "./app-rsc-cache-busting.js";
4
+ import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, createServerActionRequestUrl, toPathAbsolutePathname } from "./app-rsc-cache-busting.js";
5
5
  import { throwOnServerActionNotFound } from "./server-action-not-found.js";
6
6
  import { AppElementsWire } from "./app-elements-wire.js";
7
7
  import "./app-elements.js";
@@ -49,7 +49,7 @@ var ServerActionRedirectError = class extends Error {
49
49
  constructor(target) {
50
50
  super("NEXT_REDIRECT");
51
51
  const redirectUrl = new URL(target.href, window.location.href);
52
- const redirectHref = redirectUrl.pathname + redirectUrl.search + redirectUrl.hash;
52
+ const redirectHref = toPathAbsolutePathname(redirectUrl.pathname) + redirectUrl.search + redirectUrl.hash;
53
53
  const redirectType = target.type === "push" ? "push" : "replace";
54
54
  this.digest = `NEXT_REDIRECT;${redirectType};${redirectHref};${target.status};`;
55
55
  }
@@ -87,11 +87,11 @@ function reduceApprovedVisibleCommitState(state, commit) {
87
87
  const hmrUniquePreserveElementIds = hmrPreserveElementIds.length > 1 ? [...new Set(hmrPreserveElementIds)] : hmrPreserveElementIds;
88
88
  const preserveElementIds = action.operation.lane === "hmr" ? hmrUniquePreserveElementIds : bfcacheCompatiblePreserveElementIds;
89
89
  const preserveTemplateIds = action.reuseCurrentBfcacheIds && (action.operation.lane === "navigation" || action.operation.lane === "traverse" || action.operation.lane === "server-action") ? Object.keys(state.elements).filter((id) => Object.hasOwn(action.elements, id) && hasSameServerTemplateSeedIdentity(id, state.navigationSnapshot, action.navigationSnapshot)) : [];
90
- const mergedPreserveElementIds = preserveTemplateIds.length === 0 ? preserveElementIds : [.../* @__PURE__ */ new Set([...preserveElementIds, ...preserveTemplateIds])];
91
90
  const mergedElements = mergeElements(state.elements, action.elements, {
92
91
  clearAbsentSlots: action.type === "traverse" || !action.reuseCurrentBfcacheIds,
93
92
  preserveAbsentSlots: action.reuseCurrentBfcacheIds && commit.decision.preserveAbsentSlots,
94
- preserveElementIds: mergedPreserveElementIds,
93
+ preserveElementIds,
94
+ preserveElementSeedIds: preserveTemplateIds,
95
95
  preservePreviousSlotIds: effectivePreservePreviousSlotIds
96
96
  });
97
97
  return commitVisibleRouterState(state, {