vinext 0.1.0 → 0.1.2

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 (205) hide show
  1. package/README.md +2 -5
  2. package/dist/build/assets-ignore.d.ts +32 -0
  3. package/dist/build/assets-ignore.js +48 -0
  4. package/dist/build/client-build-config.d.ts +33 -1
  5. package/dist/build/client-build-config.js +66 -1
  6. package/dist/check.js +4 -3
  7. package/dist/cli.js +2 -0
  8. package/dist/client/navigation-runtime.d.ts +11 -2
  9. package/dist/client/navigation-runtime.js +1 -1
  10. package/dist/client/vinext-next-data.d.ts +2 -1
  11. package/dist/client/window-next.d.ts +6 -4
  12. package/dist/config/config-matchers.d.ts +31 -5
  13. package/dist/config/config-matchers.js +50 -3
  14. package/dist/config/next-config.d.ts +29 -3
  15. package/dist/config/next-config.js +32 -2
  16. package/dist/deploy.js +47 -304
  17. package/dist/entries/app-rsc-entry.d.ts +8 -2
  18. package/dist/entries/app-rsc-entry.js +61 -5
  19. package/dist/entries/app-rsc-manifest.js +20 -2
  20. package/dist/entries/pages-client-entry.js +1 -1
  21. package/dist/entries/pages-server-entry.js +16 -7
  22. package/dist/index.d.ts +0 -2
  23. package/dist/index.js +233 -280
  24. package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
  25. package/dist/plugins/dynamic-preload-metadata.js +415 -0
  26. package/dist/plugins/og-assets.js +2 -2
  27. package/dist/plugins/optimize-imports.d.ts +8 -4
  28. package/dist/plugins/optimize-imports.js +16 -12
  29. package/dist/plugins/postcss.js +18 -14
  30. package/dist/plugins/require-context.d.ts +6 -0
  31. package/dist/plugins/require-context.js +184 -0
  32. package/dist/plugins/sass.d.ts +53 -24
  33. package/dist/plugins/sass.js +249 -1
  34. package/dist/plugins/wasm-module-import.d.ts +15 -0
  35. package/dist/plugins/wasm-module-import.js +50 -0
  36. package/dist/routing/app-route-graph.d.ts +35 -2
  37. package/dist/routing/app-route-graph.js +179 -8
  38. package/dist/routing/file-matcher.js +1 -1
  39. package/dist/routing/route-pattern.d.ts +2 -1
  40. package/dist/routing/route-pattern.js +16 -1
  41. package/dist/server/api-handler.js +4 -0
  42. package/dist/server/app-browser-entry.js +155 -215
  43. package/dist/server/app-browser-error.d.ts +4 -1
  44. package/dist/server/app-browser-error.js +7 -1
  45. package/dist/server/app-browser-history-controller.d.ts +104 -0
  46. package/dist/server/app-browser-history-controller.js +210 -0
  47. package/dist/server/app-browser-interception-context.d.ts +2 -1
  48. package/dist/server/app-browser-interception-context.js +15 -2
  49. package/dist/server/app-browser-navigation-controller.d.ts +13 -2
  50. package/dist/server/app-browser-navigation-controller.js +83 -4
  51. package/dist/server/app-browser-popstate.d.ts +12 -3
  52. package/dist/server/app-browser-popstate.js +19 -4
  53. package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
  54. package/dist/server/app-browser-rsc-redirect.js +30 -8
  55. package/dist/server/app-browser-state.d.ts +3 -0
  56. package/dist/server/app-browser-state.js +10 -10
  57. package/dist/server/app-browser-visible-commit.js +10 -8
  58. package/dist/server/app-fallback-renderer.d.ts +2 -1
  59. package/dist/server/app-fallback-renderer.js +3 -1
  60. package/dist/server/app-history-state.d.ts +45 -1
  61. package/dist/server/app-history-state.js +109 -1
  62. package/dist/server/app-middleware.js +1 -0
  63. package/dist/server/app-optimistic-routing.js +22 -1
  64. package/dist/server/app-page-boundary-render.d.ts +2 -1
  65. package/dist/server/app-page-boundary-render.js +45 -21
  66. package/dist/server/app-page-cache.js +9 -7
  67. package/dist/server/app-page-dispatch.d.ts +14 -0
  68. package/dist/server/app-page-dispatch.js +21 -6
  69. package/dist/server/app-page-element-builder.d.ts +23 -2
  70. package/dist/server/app-page-element-builder.js +58 -17
  71. package/dist/server/app-page-execution.d.ts +1 -1
  72. package/dist/server/app-page-execution.js +32 -17
  73. package/dist/server/app-page-render.d.ts +7 -1
  74. package/dist/server/app-page-render.js +11 -16
  75. package/dist/server/app-page-request.d.ts +9 -6
  76. package/dist/server/app-page-request.js +14 -10
  77. package/dist/server/app-page-response.d.ts +2 -2
  78. package/dist/server/app-page-response.js +2 -2
  79. package/dist/server/app-page-route-wiring.d.ts +3 -1
  80. package/dist/server/app-page-route-wiring.js +10 -8
  81. package/dist/server/app-page-stream.d.ts +37 -7
  82. package/dist/server/app-page-stream.js +36 -6
  83. package/dist/server/app-pages-bridge.d.ts +16 -0
  84. package/dist/server/app-pages-bridge.js +23 -3
  85. package/dist/server/app-route-handler-cache.d.ts +1 -0
  86. package/dist/server/app-route-handler-cache.js +1 -0
  87. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  88. package/dist/server/app-route-handler-dispatch.js +2 -0
  89. package/dist/server/app-route-handler-execution.d.ts +1 -0
  90. package/dist/server/app-route-handler-execution.js +1 -0
  91. package/dist/server/app-route-handler-response.js +11 -10
  92. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  93. package/dist/server/app-route-handler-runtime.js +15 -3
  94. package/dist/server/app-rsc-handler.d.ts +1 -0
  95. package/dist/server/app-rsc-handler.js +5 -4
  96. package/dist/server/app-rsc-response-finalizer.js +1 -1
  97. package/dist/server/app-rsc-route-matching.d.ts +20 -1
  98. package/dist/server/app-rsc-route-matching.js +29 -4
  99. package/dist/server/app-server-action-execution.d.ts +22 -1
  100. package/dist/server/app-server-action-execution.js +73 -12
  101. package/dist/server/app-ssr-entry.d.ts +6 -0
  102. package/dist/server/app-ssr-entry.js +19 -3
  103. package/dist/server/app-ssr-stream.js +9 -1
  104. package/dist/server/dev-lockfile.js +2 -1
  105. package/dist/server/dev-server.d.ts +1 -1
  106. package/dist/server/dev-server.js +97 -43
  107. package/dist/server/headers.d.ts +8 -1
  108. package/dist/server/headers.js +8 -1
  109. package/dist/server/instrumentation-runtime.d.ts +6 -0
  110. package/dist/server/instrumentation-runtime.js +8 -0
  111. package/dist/server/isr-cache.d.ts +37 -1
  112. package/dist/server/isr-cache.js +85 -1
  113. package/dist/server/isr-decision.d.ts +79 -0
  114. package/dist/server/isr-decision.js +70 -0
  115. package/dist/server/metadata-route-response.js +5 -3
  116. package/dist/server/middleware-runtime.d.ts +13 -0
  117. package/dist/server/middleware-runtime.js +11 -7
  118. package/dist/server/middleware.js +1 -0
  119. package/dist/server/navigation-planner.d.ts +62 -1
  120. package/dist/server/navigation-planner.js +193 -3
  121. package/dist/server/navigation-trace.d.ts +12 -2
  122. package/dist/server/navigation-trace.js +11 -1
  123. package/dist/server/normalize-path.d.ts +0 -8
  124. package/dist/server/normalize-path.js +3 -1
  125. package/dist/server/otel-tracer-extension.d.ts +45 -0
  126. package/dist/server/otel-tracer-extension.js +89 -0
  127. package/dist/server/pages-api-route.d.ts +14 -3
  128. package/dist/server/pages-api-route.js +6 -1
  129. package/dist/server/pages-asset-tags.d.ts +15 -4
  130. package/dist/server/pages-asset-tags.js +18 -12
  131. package/dist/server/pages-data-route.js +5 -1
  132. package/dist/server/pages-node-compat.d.ts +5 -11
  133. package/dist/server/pages-node-compat.js +175 -118
  134. package/dist/server/pages-page-data.d.ts +38 -7
  135. package/dist/server/pages-page-data.js +64 -18
  136. package/dist/server/pages-page-handler.d.ts +10 -2
  137. package/dist/server/pages-page-handler.js +49 -20
  138. package/dist/server/pages-page-response.d.ts +55 -2
  139. package/dist/server/pages-page-response.js +74 -6
  140. package/dist/server/pages-readiness.d.ts +36 -0
  141. package/dist/server/pages-readiness.js +21 -0
  142. package/dist/server/pages-request-pipeline.d.ts +113 -0
  143. package/dist/server/pages-request-pipeline.js +230 -0
  144. package/dist/server/pages-revalidate.d.ts +15 -0
  145. package/dist/server/pages-revalidate.js +19 -0
  146. package/dist/server/prod-server.d.ts +45 -3
  147. package/dist/server/prod-server.js +182 -234
  148. package/dist/server/socket-error-backstop.d.ts +19 -1
  149. package/dist/server/socket-error-backstop.js +77 -4
  150. package/dist/shims/app-router-scroll.js +22 -4
  151. package/dist/shims/cache-runtime.js +39 -2
  152. package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
  153. package/dist/shims/dynamic-preload-chunks.js +77 -0
  154. package/dist/shims/dynamic.d.ts +4 -0
  155. package/dist/shims/dynamic.js +4 -2
  156. package/dist/shims/error-boundary.d.ts +17 -7
  157. package/dist/shims/error-boundary.js +8 -1
  158. package/dist/shims/error.js +37 -11
  159. package/dist/shims/fetch-cache.d.ts +22 -1
  160. package/dist/shims/fetch-cache.js +28 -1
  161. package/dist/shims/hash-scroll.d.ts +1 -0
  162. package/dist/shims/hash-scroll.js +3 -1
  163. package/dist/shims/head.js +6 -1
  164. package/dist/shims/headers.d.ts +16 -2
  165. package/dist/shims/headers.js +37 -1
  166. package/dist/shims/image-config.js +7 -1
  167. package/dist/shims/internal/app-route-detection.d.ts +6 -3
  168. package/dist/shims/internal/app-route-detection.js +10 -6
  169. package/dist/shims/internal/app-router-context.d.ts +5 -0
  170. package/dist/shims/internal/link-status-registry.d.ts +43 -0
  171. package/dist/shims/internal/link-status-registry.js +42 -0
  172. package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
  173. package/dist/shims/internal/route-pattern-for-warning.js +40 -0
  174. package/dist/shims/internal/utils.d.ts +1 -0
  175. package/dist/shims/link.js +20 -6
  176. package/dist/shims/metadata.d.ts +6 -2
  177. package/dist/shims/metadata.js +32 -14
  178. package/dist/shims/navigation.d.ts +9 -18
  179. package/dist/shims/navigation.js +96 -23
  180. package/dist/shims/router-state.d.ts +1 -0
  181. package/dist/shims/router-state.js +2 -0
  182. package/dist/shims/router.d.ts +6 -3
  183. package/dist/shims/router.js +156 -22
  184. package/dist/shims/script-nonce-context.d.ts +1 -1
  185. package/dist/shims/script-nonce-context.js +11 -3
  186. package/dist/shims/server.d.ts +17 -1
  187. package/dist/shims/server.js +31 -6
  188. package/dist/shims/slot.js +1 -1
  189. package/dist/shims/unified-request-context.js +1 -0
  190. package/dist/typegen.js +1 -0
  191. package/dist/utils/client-build-manifest.d.ts +8 -1
  192. package/dist/utils/client-build-manifest.js +41 -6
  193. package/dist/utils/client-entry-manifest.d.ts +11 -0
  194. package/dist/utils/client-entry-manifest.js +29 -0
  195. package/dist/utils/client-runtime-metadata.d.ts +45 -0
  196. package/dist/utils/client-runtime-metadata.js +63 -0
  197. package/dist/utils/hash.d.ts +17 -1
  198. package/dist/utils/hash.js +36 -1
  199. package/dist/utils/lazy-chunks.d.ts +27 -1
  200. package/dist/utils/lazy-chunks.js +65 -1
  201. package/dist/utils/manifest-paths.d.ts +20 -2
  202. package/dist/utils/manifest-paths.js +38 -3
  203. package/dist/utils/path.d.ts +2 -1
  204. package/dist/utils/path.js +5 -1
  205. package/package.json +6 -2
@@ -2,7 +2,7 @@ import { createRequestContext, runWithRequestContext } from "../shims/unified-re
2
2
  import { hasBasePath } from "../utils/base-path.js";
3
3
  import { getRequestExecutionContext } from "../shims/request-context.js";
4
4
  import { ACTION_REVALIDATED_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER } from "./headers.js";
5
- import { isExternalUrl, matchRedirect, matchRewrite, proxyExternalRequest, requestContextFromRequest, sanitizeDestination } from "../config/config-matchers.js";
5
+ import { isExternalUrl, matchRedirect, matchRewrite, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, sanitizeDestination } from "../config/config-matchers.js";
6
6
  import { notFoundResponse } from "./http-error-responses.js";
7
7
  import { applyConfigHeadersToResponse, cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders, normalizeTrailingSlash, resolvePublicFileRoute, validateImageUrl } from "./request-pipeline.js";
8
8
  import { headersContextFromRequest } from "../shims/headers.js";
@@ -14,12 +14,12 @@ import { isImageOptimizationPath } from "./image-optimization.js";
14
14
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
15
15
  import "./app-page-response.js";
16
16
  import { prerenderRouteParamsPayloadMatchesRoute, readTrustedPrerenderRouteParams, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
17
- import { pickRootParams, setRootParams } from "../shims/root-params.js";
18
- import { flattenErrorCauses } from "../utils/error-cause.js";
19
17
  import { applyAppMiddleware } from "./app-middleware.js";
20
18
  import { buildPageCacheTags } from "./implicit-tags.js";
21
19
  import { buildPostMwRequestContext } from "./app-post-middleware-context.js";
20
+ import { pickRootParams, setRootParams } from "../shims/root-params.js";
22
21
  import { handleAppPrerenderEndpoint } from "./app-prerender-endpoints.js";
22
+ import { flattenErrorCauses } from "../utils/error-cause.js";
23
23
  import { finalizeAppRscResponse } from "./app-rsc-response-finalizer.js";
24
24
  import { normalizeRscRequest } from "./app-rsc-request-normalization.js";
25
25
  import { handleMetadataRouteRequest } from "./metadata-route-response.js";
@@ -113,7 +113,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
113
113
  const redirect = matchRedirect(matchPathname(stripRscSuffix(pathname)), options.configRedirects, preMiddlewareRequestContext, basePathState);
114
114
  if (redirect) {
115
115
  const destination = sanitizeDestination(redirectDestinationWithBasePath(redirect.destination, options.basePath));
116
- const location = isRscRequest && request.headers.get("RSC") === "1" ? await createRscRedirectLocation(destination, request) : destination;
116
+ const location = isRscRequest && request.headers.get("RSC") === "1" ? await createRscRedirectLocation(destination, request) : preserveRedirectDestinationQuery(destination, url.search);
117
117
  return new Response(null, {
118
118
  status: redirect.permanent ? 308 : 307,
119
119
  headers: { Location: location }
@@ -302,6 +302,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
302
302
  const pageResponse = await options.dispatchMatchedPage({
303
303
  clientReuseManifest,
304
304
  cleanPathname,
305
+ displayPathname: canonicalPathname,
305
306
  formState,
306
307
  actionError,
307
308
  actionFailed,
@@ -3,9 +3,9 @@ import { hasBasePath, stripBasePath } from "../utils/base-path.js";
3
3
  import "./headers.js";
4
4
  import { normalizePath } from "./normalize-path.js";
5
5
  import { applyConfigHeadersToResponse } from "./request-pipeline.js";
6
+ import { applyCdnResponseHeaders } from "./cache-control.js";
6
7
  import { VINEXT_RSC_VARY_HEADER } from "./app-rsc-cache-busting.js";
7
8
  import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
8
- import { applyCdnResponseHeaders } from "./cache-control.js";
9
9
  import { mergeVaryHeader } from "./middleware-response-headers.js";
10
10
  //#region src/server/app-rsc-response-finalizer.ts
11
11
  /**
@@ -1,6 +1,12 @@
1
1
  import { RoutePatternParams } from "../routing/route-pattern.js";
2
2
 
3
3
  //#region src/server/app-rsc-route-matching.d.ts
4
+ /**
5
+ * Sentinel slot key used for sibling-style interception entries.
6
+ * When a matched intercept carries this key, the render layer replaces the
7
+ * route's main page element instead of a parallel slot.
8
+ */
9
+ declare const SIBLING_PAGE_INTERCEPT_SLOT_KEY = "__vinext_page_intercept";
4
10
  type AppRscRouteParams = RoutePatternParams;
5
11
  type AppRscInterceptForMatching = {
6
12
  targetPattern: string;
@@ -25,15 +31,27 @@ type AppRscInterceptForMatching = {
25
31
  sourceMatchPattern?: string;
26
32
  interceptLayouts: readonly unknown[];
27
33
  page: unknown;
34
+ __pageLoader?: (() => Promise<unknown>) | null;
28
35
  params: readonly string[];
29
36
  };
30
37
  type AppRscSlotForMatching = {
31
38
  id?: string | null;
32
39
  intercepts?: readonly AppRscInterceptForMatching[];
33
40
  };
41
+ type AppRscSiblingInterceptForMatching = {
42
+ targetPattern: string;
43
+ sourceMatchPattern: string | null;
44
+ slotId: string | null;
45
+ interceptLayouts: readonly unknown[];
46
+ page: unknown;
47
+ __pageLoader?: (() => Promise<unknown>) | null;
48
+ params: readonly string[];
49
+ };
34
50
  type AppRscRouteForMatching = {
51
+ pattern: string;
35
52
  patternParts: string[];
36
53
  slots?: Record<string, AppRscSlotForMatching>;
54
+ siblingIntercepts?: AppRscSiblingInterceptForMatching[];
37
55
  };
38
56
  type AppRscInterceptMatch = AppRscInterceptLookupEntry & {
39
57
  matchedParams: AppRscRouteParams;
@@ -47,6 +65,7 @@ type AppRscInterceptLookupEntry = {
47
65
  sourceMatchPatternParts: string[] | null;
48
66
  interceptLayouts: readonly unknown[];
49
67
  page: unknown;
68
+ __pageLoader?: (() => Promise<unknown>) | null;
50
69
  params: readonly string[];
51
70
  slotId: string | null;
52
71
  };
@@ -59,4 +78,4 @@ declare function createAppRscRouteMatcher<Route extends AppRscRouteForMatching>(
59
78
  };
60
79
  declare function matchAppRscRoutePattern(urlParts: string[], patternParts: string[]): AppRscRouteParams | null;
61
80
  //#endregion
62
- export { createAppRscRouteMatcher, matchAppRscRoutePattern };
81
+ export { SIBLING_PAGE_INTERCEPT_SLOT_KEY, createAppRscRouteMatcher, matchAppRscRoutePattern };
@@ -2,6 +2,12 @@ import { splitPathnameForRouteMatch } from "../routing/utils.js";
2
2
  import { buildRouteTrie, trieMatch } from "../routing/route-trie.js";
3
3
  import { matchRoutePattern, matchRoutePatternPrefix } from "../routing/route-pattern.js";
4
4
  //#region src/server/app-rsc-route-matching.ts
5
+ /**
6
+ * Sentinel slot key used for sibling-style interception entries.
7
+ * When a matched intercept carries this key, the render layer replaces the
8
+ * route's main page element instead of a parallel slot.
9
+ */
10
+ const SIBLING_PAGE_INTERCEPT_SLOT_KEY = "__vinext_page_intercept";
5
11
  function createRouteParams() {
6
12
  return Object.create(null);
7
13
  }
@@ -54,17 +60,18 @@ function matchInterceptSource(sourceParts, entry) {
54
60
  return matchRoutePatternPrefix(sourceParts, patternParts);
55
61
  }
56
62
  function createInterceptLookup(routes) {
63
+ const patternToIndex = new Map(routes.map((r, i) => [r.pattern, i]));
57
64
  const interceptLookup = [];
58
65
  for (let routeIndex = 0; routeIndex < routes.length; routeIndex++) {
59
66
  const route = routes[routeIndex];
60
- if (!route.slots) continue;
61
- for (const [slotKey, slotModule] of Object.entries(route.slots)) {
67
+ if (route.slots) for (const [slotKey, slotModule] of Object.entries(route.slots)) {
62
68
  if (!slotModule.intercepts) continue;
63
69
  for (const intercept of slotModule.intercepts) {
64
70
  const sourceMatchPattern = intercept.sourceMatchPattern ?? null;
65
71
  const sourceMatchPatternParts = sourceMatchPattern ? sourceMatchPattern.split("/").filter(Boolean) : null;
72
+ const ownerRouteIndex = sourceMatchPattern !== null ? patternToIndex.get(sourceMatchPattern) ?? routeIndex : routeIndex;
66
73
  interceptLookup.push({
67
- sourceRouteIndex: routeIndex,
74
+ sourceRouteIndex: ownerRouteIndex,
68
75
  slotKey,
69
76
  slotId: typeof slotModule.id === "string" ? slotModule.id : null,
70
77
  targetPattern: intercept.targetPattern,
@@ -73,10 +80,28 @@ function createInterceptLookup(routes) {
73
80
  sourceMatchPatternParts,
74
81
  interceptLayouts: intercept.interceptLayouts,
75
82
  page: intercept.page,
83
+ __pageLoader: intercept.__pageLoader,
76
84
  params: intercept.params
77
85
  });
78
86
  }
79
87
  }
88
+ if (route.siblingIntercepts) for (const intercept of route.siblingIntercepts) {
89
+ const sourceMatchPattern = intercept.sourceMatchPattern ?? null;
90
+ const sourceMatchPatternParts = sourceMatchPattern ? sourceMatchPattern.split("/").filter(Boolean) : null;
91
+ interceptLookup.push({
92
+ sourceRouteIndex: routeIndex,
93
+ slotKey: SIBLING_PAGE_INTERCEPT_SLOT_KEY,
94
+ slotId: typeof intercept.slotId === "string" ? intercept.slotId : null,
95
+ targetPattern: intercept.targetPattern,
96
+ targetPatternParts: intercept.targetPattern.split("/").filter(Boolean),
97
+ sourceMatchPattern,
98
+ sourceMatchPatternParts,
99
+ interceptLayouts: intercept.interceptLayouts,
100
+ page: intercept.page,
101
+ __pageLoader: intercept.__pageLoader,
102
+ params: intercept.params
103
+ });
104
+ }
80
105
  }
81
106
  return interceptLookup;
82
107
  }
@@ -87,4 +112,4 @@ function mergeMatchedParams(sourceParams, targetParams) {
87
112
  return Object.assign(createRouteParams(), sourceParams, targetParams);
88
113
  }
89
114
  //#endregion
90
- export { createAppRscRouteMatcher, matchAppRscRoutePattern };
115
+ export { SIBLING_PAGE_INTERCEPT_SLOT_KEY, createAppRscRouteMatcher, matchAppRscRoutePattern };
@@ -31,6 +31,17 @@ type AppServerActionRoute = {
31
31
  pattern: string;
32
32
  routeHandler?: unknown;
33
33
  routeSegments?: readonly string[];
34
+ params?: readonly string[] | null;
35
+ slots?: Readonly<Record<string, {
36
+ default?: {
37
+ default?: unknown;
38
+ } | null;
39
+ page?: {
40
+ default?: unknown;
41
+ } | null;
42
+ slotPatternParts?: readonly string[] | null;
43
+ slotParamNames?: readonly string[] | null;
44
+ }>> | null;
34
45
  };
35
46
  /**
36
47
  * Side-effect headers captured during a progressive (no-JS) server action's
@@ -108,6 +119,15 @@ type HandleProgressiveServerActionRequestOptions = {
108
119
  decodeFormState: AppServerActionFormStateDecoder;
109
120
  getAndClearPendingCookies: () => string[];
110
121
  getDraftModeCookieHeader: () => string | null | undefined;
122
+ /**
123
+ * Whether the posted-to route resolves to an App Router *page* (as opposed to
124
+ * a route handler or no match). Multipart form POSTs to a page are always
125
+ * server-action attempts in Next.js, so a body that decodes to no action must
126
+ * surface as 404 action-not-found rather than rendering the page. Route
127
+ * handlers (which run *after* this dispatch in vinext) legitimately receive
128
+ * raw multipart POSTs, so they must still fall through. See issue #1340.
129
+ */
130
+ hasPageRoute: boolean;
111
131
  maxActionBodySize: number;
112
132
  middlewareHeaders: Headers | null;
113
133
  readFormDataWithLimit: ReadFormDataWithLimit;
@@ -143,7 +163,8 @@ type HandleServerActionRscRequestOptions<TElement, TRoute extends AppServerActio
143
163
  isRscRequest: boolean;
144
164
  loadServerAction: (actionId: string) => Promise<unknown>;
145
165
  matchRoute: (pathname: string) => AppServerActionMatch<TRoute> | null;
146
- maxActionBodySize: number;
166
+ maxActionBodySize: number; /** Verbatim `serverActions.bodySizeLimit` config string (e.g. "2mb") for the body-exceeded error. */
167
+ maxActionBodySizeLabel: string;
147
168
  middlewareHeaders: Headers | null;
148
169
  middlewareStatus: number | null | undefined;
149
170
  mountedSlotsHeader: string | null;
@@ -15,6 +15,7 @@ import { applyEdgeRuntimeHeader } from "./app-page-response.js";
15
15
  import { getNextErrorDigest, parseNextHttpErrorDigest, parseNextRedirectDigest } from "./next-error-digest.js";
16
16
  import { createServerActionNotFoundResponse, getServerActionNotFoundMessage, isServerActionNotFoundError } from "./server-action-not-found.js";
17
17
  import { deferUntilStreamConsumed } from "./app-page-stream.js";
18
+ import { resolveAppPageNavigationParams } from "./app-page-element-builder.js";
18
19
  import { resolveAppPageActionRerenderTarget } from "./app-page-request.js";
19
20
  import { buildPageCacheTags } from "./implicit-tags.js";
20
21
  import { getSetCookieName } from "./cookie-utils.js";
@@ -110,6 +111,15 @@ function isRequestBodyTooLarge(error) {
110
111
  return error instanceof Error && error.message === "Request body too large";
111
112
  }
112
113
  /**
114
+ * Build the error thrown when a server-action request body exceeds the
115
+ * configured size limit. Matches Next.js' `Body exceeded {limit} limit.`
116
+ * message + docs link (action-handler.ts) verbatim — including the original
117
+ * config string (e.g. "2mb") — so it reads identically in logs.
118
+ */
119
+ function createBodyExceededError(limitLabel) {
120
+ return /* @__PURE__ */ new Error(`Body exceeded ${limitLabel} limit.\nTo configure the body size limit for Server Actions, see: https://nextjs.org/docs/app/api-reference/next-config-js/serverActions#bodysizelimit`);
121
+ }
122
+ /**
113
123
  * Collapse repeated `cookies().set(name, ...)` / `cookies().delete(name)`
114
124
  * calls down to the last value per name, matching Next.js'
115
125
  * `MutableRequestCookiesAdapter` semantics. Next.js stores response cookies in
@@ -325,7 +335,13 @@ async function handleProgressiveServerActionRequest(options) {
325
335
  return payloadResponse;
326
336
  }
327
337
  const action = await options.decodeAction(body);
328
- if (!isAppServerActionFunction(action)) return null;
338
+ if (!isAppServerActionFunction(action)) {
339
+ if (options.hasPageRoute) return createActionNotFoundResponse(null, {
340
+ clearRequestContext: options.clearRequestContext,
341
+ getAndClearPendingCookies: options.getAndClearPendingCookies
342
+ });
343
+ return null;
344
+ }
329
345
  let actionRedirect = null;
330
346
  let actionError = void 0;
331
347
  let actionFailed = false;
@@ -411,23 +427,66 @@ async function handleProgressiveServerActionRequest(options) {
411
427
  return internalServerErrorResponse(process.env.NODE_ENV === "production" ? void 0 : "Server action parsing failed: " + getServerActionFailureMessage(error));
412
428
  }
413
429
  }
430
+ /**
431
+ * Render the response for a fetch (client-invoked) server action whose request
432
+ * body exceeds the configured `serverActions.bodySizeLimit`.
433
+ *
434
+ * Next.js does not return a bare 413 here: it throws the body-exceeded error
435
+ * before the action runs, then — for fetch actions — emits a Flight response
436
+ * with status 500 carrying the rejected action result, so the nearest client
437
+ * error boundary catches it (see action-handler.ts, the `isFetchAction` branch
438
+ * of the generic error path). vinext mirrors that by rendering a Flight stream
439
+ * with `returnValue: { ok: false }` and no page root (the action never ran, so
440
+ * nothing was revalidated and the page render is skipped). A bare 413 plain
441
+ * response would bypass the boundary and surface the wrong status/content-type.
442
+ */
443
+ async function renderFetchActionBodyExceededResponse(options) {
444
+ const error = createBodyExceededError(options.maxActionBodySizeLabel);
445
+ console.error("[vinext] Server action error:", error);
446
+ options.reportRequestError(normalizeError(error), {
447
+ path: options.cleanPathname,
448
+ method: options.request.method,
449
+ headers: Object.fromEntries(options.request.headers.entries())
450
+ }, {
451
+ routerKind: "App Router",
452
+ routePath: options.cleanPathname,
453
+ routeType: "action"
454
+ });
455
+ getAndClearActionRevalidationKind();
456
+ options.getAndClearPendingCookies();
457
+ const returnValue = {
458
+ ok: false,
459
+ data: options.sanitizeErrorForClient(error)
460
+ };
461
+ const temporaryReferences = options.createTemporaryReferenceSet();
462
+ const onRenderError = options.createRscOnErrorHandler(options.request, options.cleanPathname, options.cleanPathname);
463
+ const rscStream = await options.renderToReadableStream({ returnValue }, {
464
+ temporaryReferences,
465
+ onError: onRenderError
466
+ });
467
+ const headers = new Headers({
468
+ "Content-Type": VINEXT_RSC_CONTENT_TYPE,
469
+ Vary: VINEXT_RSC_VARY_HEADER
470
+ });
471
+ applyEdgeRuntimeHeader(headers, options.isEdgeRuntime);
472
+ mergeMiddlewareResponseHeaders(headers, options.middlewareHeaders);
473
+ applyRscCompatibilityIdHeader(headers);
474
+ return createServerActionRscResponse(rscStream, {
475
+ status: 500,
476
+ headers
477
+ }, options.clearRequestContext);
478
+ }
414
479
  async function handleServerActionRscRequest(options) {
415
480
  if (options.request.method.toUpperCase() !== "POST" || !options.actionId) return null;
416
481
  const csrfResponse = validateCsrfOrigin(options.request, options.allowedOrigins);
417
482
  if (csrfResponse) return csrfResponse;
418
- if (parseInt(options.request.headers.get("content-length") || "0", 10) > options.maxActionBodySize) {
419
- options.clearRequestContext();
420
- return payloadTooLargeResponse();
421
- }
483
+ if (parseInt(options.request.headers.get("content-length") || "0", 10) > options.maxActionBodySize) return renderFetchActionBodyExceededResponse(options);
422
484
  try {
423
485
  let body;
424
486
  try {
425
487
  body = options.contentType.startsWith("multipart/form-data") ? await options.readFormDataWithLimit(options.request, options.maxActionBodySize) : await options.readBodyWithLimit(options.request, options.maxActionBodySize);
426
488
  } catch (error) {
427
- if (isRequestBodyTooLarge(error)) {
428
- options.clearRequestContext();
429
- return payloadTooLargeResponse();
430
- }
489
+ if (isRequestBodyTooLarge(error)) return renderFetchActionBodyExceededResponse(options);
431
490
  throw error;
432
491
  }
433
492
  const payloadResponse = await validateServerActionPayload(body);
@@ -533,10 +592,11 @@ async function handleServerActionRscRequest(options) {
533
592
  url: redirectTarget
534
593
  });
535
594
  setHeadersContext(headersContextFromRequest(redirectRenderRequest));
595
+ const redirectNavigationParams = resolveAppPageNavigationParams(targetMatch.route, targetMatch.params, targetPathname, null);
536
596
  options.setNavigationContext({
537
597
  pathname: targetPathname,
538
598
  searchParams: redirectTarget.searchParams,
539
- params: targetMatch.params
599
+ params: redirectNavigationParams
540
600
  });
541
601
  setCurrentFetchCacheMode(options.resolveRouteFetchCacheMode?.(targetMatch.route) ?? null);
542
602
  setCurrentFetchSoftTags(buildServerActionPageTags(targetMatch.route, targetPathname));
@@ -599,7 +659,7 @@ async function handleServerActionRscRequest(options) {
599
659
  let errorPattern = match ? match.route.pattern : options.cleanPathname;
600
660
  if (match) {
601
661
  const { route: actionRoute, params: actionParams } = match;
602
- const actionRerenderTarget = resolveAppPageActionRerenderTarget({
662
+ const actionRerenderTarget = await resolveAppPageActionRerenderTarget({
603
663
  cleanPathname: options.cleanPathname,
604
664
  currentParams: actionParams,
605
665
  currentRoute: actionRoute,
@@ -609,10 +669,11 @@ async function handleServerActionRscRequest(options) {
609
669
  isRscRequest: options.isRscRequest,
610
670
  toInterceptOpts: options.toInterceptOpts
611
671
  });
672
+ const resolvedActionNavigationParams = resolveAppPageNavigationParams(actionRerenderTarget.route, actionRerenderTarget.navigationParams, options.cleanPathname, actionRerenderTarget.interceptOpts);
612
673
  options.setNavigationContext({
613
674
  pathname: options.cleanPathname,
614
675
  searchParams: options.searchParams,
615
- params: actionRerenderTarget.navigationParams
676
+ params: resolvedActionNavigationParams
616
677
  });
617
678
  await options.ensureRouteLoaded?.(actionRerenderTarget.route);
618
679
  setCurrentFetchCacheMode(options.resolveRouteFetchCacheMode?.(actionRerenderTarget.route) ?? null);
@@ -30,6 +30,12 @@ declare function handleSsr(rscStream: ReadableStream<Uint8Array>, navContext: Na
30
30
  * SSR head. Undefined or empty disables emission entirely.
31
31
  */
32
32
  clientTraceMetadata?: readonly string[];
33
+ /**
34
+ * Maximum total length (in characters) of the preload `Link` header React
35
+ * emits during SSR. `0` disables emission. From `reactMaxHeadersLength` in
36
+ * `next.config`. Undefined falls back to React's own default.
37
+ */
38
+ reactMaxHeadersLength?: number;
33
39
  rootParams?: RootParams; /** Dev-only: original server error to surface in the browser overlay. */
34
40
  initialDevServerError?: unknown;
35
41
  /** When true, wait for the full React tree (including Suspense boundaries)
@@ -12,18 +12,25 @@ import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
12
12
  import { BfcacheStateKeyMapContext, ElementsContext, Slot } from "../shims/slot.js";
13
13
  import { createSsrErrorMetaRenderer } from "./app-ssr-error-meta.js";
14
14
  import { createNavigationRuntimeRscMetadataScript, createRscEmbedTransform, createTickBufferedTransform } from "./app-ssr-stream.js";
15
- import { BeforeInteractiveContext } from "../shims/before-interactive-context.js";
16
- import { runWithRootParamsScope } from "../shims/root-params.js";
17
15
  import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap } from "./app-browser-state.js";
18
16
  import { RSC_FORM_STATE_GLOBAL } from "./app-browser-hydration.js";
19
17
  import { createClientReferencePreloader } from "./app-client-reference-preloader.js";
20
18
  import { deferUntilStreamConsumed } from "./app-page-stream.js";
19
+ import { runWithRootParamsScope } from "../shims/root-params.js";
20
+ import { BeforeInteractiveContext } from "../shims/before-interactive-context.js";
21
21
  import { createInitialDevServerErrorScript } from "./dev-initial-server-error.js";
22
22
  import { Fragment, createElement, use } from "react";
23
23
  import { renderToReadableStream, renderToStaticMarkup } from "react-dom/server.edge";
24
24
  import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
25
25
  import clientReferences from "virtual:vite-rsc/client-references";
26
26
  //#region src/server/app-ssr-entry.ts
27
+ /**
28
+ * Default cap for the preload `Link` header, matching Next.js's
29
+ * `defaultConfig.reactMaxHeadersLength`. Used when no config value threads
30
+ * through (e.g. error-boundary renders) so React's internal cap agrees with
31
+ * the response-layer combine cap.
32
+ */
33
+ const DEFAULT_REACT_MAX_HEADERS_LENGTH = 6e3;
27
34
  const clientReferencePreloader = createClientReferencePreloader({
28
35
  getReferences() {
29
36
  return clientReferences;
@@ -175,7 +182,15 @@ async function handleSsr(rscStream, navContext, fontData, options) {
175
182
  const ssrRoot = withScriptNonce(createElement(BeforeInteractiveContext.Provider, { value: registerBeforeInteractiveInlineScript }, ssrTree), options?.scriptNonce);
176
183
  const bootstrapModuleUrl = extractBootstrapModuleUrl(await import.meta.viteRsc.loadBootstrapScriptContent("index"));
177
184
  const errorMetaRenderer = createSsrErrorMetaRenderer({ basePath: options?.basePath });
185
+ let reactLinkHeader = "";
186
+ const maxHeadersLength = options?.reactMaxHeadersLength ?? DEFAULT_REACT_MAX_HEADERS_LENGTH;
187
+ const captureHeaders = maxHeadersLength > 0;
178
188
  const htmlStream = await renderToReadableStream(ssrRoot, {
189
+ onHeaders: captureHeaders ? (headers) => {
190
+ const link = headers.get("Link");
191
+ if (link) reactLinkHeader = link;
192
+ } : void 0,
193
+ maxHeadersLength: captureHeaders ? maxHeadersLength : void 0,
179
194
  bootstrapModules: bootstrapModuleUrl ? [bootstrapModuleUrl] : void 0,
180
195
  formState: options?.formState ?? null,
181
196
  nonce: options?.scriptNonce,
@@ -210,7 +225,8 @@ async function handleSsr(rscStream, navContext, fontData, options) {
210
225
  return {
211
226
  htmlStream: deferUntilStreamConsumed(htmlStream.pipeThrough(createTickBufferedTransform(rscEmbed, getInsertedHTML, getBeforeInteractiveHeadHTML, inlineCssManifest, inlineCssFontStyles, inlineCssFontStyleFallbackHTML, options?.scriptNonce)), cleanup),
212
227
  metadataReady: Promise.resolve(),
213
- capturedRscData: options?.capturedRscDataRef?.value ?? null
228
+ capturedRscData: options?.capturedRscDataRef?.value ?? null,
229
+ linkHeader: reactLinkHeader
214
230
  };
215
231
  } catch (error) {
216
232
  cleanup();
@@ -89,6 +89,12 @@ function fixPreloadAs(html) {
89
89
  const LINK_TAG_RE = /<link\b[^>]*>/gi;
90
90
  const HTML_REWRITE_EXCLUDED_REGION_RE = /<!--[\s\S]*?-->|<(script|style|textarea|title)\b[^>]*>[\s\S]*?<\/\1\s*>/gi;
91
91
  const HTML_REWRITE_EXCLUDED_REGION_START_RE = /<!--|<(script|style|textarea|title)\b[^>]*>/gi;
92
+ const CLOSE_TAG_RES = {
93
+ script: /<\/script\s*>/i,
94
+ style: /<\/style\s*>/i,
95
+ textarea: /<\/textarea\s*>/i,
96
+ title: /<\/title\s*>/i
97
+ };
92
98
  function getHtmlAttribute(tag, name) {
93
99
  const attrRe = /\s([^\s"'=<>`]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
94
100
  let match;
@@ -141,7 +147,9 @@ function findTrailingOpenHtmlRewriteExcludedRegionStart(html) {
141
147
  }
142
148
  const tagName = match[1]?.toLowerCase();
143
149
  if (!tagName) continue;
144
- const close = new RegExp(`</${tagName}\\s*>`, "i").exec(html.slice(HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex));
150
+ const closeTagRe = CLOSE_TAG_RES[tagName];
151
+ if (!closeTagRe) continue;
152
+ const close = closeTagRe.exec(html.slice(HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex));
145
153
  if (!close) return start;
146
154
  HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex += close.index + close[0].length;
147
155
  }
@@ -1,3 +1,4 @@
1
+ import { normalizePathSeparators } from "../utils/path.js";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
4
  //#region src/server/dev-lockfile.ts
@@ -98,7 +99,7 @@ function formatAlreadyRunningError(opts) {
98
99
  if (!existing) return [
99
100
  "Another vinext dev server appears to be running in this directory.",
100
101
  "",
101
- `Stale lock file: ${path.relative(cwd, lockfilePath)}`,
102
+ `Stale lock file: ${normalizePathSeparators(path.relative(cwd, lockfilePath))}`,
102
103
  "Remove it manually if no server is running, then re-run `vinext dev`."
103
104
  ].join("\n");
104
105
  const killCommand = process.platform === "win32" ? `taskkill /PID ${existing.pid} /F` : `kill ${existing.pid}`;
@@ -36,7 +36,7 @@ declare function parseCookieLocale(req: IncomingMessage, i18nConfig: NextI18nCon
36
36
  * 4. Render the component to HTML
37
37
  * 5. Wrap in _document shell and send response
38
38
  */
39
- declare function createSSRHandler(server: ViteDevServer, runner: ModuleImporter, routes: Route[], pagesDir: string, i18nConfig?: NextI18nConfig | null, fileMatcher?: ValidFileMatcher, basePath?: string, trailingSlash?: boolean, hasMiddleware?: boolean,
39
+ declare function createSSRHandler(server: ViteDevServer, runner: ModuleImporter, routes: Route[], pagesDir: string, i18nConfig?: NextI18nConfig | null, fileMatcher?: ValidFileMatcher, basePath?: string, trailingSlash?: boolean, hasMiddleware?: boolean, hasRewrites?: boolean,
40
40
  /**
41
41
  * Allow-list of OpenTelemetry propagation keys to emit as `<meta>` tags
42
42
  * in the SSR head. Sourced from `experimental.clientTraceMetadata` in