vinext 1.2.0 → 1.3.0

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 (283) hide show
  1. package/README.md +13 -10
  2. package/dist/build/client-build-config.d.ts +64 -3
  3. package/dist/build/client-build-config.js +131 -4
  4. package/dist/build/client-reference-groups.d.ts +163 -0
  5. package/dist/build/client-reference-groups.js +534 -0
  6. package/dist/build/module-dependency-cache.d.ts +31 -0
  7. package/dist/build/module-dependency-cache.js +51 -1
  8. package/dist/build/next-client-runtime-manifests.d.ts +10 -1
  9. package/dist/build/next-client-runtime-manifests.js +19 -4
  10. package/dist/build/nitro-public-files.d.ts +24 -0
  11. package/dist/build/nitro-public-files.js +60 -0
  12. package/dist/build/output-root-externals.d.ts +21 -0
  13. package/dist/build/output-root-externals.js +40 -0
  14. package/dist/build/pages-client-assets-module.js +1 -1
  15. package/dist/build/prerender-paths.js +6 -3
  16. package/dist/build/prerender.js +1 -1
  17. package/dist/build/server-actions-encryption-key.d.ts +23 -0
  18. package/dist/build/server-actions-encryption-key.js +101 -0
  19. package/dist/build/ssr-manifest.d.ts +3 -0
  20. package/dist/build/ssr-manifest.js +10 -1
  21. package/dist/check.js +5 -6
  22. package/dist/cli-dev-config.js +3 -3
  23. package/dist/cli.js +1 -1
  24. package/dist/client/client-rewrite-params.d.ts +9 -0
  25. package/dist/client/client-rewrite-params.js +20 -0
  26. package/dist/client/dev-error-overlay.js +2 -1
  27. package/dist/client/navigation-runtime.d.ts +25 -2
  28. package/dist/client/navigation-runtime.js +42 -11
  29. package/dist/client/vinext-next-data.d.ts +0 -1
  30. package/dist/config/config-matchers.d.ts +15 -40
  31. package/dist/config/config-matchers.js +195 -262
  32. package/dist/config/config-source.d.ts +34 -0
  33. package/dist/config/config-source.js +141 -0
  34. package/dist/config/header-params.d.ts +29 -0
  35. package/dist/config/header-params.js +106 -0
  36. package/dist/config/next-config.d.ts +7 -5
  37. package/dist/config/next-config.js +120 -7
  38. package/dist/entries/app-browser-entry.d.ts +14 -1
  39. package/dist/entries/app-browser-entry.js +26 -36
  40. package/dist/entries/app-rsc-entry.d.ts +2 -0
  41. package/dist/entries/app-rsc-entry.js +92 -57
  42. package/dist/entries/app-rsc-manifest.d.ts +10 -0
  43. package/dist/entries/app-rsc-manifest.js +20 -1
  44. package/dist/entries/app-ssr-entry.d.ts +4 -1
  45. package/dist/entries/app-ssr-entry.js +9 -2
  46. package/dist/entries/pages-client-entry.d.ts +1 -0
  47. package/dist/entries/pages-client-entry.js +6 -6
  48. package/dist/entries/pages-server-entry.js +17 -14
  49. package/dist/index.d.ts +11 -0
  50. package/dist/index.js +350 -175
  51. package/dist/init.d.ts +1 -1
  52. package/dist/init.js +103 -6
  53. package/dist/plugins/ast-utils.d.ts +53 -2
  54. package/dist/plugins/ast-utils.js +90 -2
  55. package/dist/plugins/cloudflare-dev-module-urls.d.ts +41 -0
  56. package/dist/plugins/cloudflare-dev-module-urls.js +271 -0
  57. package/dist/plugins/commonjs-esm-facade.js +2 -2
  58. package/dist/plugins/css-module-imports.js +13 -20
  59. package/dist/plugins/dynamic-preload-metadata.js +2 -3
  60. package/dist/plugins/extensionless-dynamic-import.js +2 -3
  61. package/dist/plugins/fonts.d.ts +1 -1
  62. package/dist/plugins/fonts.js +3 -3
  63. package/dist/plugins/ignore-dynamic-requests.js +2 -3
  64. package/dist/plugins/import-meta-url.js +3 -4
  65. package/dist/plugins/instrumentation-client.js +3 -2
  66. package/dist/plugins/middleware-export-validation.js +2 -3
  67. package/dist/plugins/og-harfbuzz.js +2 -3
  68. package/dist/plugins/optimize-imports.d.ts +25 -2
  69. package/dist/plugins/optimize-imports.js +109 -24
  70. package/dist/plugins/pages-node-externals.js +2 -3
  71. package/dist/plugins/remove-console.js +2 -18
  72. package/dist/plugins/require-condition-resolution.js +3 -3
  73. package/dist/plugins/require-context.js +2 -3
  74. package/dist/plugins/rsc-client-reference-loaders.d.ts +45 -1
  75. package/dist/plugins/rsc-client-reference-loaders.js +336 -18
  76. package/dist/plugins/strip-server-exports.js +3 -4
  77. package/dist/plugins/styled-jsx.js +2 -3
  78. package/dist/plugins/use-cache-callable.d.ts +26 -2
  79. package/dist/plugins/use-cache-callable.js +101 -36
  80. package/dist/plugins/worker-image-imports.d.ts +1 -0
  81. package/dist/plugins/worker-image-imports.js +31 -34
  82. package/dist/plugins/worker-use-server.js +38 -14
  83. package/dist/routing/app-route-graph.d.ts +1 -1
  84. package/dist/routing/browser-route-manifest.d.ts +43 -0
  85. package/dist/routing/browser-route-manifest.js +195 -0
  86. package/dist/routing/hashed-route-patterns.d.ts +4 -0
  87. package/dist/routing/hashed-route-patterns.js +22 -0
  88. package/dist/routing/optimistic-route-trie.d.ts +27 -0
  89. package/dist/routing/optimistic-route-trie.js +102 -0
  90. package/dist/routing/route-validation.d.ts +6 -1
  91. package/dist/routing/route-validation.js +26 -10
  92. package/dist/server/api-handler.js +1 -1
  93. package/dist/server/app-action-forwarding.d.ts +8 -0
  94. package/dist/server/app-action-forwarding.js +17 -3
  95. package/dist/server/app-browser-entry.js +149 -62
  96. package/dist/server/app-browser-error.d.ts +3 -1
  97. package/dist/server/app-browser-error.js +7 -2
  98. package/dist/server/app-browser-interception-context.d.ts +2 -2
  99. package/dist/server/app-browser-interception-context.js +1 -1
  100. package/dist/server/app-browser-mpa-navigation.d.ts +1 -0
  101. package/dist/server/app-browser-mpa-navigation.js +6 -5
  102. package/dist/server/app-browser-navigation-controller.d.ts +17 -6
  103. package/dist/server/app-browser-navigation-controller.js +88 -26
  104. package/dist/server/app-browser-prefetch-response.js +2 -2
  105. package/dist/server/app-browser-state.d.ts +2 -2
  106. package/dist/server/app-browser-state.js +1 -1
  107. package/dist/server/app-browser-supplemental-refresh.js +1 -1
  108. package/dist/server/app-browser-visible-commit.d.ts +4 -4
  109. package/dist/server/app-client-reference-preloader.d.ts +23 -0
  110. package/dist/server/app-client-reference-preloader.js +30 -7
  111. package/dist/server/app-fallback-renderer.js +2 -1
  112. package/dist/server/app-middleware.d.ts +6 -0
  113. package/dist/server/app-middleware.js +8 -10
  114. package/dist/server/app-optimistic-routing.d.ts +8 -10
  115. package/dist/server/app-optimistic-routing.js +4 -95
  116. package/dist/server/app-page-boundary-render.js +8 -1
  117. package/dist/server/app-page-cache-render.js +1 -0
  118. package/dist/server/app-page-dispatch.js +3 -1
  119. package/dist/server/app-page-render.js +4 -2
  120. package/dist/server/app-page-rsc-render-status.d.ts +1 -0
  121. package/dist/server/app-page-rsc-render-status.js +1 -0
  122. package/dist/server/app-page-search-params-observation.d.ts +1 -4
  123. package/dist/server/app-page-search-params-observation.js +8 -10
  124. package/dist/server/app-page-stream.d.ts +7 -0
  125. package/dist/server/app-page-stream.js +1 -0
  126. package/dist/server/app-pages-bridge.d.ts +2 -1
  127. package/dist/server/app-pages-bridge.js +12 -8
  128. package/dist/server/app-prerender-endpoints.js +1 -1
  129. package/dist/server/app-prerender-static-params.js +3 -2
  130. package/dist/server/app-render-abort-error.d.ts +19 -0
  131. package/dist/server/app-render-abort-error.js +31 -1
  132. package/dist/server/app-render-dependency.js +42 -2
  133. package/dist/server/app-request-stage-independent-entry.js +7 -7
  134. package/dist/server/app-response-stage-entry.js +5 -5
  135. package/dist/server/app-route-handler-dispatch.js +3 -1
  136. package/dist/server/app-route-handler-execution.d.ts +2 -0
  137. package/dist/server/app-route-handler-execution.js +9 -2
  138. package/dist/server/app-route-handler-middleware-context.d.ts +2 -1
  139. package/dist/server/app-route-handler-middleware-context.js +3 -3
  140. package/dist/server/app-route-handler-response.d.ts +2 -1
  141. package/dist/server/app-route-handler-response.js +2 -2
  142. package/dist/server/app-route-handler-runtime.d.ts +2 -0
  143. package/dist/server/app-route-handler-runtime.js +21 -4
  144. package/dist/server/app-route-module-loader.js +7 -6
  145. package/dist/server/app-route-tree.d.ts +20 -0
  146. package/dist/server/app-route-tree.js +18 -0
  147. package/dist/server/app-router-entry.js +7 -7
  148. package/dist/server/app-rsc-cache-busting.d.ts +16 -0
  149. package/dist/server/app-rsc-cache-busting.js +29 -12
  150. package/dist/server/app-rsc-combined-handler.js +8 -1
  151. package/dist/server/app-rsc-errors.d.ts +7 -0
  152. package/dist/server/app-rsc-errors.js +9 -2
  153. package/dist/server/app-rsc-handler.d.ts +4 -1
  154. package/dist/server/app-rsc-handler.js +151 -135
  155. package/dist/server/app-rsc-request-normalization.js +3 -3
  156. package/dist/server/app-rsc-response-finalizer.d.ts +5 -0
  157. package/dist/server/app-rsc-response-finalizer.js +4 -1
  158. package/dist/server/app-rsc-response-stage.js +5 -3
  159. package/dist/server/app-rsc-route-matching.d.ts +1 -0
  160. package/dist/server/app-rsc-route-matching.js +1 -1
  161. package/dist/server/app-server-action-execution.d.ts +23 -6
  162. package/dist/server/app-server-action-execution.js +51 -57
  163. package/dist/server/app-ssr-entry.d.ts +6 -0
  164. package/dist/server/app-ssr-entry.js +21 -11
  165. package/dist/server/app-ssr-stream.d.ts +7 -0
  166. package/dist/server/app-ssr-stream.js +13 -1
  167. package/dist/server/app-visited-response-cache.js +2 -2
  168. package/dist/server/config-headers.js +13 -4
  169. package/dist/server/defer-until-stream-consumed.d.ts +2 -1
  170. package/dist/server/defer-until-stream-consumed.js +3 -1
  171. package/dist/server/dev-server.js +1 -1
  172. package/dist/server/image-optimization.js +1 -0
  173. package/dist/server/isolate-shared-promise.d.ts +10 -0
  174. package/dist/server/isolate-shared-promise.js +33 -0
  175. package/dist/server/isr-cache.js +3 -2
  176. package/dist/server/metadata-route-response.js +2 -2
  177. package/dist/server/middleware-matcher-pattern.d.ts +0 -1
  178. package/dist/server/middleware-matcher-pattern.js +11 -140
  179. package/dist/server/middleware-matcher.js +5 -6
  180. package/dist/server/middleware-response-headers.d.ts +31 -0
  181. package/dist/server/middleware-response-headers.js +57 -1
  182. package/dist/server/middleware-runtime.js +30 -17
  183. package/dist/server/navigation-planner.d.ts +2 -2
  184. package/dist/server/navigation-planner.js +1 -1
  185. package/dist/server/nitro-public-files-plugin.d.ts +38 -0
  186. package/dist/server/nitro-public-files-plugin.js +106 -0
  187. package/dist/server/nitro-public-files.d.ts +21 -0
  188. package/dist/server/nitro-public-files.js +39 -0
  189. package/dist/server/open-redirect.d.ts +11 -4
  190. package/dist/server/open-redirect.js +11 -4
  191. package/dist/server/pages-api-route.js +2 -2
  192. package/dist/server/pages-asset-tags.d.ts +5 -0
  193. package/dist/server/pages-asset-tags.js +9 -2
  194. package/dist/server/pages-client-assets.d.ts +4 -1
  195. package/dist/server/pages-data-route.js +2 -1
  196. package/dist/server/pages-i18n.d.ts +18 -0
  197. package/dist/server/pages-i18n.js +18 -2
  198. package/dist/server/pages-node-compat.d.ts +5 -3
  199. package/dist/server/pages-node-compat.js +35 -16
  200. package/dist/server/pages-page-data.js +1 -1
  201. package/dist/server/pages-page-handler.js +1 -1
  202. package/dist/server/pages-page-response.js +1 -1
  203. package/dist/server/pages-request-pipeline.d.ts +6 -1
  204. package/dist/server/pages-request-pipeline.js +35 -39
  205. package/dist/server/pages-request-stage-entry.js +20 -10
  206. package/dist/server/pages-response-stage-entry.js +1 -1
  207. package/dist/server/pages-revalidate.js +3 -2
  208. package/dist/server/pages-router-entry.js +2 -2
  209. package/dist/server/prod-server.d.ts +2 -2
  210. package/dist/server/prod-server.js +74 -36
  211. package/dist/server/proxy-trust.d.ts +6 -0
  212. package/dist/server/proxy-trust.js +12 -6
  213. package/dist/server/request-pipeline.d.ts +5 -0
  214. package/dist/server/request-pipeline.js +12 -1
  215. package/dist/server/rsc-stream-hints.d.ts +1 -1
  216. package/dist/server/rsc-stream-hints.js +21 -3
  217. package/dist/server/server-reference-loader.js +10 -1
  218. package/dist/server/static-file-cache.js +15 -3
  219. package/dist/server/worker-image-optimization.js +19 -6
  220. package/dist/server/worker-utils.js +1 -1
  221. package/dist/shims/cache-handler.js +3 -2
  222. package/dist/shims/cache-request-state.d.ts +40 -3
  223. package/dist/shims/cache-request-state.js +70 -5
  224. package/dist/shims/cache-runtime.js +47 -19
  225. package/dist/shims/cache.d.ts +2 -2
  226. package/dist/shims/cache.js +17 -8
  227. package/dist/shims/cdn-purge-routes.d.ts +24 -0
  228. package/dist/shims/cdn-purge-routes.js +77 -0
  229. package/dist/shims/dynamic.js +17 -4
  230. package/dist/shims/form.js +8 -3
  231. package/dist/shims/head.js +1 -1
  232. package/dist/shims/headers.js +2 -2
  233. package/dist/shims/image-props.d.ts +93 -0
  234. package/dist/shims/image-props.js +323 -0
  235. package/dist/shims/image.d.ts +4 -18
  236. package/dist/shims/image.js +4 -319
  237. package/dist/shims/internal/headers-state.d.ts +7 -0
  238. package/dist/shims/internal/headers-state.js +14 -2
  239. package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +4 -1
  240. package/dist/shims/internal/hybrid-client-route-owner-direct.js +32 -15
  241. package/dist/shims/internal/hybrid-client-route-owner.js +9 -8
  242. package/dist/shims/link-prefetch.js +5 -4
  243. package/dist/shims/link.js +7 -5
  244. package/dist/shims/navigation.d.ts +1 -1
  245. package/dist/shims/navigation.js +21 -10
  246. package/dist/shims/og.js +35 -10
  247. package/dist/shims/request-context.d.ts +13 -0
  248. package/dist/shims/router.js +28 -52
  249. package/dist/shims/server.d.ts +42 -8
  250. package/dist/shims/server.js +99 -15
  251. package/dist/shims/thenable-params.d.ts +6 -1
  252. package/dist/shims/thenable-params.js +11 -2
  253. package/dist/shims/unified-request-context.d.ts +5 -0
  254. package/dist/shims/unified-request-context.js +2 -0
  255. package/dist/shims/url-utils.d.ts +7 -0
  256. package/dist/shims/url-utils.js +12 -3
  257. package/dist/utils/asset-prefix.d.ts +7 -0
  258. package/dist/utils/asset-prefix.js +10 -1
  259. package/dist/utils/client-build-manifest.js +12 -5
  260. package/dist/utils/client-runtime-metadata.d.ts +1 -0
  261. package/dist/utils/client-runtime-metadata.js +15 -2
  262. package/dist/utils/domain-locale.js +1 -1
  263. package/dist/utils/lazy-chunks.d.ts +4 -0
  264. package/dist/utils/manifest-paths.d.ts +14 -0
  265. package/dist/utils/manifest-paths.js +22 -1
  266. package/dist/utils/path-to-regexp.d.ts +21 -0
  267. package/dist/{server/middleware-path-to-regexp.js → utils/path-to-regexp.js} +14 -11
  268. package/dist/utils/plugin-react-version.d.ts +15 -0
  269. package/dist/utils/plugin-react-version.js +64 -0
  270. package/dist/utils/plugin-rsc-version.d.ts +2 -0
  271. package/dist/utils/plugin-rsc-version.js +28 -2
  272. package/dist/utils/public-routes.d.ts +2 -0
  273. package/dist/utils/public-routes.js +18 -7
  274. package/dist/utils/react-version.d.ts +7 -0
  275. package/dist/utils/react-version.js +39 -16
  276. package/package.json +6 -6
  277. package/dist/build/nitro-route-rules.d.ts +0 -47
  278. package/dist/build/nitro-route-rules.js +0 -79
  279. package/dist/plugins/cache-flight-codec.d.ts +0 -19
  280. package/dist/plugins/cache-flight-codec.js +0 -68
  281. package/dist/server/middleware-path-to-regexp.d.ts +0 -13
  282. package/dist/utils/regex-safety.d.ts +0 -17
  283. package/dist/utils/regex-safety.js +0 -789
@@ -0,0 +1,141 @@
1
+ import { normalizePathTokens, parsePathPattern, pathTokensToRegExpSource } from "../utils/path-to-regexp.js";
2
+ //#region src/config/config-source.ts
3
+ /**
4
+ * Compiles and matches next.config.js redirect, rewrite and header sources.
5
+ *
6
+ * Kept apart from config-matchers so the Pages router can match sources
7
+ * synchronously with the same code as the server, without bundling the rest
8
+ * of config-matchers.
9
+ */
10
+ /**
11
+ * Cache for compiled redirect/rewrite/header sources.
12
+ *
13
+ * Sources come from next.config.js and never change at runtime. Without
14
+ * caching, every request re-parses and recompiles every rule. On apps with
15
+ * many locale-prefixed rules this dominated profiling at ~2.4 seconds of CPU
16
+ * self-time.
17
+ *
18
+ * Value is `null` when the source is invalid, so we skip it on subsequent
19
+ * requests too without re-parsing it.
20
+ */
21
+ const _compiledSourceCache = /* @__PURE__ */ new Map();
22
+ /**
23
+ * Compile a redirect/rewrite/header source the way Next.js does at runtime:
24
+ * path-to-regexp 6 with `strict: true`, `delimiter: "/"` and case-insensitive
25
+ * matching, then `modifyRouteRegex` appends an optional trailing slash.
26
+ *
27
+ * Returns an error for sources path-to-regexp rejects. Like Next.js, custom
28
+ * param patterns are not checked for catastrophic backtracking.
29
+ *
30
+ * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/server/lib/router-utils/filesystem.ts
31
+ * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/shared/lib/router/utils/path-match.ts
32
+ */
33
+ function compileConfigSourceUncached(source) {
34
+ const parsed = parseConfigSource(source);
35
+ if ("kind" in parsed) return parsed;
36
+ const { tokens, re } = parsed;
37
+ return {
38
+ re,
39
+ keys: tokens.filter((token) => typeof token !== "string" && !!token.pattern)
40
+ };
41
+ }
42
+ function parseConfigSource(source) {
43
+ try {
44
+ const tokens = parsePathPattern(source, { delimiter: "/" });
45
+ return {
46
+ tokens,
47
+ re: new RegExp(`${pathTokensToRegExpSource(tokens)}(?:\\/)?$`, "i")
48
+ };
49
+ } catch (error) {
50
+ return {
51
+ kind: "invalid",
52
+ error: error instanceof Error ? error.message : String(error)
53
+ };
54
+ }
55
+ }
56
+ /**
57
+ * Length of the RegExp Next.js builds when validating a source
58
+ * (tryToParsePath: path-to-regexp defaults, retried with normalized repeat
59
+ * tokens), or null when it cannot be built.
60
+ */
61
+ function validationRegExpLength(source) {
62
+ try {
63
+ const tokens = parsePathPattern(source);
64
+ const build = (value) => new RegExp(`${pathTokensToRegExpSource(value)}[\\/#\\?]?$`).source.length;
65
+ try {
66
+ return build(tokens);
67
+ } catch {
68
+ return build(normalizePathTokens(tokens));
69
+ }
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+ /**
75
+ * Why `source` is not a valid redirects/rewrites/headers source, or null.
76
+ * Mirrors the source checks in Next.js's checkCustomRoutes, which fail the
77
+ * build; config loading calls this to do the same.
78
+ *
79
+ * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/lib/load-custom-routes.ts
80
+ */
81
+ function configSourceError(source) {
82
+ if (!source) return "`source` is missing";
83
+ if (typeof source !== "string") return "`source` is not a string";
84
+ if (!source.startsWith("/")) return "`source` does not start with /";
85
+ const parsed = parseConfigSource(source);
86
+ if ("kind" in parsed) return parsed.error;
87
+ const length = validationRegExpLength(source);
88
+ if (length !== null && length > 4096) return "`source` exceeds max built length of 4096";
89
+ return null;
90
+ }
91
+ function compileConfigSource(source) {
92
+ let compiled = _compiledSourceCache.get(source);
93
+ if (compiled !== void 0) return compiled;
94
+ const result = compileConfigSourceUncached(source);
95
+ if ("kind" in result) {
96
+ console.warn(`[vinext] Ignoring invalid next.config.js source "${source}": ${result.error}`);
97
+ compiled = null;
98
+ } else compiled = result;
99
+ _compiledSourceCache.set(source, compiled);
100
+ return compiled;
101
+ }
102
+ /**
103
+ * Match a Next.js config source (from redirects/rewrites/headers) against a
104
+ * pathname. Returns the named params or null.
105
+ *
106
+ * Unnamed groups are dropped, as Next.js does with `removeUnnamedParams`.
107
+ * Repeated params (`:path*`, `:path+`) keep the captured string (`a/b`), and
108
+ * an optional param that did not match is `""`.
109
+ */
110
+ function matchConfigPattern(pathname, pattern) {
111
+ return execConfigSource(pathname, pattern)?.params ?? null;
112
+ }
113
+ /**
114
+ * matchConfigPattern, also reporting the optional params that did not match.
115
+ * Next.js leaves those out of its params entirely.
116
+ */
117
+ function execConfigSource(pathname, source) {
118
+ const compiled = compileConfigSource(source);
119
+ if (!compiled) return null;
120
+ const match = compiled.re.exec(pathname);
121
+ if (!match) return null;
122
+ const params = Object.create(null);
123
+ const matched = /* @__PURE__ */ new Set();
124
+ for (let i = 0; i < compiled.keys.length; i++) {
125
+ const key = compiled.keys[i].name;
126
+ if (typeof key !== "string") continue;
127
+ const value = match[i + 1];
128
+ if (value === void 0) {
129
+ if (!matched.has(key)) params[key] = "";
130
+ continue;
131
+ }
132
+ matched.add(key);
133
+ params[key] = value;
134
+ }
135
+ return {
136
+ params,
137
+ unmatched: Object.keys(params).filter((key) => !matched.has(key))
138
+ };
139
+ }
140
+ //#endregion
141
+ export { compileConfigSource, configSourceError, execConfigSource, matchConfigPattern };
@@ -0,0 +1,29 @@
1
+ //#region src/config/header-params.d.ts
2
+ /**
3
+ * Param substitution for next.config.js `headers()` keys and values.
4
+ *
5
+ * Next.js compiles each header key and value with `compileNonPath`: only
6
+ * `:name` markers for captured params are substituted, every other
7
+ * path-to-regexp metacharacter stays literal, repeated params are joined with
8
+ * their template prefix and suffix, and params are inserted verbatim (no
9
+ * encoding or validation).
10
+ *
11
+ * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/shared/lib/router/utils/prepare-destination.ts
12
+ * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/shared/lib/router/utils/route-match-utils.ts
13
+ */
14
+ /**
15
+ * Params captured by a header rule: path-to-regexp source params (`*`/`+`
16
+ * params are arrays) merged with Next.js `matchHas` captures. A has capture
17
+ * can be `undefined` when its named group did not participate in the match.
18
+ */
19
+ export type HeaderParams = Record<string, string | string[] | undefined>;
20
+ /**
21
+ * Substitute `params` into a header key or value like Next.js's
22
+ * `compileNonPath`. Throws where Next.js throws: a repeated param referenced
23
+ * without `*`/`+`, an `undefined` has capture, or a template path-to-regexp
24
+ * cannot parse.
25
+ */
26
+ export declare function compileHeaderParamTemplate(value: string, params: HeaderParams): string;
27
+ /** Whether a substituted header pair can be sent as a response header. */
28
+ export declare function isValidHeaderPair(key: string, value: string): boolean;
29
+ //#endregion
@@ -0,0 +1,106 @@
1
+ import { parsePathPattern } from "../utils/path-to-regexp.js";
2
+ //#region src/config/header-params.ts
3
+ /**
4
+ * Param substitution for next.config.js `headers()` keys and values.
5
+ *
6
+ * Next.js compiles each header key and value with `compileNonPath`: only
7
+ * `:name` markers for captured params are substituted, every other
8
+ * path-to-regexp metacharacter stays literal, repeated params are joined with
9
+ * their template prefix and suffix, and params are inserted verbatim (no
10
+ * encoding or validation).
11
+ *
12
+ * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/shared/lib/router/utils/prepare-destination.ts
13
+ * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/shared/lib/router/utils/route-match-utils.ts
14
+ */
15
+ /**
16
+ * Substitute `params` into a header key or value like Next.js's
17
+ * `compileNonPath`. Throws where Next.js throws: a repeated param referenced
18
+ * without `*`/`+`, an `undefined` has capture, or a template path-to-regexp
19
+ * cannot parse.
20
+ */
21
+ function compileHeaderParamTemplate(value, params) {
22
+ if (!value.includes(":")) return value;
23
+ const keys = Object.keys(params);
24
+ const cacheKey = JSON.stringify([value, keys]);
25
+ let compiled = _compiledTemplateCache.get(cacheKey);
26
+ if (!compiled) {
27
+ try {
28
+ compiled = { compile: compileTemplate(value, keys) };
29
+ } catch (error) {
30
+ compiled = { error };
31
+ }
32
+ _compiledTemplateCache.set(cacheKey, compiled);
33
+ }
34
+ if ("error" in compiled) throw compiled.error;
35
+ return compiled.compile(params);
36
+ }
37
+ const _compiledTemplateCache = /* @__PURE__ */ new Map();
38
+ function compileTemplate(value, keys) {
39
+ for (const key of keys) if (value.includes(`:${key}`)) value = value.replace(new RegExp(`:${key}\\*`, "g"), `:${key}--ESCAPED_PARAM_ASTERISKS`).replace(new RegExp(`:${key}\\?`, "g"), `:${key}--ESCAPED_PARAM_QUESTION`).replace(new RegExp(`:${key}\\+`, "g"), `:${key}--ESCAPED_PARAM_PLUS`).replace(new RegExp(`:${key}(?!\\w)`, "g"), `--ESCAPED_PARAM_COLON${key}`);
40
+ value = value.replace(/(:|\*|\?|\+|\(|\)|\{|\})/g, "\\$1").replace(/--ESCAPED_PARAM_PLUS/g, "+").replace(/--ESCAPED_PARAM_COLON/g, ":").replace(/--ESCAPED_PARAM_QUESTION/g, "?").replace(/--ESCAPED_PARAM_ASTERISKS/g, "*");
41
+ const compiler = safeCompile(`/${value}`);
42
+ return (params) => compiler(params).slice(1);
43
+ }
44
+ const PARAM_SEPARATOR = "_NEXTSEP_";
45
+ function safeCompile(route) {
46
+ const needsNormalization = /\/\(\.{1,3}\):[^/\s]+/.test(route) || ADJACENT_PARAMS_RE.test(route);
47
+ const routeToUse = needsNormalization ? normalizeAdjacentParameters(route) : route;
48
+ try {
49
+ const compiler = compile(routeToUse);
50
+ return needsNormalization ? (params) => stripNormalizedSeparators(compiler(params)) : compiler;
51
+ } catch (error) {
52
+ if (needsNormalization) throw error;
53
+ try {
54
+ const compiler = compile(normalizeAdjacentParameters(route));
55
+ return (params) => stripNormalizedSeparators(compiler(params));
56
+ } catch {
57
+ throw error;
58
+ }
59
+ }
60
+ }
61
+ const ADJACENT_PARAMS_RE = /:[a-zA-Z_][a-zA-Z0-9_]*:[a-zA-Z_][a-zA-Z0-9_]*/;
62
+ function normalizeAdjacentParameters(route) {
63
+ return route.replace(/(\([^)]*\)):([^/\s]+)/g, `$1${PARAM_SEPARATOR}:$2`).replace(/:([^:/\s)]+)(?=:)/g, `:$1${PARAM_SEPARATOR}`);
64
+ }
65
+ function stripNormalizedSeparators(pathname) {
66
+ return pathname.replace(new RegExp(`\\)${PARAM_SEPARATOR}`, "g"), ")");
67
+ }
68
+ function compile(route) {
69
+ const tokens = parsePathPattern(route);
70
+ return (params) => {
71
+ let path = "";
72
+ for (const token of tokens) {
73
+ if (typeof token === "string") {
74
+ path += token;
75
+ continue;
76
+ }
77
+ const value = params[token.name];
78
+ const optional = token.modifier === "?" || token.modifier === "*";
79
+ const repeat = token.modifier === "*" || token.modifier === "+";
80
+ if (Array.isArray(value)) {
81
+ if (!repeat) throw new TypeError(`Expected "${token.name}" to not repeat, but got an array`);
82
+ if (value.length === 0) {
83
+ if (optional) continue;
84
+ throw new TypeError(`Expected "${token.name}" to not be empty`);
85
+ }
86
+ for (const segment of value) path += token.prefix + segment + token.suffix;
87
+ continue;
88
+ }
89
+ if (typeof value === "string") {
90
+ path += token.prefix + value + token.suffix;
91
+ continue;
92
+ }
93
+ if (optional) continue;
94
+ throw new TypeError(`Expected "${token.name}" to be ${repeat ? "an array" : "a string"}`);
95
+ }
96
+ return path;
97
+ };
98
+ }
99
+ const HTTP_TOKEN_RE = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
100
+ const INVALID_HEADER_VALUE_CHAR_RE = /[^\t\x20-\x7e\x80-\xff]/;
101
+ /** Whether a substituted header pair can be sent as a response header. */
102
+ function isValidHeaderPair(key, value) {
103
+ return HTTP_TOKEN_RE.test(key) && !INVALID_HEADER_VALUE_CHAR_RE.test(value);
104
+ }
105
+ //#endregion
106
+ export { compileHeaderParamTemplate, isValidHeaderPair };
@@ -609,11 +609,13 @@ export declare function normalizeAssetPrefix(value: unknown): string;
609
609
  /**
610
610
  * Resolve the App Router RSC compatibility identity for a build.
611
611
  *
612
- * This token is baked into the client bundle and echoed by the server in the
613
- * `X-Vinext-RSC-Compatibility-Id` response header; browser navigation rejects
614
- * RSC payloads whose token differs (deploy skew) without exposing the raw
615
- * build ID. When the user pins a `deploymentId` we reuse it (already stable
616
- * across plugin instances); otherwise we mint a random UUID.
612
+ * The server sends this token with each page (the navigation bootstrap) and in
613
+ * the `X-Vinext-RSC-Compatibility-Id` response header; browser navigation
614
+ * rejects RSC payloads whose token differs from its page's (deploy skew)
615
+ * without exposing the raw build ID. It is never inlined into the client
616
+ * bundle, where a per-build value would rename chunks on every build. When
617
+ * the user pins a `deploymentId` we reuse it (already stable across plugin
618
+ * instances); otherwise we mint a random UUID.
617
619
  *
618
620
  * NOTE: like `resolveBuildId`, this is non-deterministic in the no-deploymentId
619
621
  * case, so a single `vite build` that instantiates the plugin more than once
@@ -5,6 +5,7 @@ import { isUnknownRecord } from "../utils/record.js";
5
5
  import { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_BUILD } from "../shims/constants.js";
6
6
  import { getHtmlLimitedBotRegex } from "../utils/html-limited-bots.js";
7
7
  import { flattenPluginOptions } from "../utils/plugin-options.js";
8
+ import { configSourceError } from "./config-source.js";
8
9
  import { isExternalUrl } from "../utils/external-url.js";
9
10
  import { applyLocaleToRoutes } from "./config-matchers.js";
10
11
  import { loadTsconfigResolutionForRoot } from "./tsconfig-paths.js";
@@ -534,11 +535,13 @@ function resolveDeploymentId(configDeploymentId) {
534
535
  /**
535
536
  * Resolve the App Router RSC compatibility identity for a build.
536
537
  *
537
- * This token is baked into the client bundle and echoed by the server in the
538
- * `X-Vinext-RSC-Compatibility-Id` response header; browser navigation rejects
539
- * RSC payloads whose token differs (deploy skew) without exposing the raw
540
- * build ID. When the user pins a `deploymentId` we reuse it (already stable
541
- * across plugin instances); otherwise we mint a random UUID.
538
+ * The server sends this token with each page (the navigation bootstrap) and in
539
+ * the `X-Vinext-RSC-Compatibility-Id` response header; browser navigation
540
+ * rejects RSC payloads whose token differs from its page's (deploy skew)
541
+ * without exposing the raw build ID. It is never inlined into the client
542
+ * bundle, where a per-build value would rename chunks on every build. When
543
+ * the user pins a `deploymentId` we reuse it (already stable across plugin
544
+ * instances); otherwise we mint a random UUID.
542
545
  *
543
546
  * NOTE: like `resolveBuildId`, this is non-deterministic in the no-deploymentId
544
547
  * case, so a single `vite build` that instantiates the plugin more than once
@@ -750,6 +753,93 @@ function normalizeI18nConfig(value) {
750
753
  };
751
754
  }
752
755
  /**
756
+ * Fail config loading on an invalid source, as Next.js fails the build
757
+ * (load-custom-routes.ts checkCustomRoutes), instead of silently skipping a
758
+ * redirect or security header per request.
759
+ */
760
+ function validateConfigSources(type, routes) {
761
+ const invalid = [];
762
+ for (const route of routes) {
763
+ const error = configSourceError(route?.source);
764
+ if (!error) continue;
765
+ invalid.push(` ${describeSource(route?.source)}: ${error}`);
766
+ }
767
+ if (invalid.length > 0) throw new Error(`Invalid next.config.js ${type} source${invalid.length === 1 ? "" : "s"}:\n${invalid.join("\n")}`);
768
+ }
769
+ /**
770
+ * A route source for an error message. Only a string is shown as is; any
771
+ * other value, such as a BigInt, may not serialize.
772
+ */
773
+ function describeSource(source) {
774
+ return typeof source === "string" ? JSON.stringify(source) : `(${source === null ? "null" : typeof source})`;
775
+ }
776
+ const CONDITION_TYPES = /* @__PURE__ */ new Set([
777
+ "header",
778
+ "cookie",
779
+ "query",
780
+ "host"
781
+ ]);
782
+ /**
783
+ * Fail config loading on a malformed has/missing list or item, with the rules
784
+ * of Next.js's checkCustomRoutes (load-custom-routes.ts). The matchers rely on
785
+ * these shapes.
786
+ */
787
+ function validateConfigConditions(type, routes) {
788
+ const invalid = [];
789
+ for (const route of routes) {
790
+ const source = describeSource(route?.source);
791
+ for (const field of ["has", "missing"]) {
792
+ const items = route?.[field];
793
+ if (items === void 0) continue;
794
+ if (!Array.isArray(items)) {
795
+ invalid.push(` ${source}: \`${field}\` must be undefined or an array`);
796
+ continue;
797
+ }
798
+ for (const item of items) {
799
+ const condition = item ?? {};
800
+ const parts = [];
801
+ if (!CONDITION_TYPES.has(condition.type)) parts.push("invalid type");
802
+ if (typeof condition.key !== "string" && condition.type !== "host") parts.push("invalid key");
803
+ if (condition.value !== void 0 && typeof condition.value !== "string") parts.push("invalid value");
804
+ if (condition.value === void 0 && condition.type === "host") parts.push("value is required for \"host\" type");
805
+ if (parts.length > 0) invalid.push(` ${source}: \`${field}\` item: ${parts.join(", ")} for ${describeConfigValue(item)}`);
806
+ }
807
+ }
808
+ }
809
+ if (invalid.length > 0) throw new Error(`Invalid next.config.js ${type} has/missing condition${invalid.length === 1 ? "" : "s"}:\n${invalid.join("\n")}\nEach item must be { type: header, cookie, query or host, key: string, value?: string }, with a value and no key required for host.`);
810
+ }
811
+ /** JSON for a config value, or its type when JSON cannot serialize it. */
812
+ function describeConfigValue(value) {
813
+ try {
814
+ return JSON.stringify(value) ?? `(${typeof value})`;
815
+ } catch {
816
+ return `(${typeof value})`;
817
+ }
818
+ }
819
+ /**
820
+ * The sources Next.js's processRoutes builds from `routes` by adding the
821
+ * basePath and locale prefixes, which checkCustomRoutes then checks again
822
+ * (load-custom-routes.ts). vinext adds basePath at match time instead, so
823
+ * these exist only for that check.
824
+ */
825
+ function processedRouteSources(routes, type, options) {
826
+ const { basePath, i18n, trailingSlash } = options;
827
+ const escape = (value) => value.replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&");
828
+ const suffixFor = (source) => source === "/" && !trailingSlash ? "" : source;
829
+ const defaultLocales = i18n && type === "redirect" ? [...(i18n.domains ?? []).map((domain) => domain.defaultLocale), i18n.defaultLocale] : [];
830
+ const sources = [];
831
+ for (const route of routes) {
832
+ const sourceBasePath = basePath && route.basePath !== false ? basePath : "";
833
+ let source = route.source;
834
+ if (i18n && route.locale !== false) {
835
+ if ("destination" in route && route.destination?.startsWith("/")) for (const locale of defaultLocales) sources.push({ source: `${sourceBasePath}/${locale}${suffixFor(source)}` });
836
+ source = `/:nextInternalLocale(${i18n.locales.map(escape).join("|")})${suffixFor(source)}`;
837
+ }
838
+ sources.push({ source: `${sourceBasePath}${source === "/" && sourceBasePath ? "" : source}` });
839
+ }
840
+ return sources;
841
+ }
842
+ /**
753
843
  * Resolve a NextConfig into a fully-resolved ResolvedNextConfig.
754
844
  * Awaits async functions for redirects/rewrites/headers.
755
845
  */
@@ -852,11 +942,18 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
852
942
  };
853
943
  }
854
944
  {
855
- const externalRewrites = [
945
+ const allRewrites = [
856
946
  ...rewrites.beforeFiles,
857
947
  ...rewrites.afterFiles,
858
948
  ...rewrites.fallback
859
- ].filter((rewrite) => isExternalUrl(rewrite.destination));
949
+ ];
950
+ validateConfigSources("redirect", redirects);
951
+ validateConfigSources("rewrite", allRewrites);
952
+ validateConfigConditions("redirect", redirects);
953
+ validateConfigConditions("rewrite", allRewrites);
954
+ const invalidBasePathRewrites = allRewrites.filter((rewrite) => rewrite.basePath === false && !(rewrite.destination.startsWith("http://") || rewrite.destination.startsWith("https://")));
955
+ if (invalidBasePathRewrites.length > 0) throw new Error(invalidBasePathRewrites.map((rewrite) => `The route ${rewrite.source} rewrites urls outside of the basePath. Please use a destination that starts with \`http://\` or \`https://\` https://nextjs.org/docs/messages/invalid-external-rewrite`).concat(`Invalid rewrite${invalidBasePathRewrites.length === 1 ? "" : "s"} found`).join("\n"));
956
+ const externalRewrites = allRewrites.filter((rewrite) => isExternalUrl(rewrite.destination));
860
957
  if (externalRewrites.length > 0) {
861
958
  const noun = externalRewrites.length === 1 ? "external rewrite" : "external rewrites";
862
959
  const listing = externalRewrites.map((rewrite) => ` ${rewrite.source} → ${rewrite.destination}`).join("\n");
@@ -865,6 +962,22 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
865
962
  }
866
963
  let headers = [];
867
964
  if (config.headers) headers = await config.headers();
965
+ validateConfigSources("header", headers);
966
+ validateConfigConditions("header", headers);
967
+ if (config.basePath || i18n) {
968
+ const processOptions = {
969
+ basePath: config.basePath ?? "",
970
+ i18n,
971
+ trailingSlash: config.trailingSlash ?? false
972
+ };
973
+ validateConfigSources("redirect", processedRouteSources(redirects, "redirect", processOptions));
974
+ validateConfigSources("rewrite", processedRouteSources([
975
+ ...rewrites.beforeFiles,
976
+ ...rewrites.afterFiles,
977
+ ...rewrites.fallback
978
+ ], "rewrite", processOptions));
979
+ validateConfigSources("header", processedRouteSources(headers, "header", processOptions));
980
+ }
868
981
  const webpackProbe = await probeWebpackConfig(config, root, options.dev ?? false);
869
982
  const mdx = webpackProbe.mdx;
870
983
  const aliases = {
@@ -9,13 +9,26 @@ import { VinextLinkPrefetchRoute, VinextPagesLinkPrefetchRoute } from "../client
9
9
  * embedded RSC payload and handles client-side navigation by re-fetching
10
10
  * RSC streams.
11
11
  */
12
- export declare function generateBrowserEntry(routes?: readonly AppRoute[], routeManifest?: RouteManifest | null, pagesPrefetchRoutes?: readonly VinextPagesLinkPrefetchRoute[], rewrites?: {
12
+ export declare function generateBrowserEntry(routes?: readonly AppRoute[], pagesPrefetchRoutes?: readonly VinextPagesLinkPrefetchRoute[], rewrites?: {
13
13
  afterFiles: NextRewrite[];
14
14
  beforeFiles: NextRewrite[];
15
15
  fallback: NextRewrite[];
16
16
  }, locales?: readonly string[]): string;
17
+ /**
18
+ * App route handlers for hybrid App/Pages ownership, with their static
19
+ * segments hashed so the client never receives their paths in plain text.
20
+ */
21
+ export declare function toHashedRouteHandlerRoutes(routes: readonly AppRoute[]): VinextLinkPrefetchRoute[];
17
22
  /** Project an `AppRoute` down to the public `VinextLinkPrefetchRoute` shape. */
18
23
  export declare function toLinkPrefetchRoute(route: AppRoute, hasSiblingInterceptLoading?: boolean): VinextLinkPrefetchRoute;
19
24
  /** Project App routes together so sibling-intercept loading is applied to its target route. */
20
25
  export declare function toLinkPrefetchRoutes(routes: readonly AppRoute[]): VinextLinkPrefetchRoute[];
26
+ /**
27
+ * Generate the virtual module that holds the browser RouteManifest.
28
+ *
29
+ * The App browser runtime imports it dynamically when hydration starts, so the
30
+ * manifest, which grows with every route, stays out of the entry chunk. The
31
+ * JSON string keeps the parse on V8's fast JSON path.
32
+ */
33
+ export declare function generateBrowserRouteManifestModule(routeManifest: RouteManifest | null): string;
21
34
  //#endregion
@@ -1,6 +1,8 @@
1
1
  import { patternsStructurallyEquivalent } from "../routing/app-route-graph.js";
2
2
  import { resolveClientRuntimeModule, resolveRuntimeEntryModule } from "./runtime-entry-module.js";
3
3
  import { toClientRewrites } from "../client/client-rewrites.js";
4
+ import { hashRoutePatternParts } from "../routing/hashed-route-patterns.js";
5
+ import { toBrowserRouteManifestWire } from "../routing/browser-route-manifest.js";
4
6
  //#region src/entries/app-browser-entry.ts
5
7
  /**
6
8
  * Generate the virtual browser entry module.
@@ -9,20 +11,20 @@ import { toClientRewrites } from "../client/client-rewrites.js";
9
11
  * embedded RSC payload and handles client-side navigation by re-fetching
10
12
  * RSC streams.
11
13
  */
12
- function generateBrowserEntry(routes = [], routeManifest = null, pagesPrefetchRoutes = [], rewrites = {
14
+ function generateBrowserEntry(routes = [], pagesPrefetchRoutes = [], rewrites = {
13
15
  afterFiles: [],
14
16
  beforeFiles: [],
15
17
  fallback: []
16
18
  }, locales = []) {
17
19
  const entryPath = resolveRuntimeEntryModule("app-browser-entry");
18
20
  const reactInstanceBootstrapPath = resolveClientRuntimeModule("react-instance-bootstrap");
19
- const navigationRuntimePath = resolveClientRuntimeModule("navigation-runtime");
20
21
  const prefetchRoutes = toLinkPrefetchRoutes(routes);
22
+ const routeHandlers = pagesPrefetchRoutes.length > 0 ? toHashedRouteHandlerRoutes(routes) : [];
21
23
  const clientRewrites = toClientRewrites(rewrites);
22
24
  return `import ${JSON.stringify(reactInstanceBootstrapPath)};
23
- import { registerNavigationRuntimeBootstrap } from ${JSON.stringify(navigationRuntimePath)};
24
25
 
25
26
  window.__VINEXT_LINK_PREFETCH_ROUTES__ = ${JSON.stringify(prefetchRoutes)};
27
+ window.__VINEXT_APP_ROUTE_HANDLERS__ = ${JSON.stringify(routeHandlers)};
26
28
  // Pages route manifest for hybrid ownership decisions. In a hybrid
27
29
  // app+pages build the user can land on an App page, so the App browser
28
30
  // entry must also expose the Pages manifest (the Pages client entry does
@@ -30,9 +32,6 @@ window.__VINEXT_LINK_PREFETCH_ROUTES__ = ${JSON.stringify(prefetchRoutes)};
30
32
  window.__VINEXT_PAGES_LINK_PREFETCH_ROUTES__ = ${JSON.stringify(pagesPrefetchRoutes)};
31
33
  window.__VINEXT_CLIENT_REWRITES__ = ${JSON.stringify(clientRewrites)};
32
34
  window.__VINEXT_LOCALES__ = ${JSON.stringify(locales)};
33
- registerNavigationRuntimeBootstrap({
34
- routeManifest: ${buildRouteManifestExpression(routeManifest)}
35
- });
36
35
  import ${JSON.stringify(entryPath)};`;
37
36
  }
38
37
  /**
@@ -45,13 +44,17 @@ function isLinkPrefetchRoute(route) {
45
44
  if (route.pagePath !== null) return true;
46
45
  return route.routePath === null && route.layouts.length > 0;
47
46
  }
48
- function toDocumentOnlyAppRoute(route) {
49
- return {
47
+ /**
48
+ * App route handlers for hybrid App/Pages ownership, with their static
49
+ * segments hashed so the client never receives their paths in plain text.
50
+ */
51
+ function toHashedRouteHandlerRoutes(routes) {
52
+ return routes.flatMap((route) => isLinkPrefetchRoute(route) ? [] : [{
50
53
  canPrefetchLoadingShell: false,
51
54
  documentOnly: true,
52
- patternParts: [...route.patternParts],
53
- isDynamic: route.isDynamic
54
- };
55
+ isDynamic: route.isDynamic,
56
+ patternParts: hashRoutePatternParts(route.patternParts)
57
+ }]);
55
58
  }
56
59
  function requiresDynamicNavigationRequest(route) {
57
60
  return route.isDynamic && route.parallelSlots.length > 0;
@@ -79,31 +82,18 @@ function toLinkPrefetchRoute(route, hasSiblingInterceptLoading = route.siblingIn
79
82
  function toLinkPrefetchRoutes(routes) {
80
83
  const siblingInterceptLoadingTargets = [];
81
84
  for (const route of routes) for (const intercept of route.siblingIntercepts) if ((intercept.loadingPaths?.length ?? 0) > 0) siblingInterceptLoadingTargets.push(splitPatternParts(intercept.targetPattern));
82
- return routes.map((route) => isLinkPrefetchRoute(route) ? toLinkPrefetchRoute(route, siblingInterceptLoadingTargets.some((targetParts) => patternsStructurallyEquivalent(targetParts, route.patternParts))) : toDocumentOnlyAppRoute(route));
83
- }
84
- function buildRouteManifestExpression(routeManifest) {
85
- if (routeManifest === null) return "null";
86
- const graph = routeManifest.segmentGraph;
87
- return `{
88
- graphVersion: ${JSON.stringify(routeManifest.graphVersion)},
89
- segmentGraph: {
90
- routes: ${buildMapExpression(graph.routes)},
91
- pages: ${buildMapExpression(graph.pages)},
92
- routeHandlers: ${buildMapExpression(graph.routeHandlers)},
93
- layouts: ${buildMapExpression(graph.layouts)},
94
- templates: ${buildMapExpression(graph.templates)},
95
- slots: ${buildMapExpression(graph.slots)},
96
- defaults: ${buildMapExpression(graph.defaults)},
97
- slotBindings: ${buildMapExpression(graph.slotBindings)},
98
- interceptions: ${buildMapExpression(graph.interceptions)},
99
- interceptionsBySlotId: ${buildMapExpression(graph.interceptionsBySlotId)},
100
- boundaries: ${buildMapExpression(graph.boundaries)},
101
- rootBoundaries: ${buildMapExpression(graph.rootBoundaries)}
102
- }
103
- }`;
85
+ return routes.filter(isLinkPrefetchRoute).map((route) => toLinkPrefetchRoute(route, siblingInterceptLoadingTargets.some((targetParts) => patternsStructurallyEquivalent(targetParts, route.patternParts))));
104
86
  }
105
- function buildMapExpression(map) {
106
- return `new Map(${JSON.stringify(Array.from(map.entries()))})`;
87
+ /**
88
+ * Generate the virtual module that holds the browser RouteManifest.
89
+ *
90
+ * The App browser runtime imports it dynamically when hydration starts, so the
91
+ * manifest, which grows with every route, stays out of the entry chunk. The
92
+ * JSON string keeps the parse on V8's fast JSON path.
93
+ */
94
+ function generateBrowserRouteManifestModule(routeManifest) {
95
+ if (routeManifest === null) return "export default null;";
96
+ return `export default JSON.parse(${JSON.stringify(JSON.stringify(toBrowserRouteManifestWire(routeManifest)))});`;
107
97
  }
108
98
  //#endregion
109
- export { generateBrowserEntry, toLinkPrefetchRoute, toLinkPrefetchRoutes };
99
+ export { generateBrowserEntry, generateBrowserRouteManifestModule, toHashedRouteHandlerRoutes, toLinkPrefetchRoute, toLinkPrefetchRoutes };
@@ -93,6 +93,8 @@ type AppRouterConfig = {
93
93
  draftModeSecret?: string;
94
94
  /** Server-only token used to authorize build-time prerender endpoints. */
95
95
  prerenderSecret?: string;
96
+ /** next.config `skipProxyUrlNormalize`. */
97
+ skipProxyUrlNormalize?: boolean;
96
98
  /** next.config `skipTrailingSlashRedirect`. */
97
99
  skipTrailingSlashRedirect?: boolean;
98
100
  /** Dev-only key authenticating the hybrid Pages handler's forwarded middleware context. */