vinext 0.1.2 → 0.1.4

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 (243) hide show
  1. package/dist/build/client-build-config.d.ts +11 -2
  2. package/dist/build/client-build-config.js +17 -6
  3. package/dist/build/prerender.d.ts +9 -1
  4. package/dist/build/prerender.js +42 -12
  5. package/dist/build/run-prerender.d.ts +10 -2
  6. package/dist/build/run-prerender.js +15 -1
  7. package/dist/client/app-nav-failure-handler.d.ts +8 -0
  8. package/dist/client/app-nav-failure-handler.js +44 -0
  9. package/dist/client/pages-router-link-navigation.d.ts +33 -7
  10. package/dist/client/pages-router-link-navigation.js +32 -2
  11. package/dist/client/vinext-next-data.d.ts +18 -1
  12. package/dist/client/vinext-next-data.js +2 -0
  13. package/dist/client/window-next.d.ts +2 -1
  14. package/dist/client/window-next.js +12 -1
  15. package/dist/cloudflare/src/cache/cdn-adapter.runtime.js +6 -1
  16. package/dist/config/config-matchers.d.ts +11 -1
  17. package/dist/config/config-matchers.js +87 -16
  18. package/dist/config/next-config.d.ts +46 -4
  19. package/dist/config/next-config.js +147 -48
  20. package/dist/config/tsconfig-paths.js +14 -1
  21. package/dist/deploy.d.ts +30 -11
  22. package/dist/deploy.js +200 -112
  23. package/dist/entries/app-browser-entry.d.ts +9 -3
  24. package/dist/entries/app-browser-entry.js +21 -3
  25. package/dist/entries/app-rsc-entry.d.ts +2 -0
  26. package/dist/entries/app-rsc-entry.js +65 -5
  27. package/dist/entries/app-rsc-manifest.js +2 -0
  28. package/dist/entries/app-ssr-entry.js +1 -1
  29. package/dist/entries/pages-client-entry.js +66 -20
  30. package/dist/entries/pages-server-entry.js +47 -31
  31. package/dist/index.js +417 -102
  32. package/dist/plugins/dynamic-preload-metadata.js +2 -4
  33. package/dist/plugins/extensionless-dynamic-import.d.ts +6 -0
  34. package/dist/plugins/extensionless-dynamic-import.js +152 -0
  35. package/dist/plugins/fonts.js +5 -4
  36. package/dist/plugins/optimize-imports.d.ts +2 -1
  37. package/dist/plugins/optimize-imports.js +11 -9
  38. package/dist/plugins/postcss.js +7 -7
  39. package/dist/plugins/strip-server-exports.d.ts +9 -7
  40. package/dist/plugins/strip-server-exports.js +493 -46
  41. package/dist/plugins/typeof-window.d.ts +14 -0
  42. package/dist/plugins/typeof-window.js +150 -0
  43. package/dist/routing/app-route-graph.d.ts +2 -1
  44. package/dist/routing/app-route-graph.js +46 -16
  45. package/dist/routing/file-matcher.d.ts +10 -1
  46. package/dist/routing/file-matcher.js +22 -1
  47. package/dist/routing/pages-router.js +3 -3
  48. package/dist/routing/utils.d.ts +35 -6
  49. package/dist/routing/utils.js +59 -7
  50. package/dist/server/api-handler.d.ts +6 -1
  51. package/dist/server/api-handler.js +21 -15
  52. package/dist/server/app-browser-action-result.d.ts +19 -6
  53. package/dist/server/app-browser-action-result.js +20 -11
  54. package/dist/server/app-browser-entry.js +175 -91
  55. package/dist/server/app-browser-error.d.ts +10 -6
  56. package/dist/server/app-browser-error.js +43 -8
  57. package/dist/server/app-browser-hydration.d.ts +2 -0
  58. package/dist/server/app-browser-hydration.js +1 -0
  59. package/dist/server/app-browser-navigation-controller.d.ts +5 -3
  60. package/dist/server/app-browser-navigation-controller.js +23 -2
  61. package/dist/server/app-browser-server-action-navigation.d.ts +6 -0
  62. package/dist/server/app-browser-server-action-navigation.js +9 -0
  63. package/dist/server/app-browser-state.d.ts +1 -1
  64. package/dist/server/app-browser-state.js +19 -11
  65. package/dist/server/app-browser-stream.js +86 -43
  66. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  67. package/dist/server/app-elements-wire.d.ts +6 -1
  68. package/dist/server/app-elements-wire.js +14 -4
  69. package/dist/server/app-elements.d.ts +2 -2
  70. package/dist/server/app-elements.js +2 -2
  71. package/dist/server/app-fallback-renderer.d.ts +1 -0
  72. package/dist/server/app-fallback-renderer.js +3 -1
  73. package/dist/server/app-optimistic-routing.js +2 -2
  74. package/dist/server/app-page-boundary-render.d.ts +1 -0
  75. package/dist/server/app-page-boundary-render.js +27 -14
  76. package/dist/server/app-page-cache-render.d.ts +53 -0
  77. package/dist/server/app-page-cache-render.js +91 -0
  78. package/dist/server/app-page-cache.d.ts +16 -2
  79. package/dist/server/app-page-cache.js +62 -1
  80. package/dist/server/app-page-dispatch.d.ts +26 -0
  81. package/dist/server/app-page-dispatch.js +149 -92
  82. package/dist/server/app-page-element-builder.d.ts +1 -0
  83. package/dist/server/app-page-element-builder.js +5 -2
  84. package/dist/server/app-page-execution.d.ts +6 -1
  85. package/dist/server/app-page-execution.js +21 -1
  86. package/dist/server/app-page-probe.d.ts +1 -0
  87. package/dist/server/app-page-probe.js +4 -0
  88. package/dist/server/app-page-render-observation.d.ts +3 -1
  89. package/dist/server/app-page-render-observation.js +17 -1
  90. package/dist/server/app-page-render.d.ts +12 -1
  91. package/dist/server/app-page-render.js +42 -4
  92. package/dist/server/app-page-request.d.ts +2 -0
  93. package/dist/server/app-page-request.js +2 -1
  94. package/dist/server/app-page-route-wiring.d.ts +3 -1
  95. package/dist/server/app-page-route-wiring.js +14 -5
  96. package/dist/server/app-page-stream.d.ts +15 -3
  97. package/dist/server/app-page-stream.js +11 -5
  98. package/dist/server/app-pages-bridge.d.ts +23 -1
  99. package/dist/server/app-pages-bridge.js +26 -17
  100. package/dist/server/app-ppr-fallback-shell-render.d.ts +17 -0
  101. package/dist/server/app-ppr-fallback-shell-render.js +26 -0
  102. package/dist/server/app-ppr-fallback-shell.d.ts +13 -1
  103. package/dist/server/app-ppr-fallback-shell.js +8 -1
  104. package/dist/server/app-route-handler-dispatch.js +9 -2
  105. package/dist/server/app-route-handler-policy.d.ts +1 -0
  106. package/dist/server/app-router-entry.js +5 -0
  107. package/dist/server/app-rsc-cache-busting.js +2 -0
  108. package/dist/server/app-rsc-handler.d.ts +28 -0
  109. package/dist/server/app-rsc-handler.js +195 -59
  110. package/dist/server/app-rsc-route-matching.d.ts +3 -0
  111. package/dist/server/app-rsc-route-matching.js +8 -2
  112. package/dist/server/app-segment-config.d.ts +9 -1
  113. package/dist/server/app-segment-config.js +12 -3
  114. package/dist/server/app-server-action-execution.d.ts +1 -0
  115. package/dist/server/app-server-action-execution.js +47 -15
  116. package/dist/server/app-ssr-entry.d.ts +2 -0
  117. package/dist/server/app-ssr-entry.js +84 -39
  118. package/dist/server/before-interactive-head.d.ts +17 -0
  119. package/dist/server/before-interactive-head.js +35 -0
  120. package/dist/server/cache-control.js +4 -0
  121. package/dist/server/csp.js +1 -4
  122. package/dist/server/dev-server.d.ts +2 -2
  123. package/dist/server/dev-server.js +321 -83
  124. package/dist/server/hybrid-route-priority.d.ts +22 -0
  125. package/dist/server/hybrid-route-priority.js +33 -0
  126. package/dist/server/image-optimization.d.ts +18 -9
  127. package/dist/server/image-optimization.js +37 -23
  128. package/dist/server/implicit-tags.d.ts +2 -1
  129. package/dist/server/implicit-tags.js +4 -1
  130. package/dist/server/middleware-matcher.js +12 -3
  131. package/dist/server/middleware-runtime.d.ts +3 -4
  132. package/dist/server/middleware-runtime.js +2 -0
  133. package/dist/server/navigation-planner.d.ts +135 -41
  134. package/dist/server/navigation-planner.js +138 -0
  135. package/dist/server/navigation-trace.d.ts +9 -1
  136. package/dist/server/navigation-trace.js +9 -1
  137. package/dist/server/operation-token.d.ts +40 -0
  138. package/dist/server/operation-token.js +85 -0
  139. package/dist/server/pages-api-route.d.ts +6 -0
  140. package/dist/server/pages-api-route.js +13 -2
  141. package/dist/server/pages-asset-tags.d.ts +2 -1
  142. package/dist/server/pages-asset-tags.js +6 -2
  143. package/dist/server/pages-data-route.d.ts +9 -2
  144. package/dist/server/pages-data-route.js +18 -6
  145. package/dist/server/pages-dev-module-url.d.ts +4 -0
  146. package/dist/server/pages-dev-module-url.js +15 -0
  147. package/dist/server/pages-document-initial-props.d.ts +4 -15
  148. package/dist/server/pages-document-initial-props.js +27 -56
  149. package/dist/server/pages-get-initial-props.d.ts +54 -4
  150. package/dist/server/pages-get-initial-props.js +43 -1
  151. package/dist/server/pages-i18n.js +2 -2
  152. package/dist/server/pages-node-compat.js +2 -2
  153. package/dist/server/pages-page-data.d.ts +11 -2
  154. package/dist/server/pages-page-data.js +207 -34
  155. package/dist/server/pages-page-handler.d.ts +4 -2
  156. package/dist/server/pages-page-handler.js +62 -23
  157. package/dist/server/pages-page-response.d.ts +4 -1
  158. package/dist/server/pages-page-response.js +11 -8
  159. package/dist/server/pages-readiness.js +1 -1
  160. package/dist/server/pages-request-pipeline.d.ts +8 -7
  161. package/dist/server/pages-request-pipeline.js +126 -47
  162. package/dist/server/pregenerated-concrete-paths.d.ts +1 -17
  163. package/dist/server/pregenerated-concrete-paths.js +2 -19
  164. package/dist/server/prerender-manifest.d.ts +33 -0
  165. package/dist/server/prerender-manifest.js +54 -0
  166. package/dist/server/prerender-route-params.d.ts +1 -2
  167. package/dist/server/prod-server.d.ts +3 -1
  168. package/dist/server/prod-server.js +50 -13
  169. package/dist/server/request-pipeline.d.ts +3 -15
  170. package/dist/server/request-pipeline.js +58 -47
  171. package/dist/server/rsc-stream-hints.d.ts +5 -1
  172. package/dist/server/rsc-stream-hints.js +6 -1
  173. package/dist/server/seed-cache.js +10 -18
  174. package/dist/server/static-file-cache.js +16 -4
  175. package/dist/shims/app-router-scroll-state.d.ts +3 -1
  176. package/dist/shims/app-router-scroll-state.js +14 -2
  177. package/dist/shims/app-router-scroll.d.ts +3 -0
  178. package/dist/shims/app-router-scroll.js +28 -18
  179. package/dist/shims/before-interactive-context.d.ts +14 -3
  180. package/dist/shims/cache-runtime.js +3 -2
  181. package/dist/shims/cache.d.ts +1 -0
  182. package/dist/shims/cache.js +1 -1
  183. package/dist/shims/cdn-cache.d.ts +5 -5
  184. package/dist/shims/document.d.ts +15 -20
  185. package/dist/shims/document.js +5 -8
  186. package/dist/shims/dynamic-preload-chunks.js +6 -4
  187. package/dist/shims/error-boundary.d.ts +2 -0
  188. package/dist/shims/error-boundary.js +7 -0
  189. package/dist/shims/error.js +3 -2
  190. package/dist/shims/error.react-server.d.ts +9 -0
  191. package/dist/shims/error.react-server.js +6 -0
  192. package/dist/shims/fetch-cache.d.ts +3 -1
  193. package/dist/shims/fetch-cache.js +45 -20
  194. package/dist/shims/hash-scroll.js +6 -1
  195. package/dist/shims/headers.js +29 -4
  196. package/dist/shims/image.js +9 -2
  197. package/dist/shims/internal/als-registry.js +28 -1
  198. package/dist/shims/internal/app-route-detection.js +8 -17
  199. package/dist/shims/internal/hybrid-client-route-owner.d.ts +31 -0
  200. package/dist/shims/internal/hybrid-client-route-owner.js +143 -0
  201. package/dist/shims/internal/navigation-untracked.d.ts +35 -0
  202. package/dist/shims/internal/navigation-untracked.js +55 -0
  203. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +6 -7
  204. package/dist/shims/internal/pages-data-fetch-dedup.js +67 -14
  205. package/dist/shims/internal/pages-data-target.d.ts +7 -2
  206. package/dist/shims/internal/pages-data-target.js +17 -8
  207. package/dist/shims/internal/pages-router-accessor.d.ts +19 -0
  208. package/dist/shims/internal/pages-router-accessor.js +13 -0
  209. package/dist/shims/internal/router-context.d.ts +2 -1
  210. package/dist/shims/internal/router-context.js +3 -1
  211. package/dist/shims/link.js +47 -19
  212. package/dist/shims/metadata.js +4 -4
  213. package/dist/shims/navigation.d.ts +8 -2
  214. package/dist/shims/navigation.js +63 -31
  215. package/dist/shims/ppr-fallback-shell.d.ts +5 -1
  216. package/dist/shims/ppr-fallback-shell.js +28 -7
  217. package/dist/shims/router.d.ts +18 -3
  218. package/dist/shims/router.js +512 -142
  219. package/dist/shims/script.js +8 -4
  220. package/dist/shims/server.d.ts +16 -1
  221. package/dist/shims/server.js +44 -12
  222. package/dist/shims/unified-request-context.js +1 -0
  223. package/dist/utils/built-asset-url.d.ts +4 -0
  224. package/dist/utils/built-asset-url.js +11 -0
  225. package/dist/utils/commonjs-loader.d.ts +16 -0
  226. package/dist/utils/commonjs-loader.js +100 -0
  227. package/dist/utils/deployment-id.d.ts +8 -0
  228. package/dist/utils/deployment-id.js +22 -0
  229. package/dist/utils/has-trailing-comma.d.ts +24 -0
  230. package/dist/utils/has-trailing-comma.js +62 -0
  231. package/dist/utils/html-limited-bots.d.ts +18 -1
  232. package/dist/utils/html-limited-bots.js +23 -1
  233. package/dist/utils/parse-cookie.d.ts +13 -0
  234. package/dist/utils/parse-cookie.js +52 -0
  235. package/dist/utils/path.d.ts +7 -1
  236. package/dist/utils/path.js +9 -1
  237. package/dist/utils/text-stream.d.ts +1 -1
  238. package/dist/utils/text-stream.js +2 -2
  239. package/dist/utils/vite-version.d.ts +12 -1
  240. package/dist/utils/vite-version.js +9 -1
  241. package/package.json +2 -2
  242. package/dist/shims/internal/parse-cookie-header.d.ts +0 -14
  243. package/dist/shims/internal/parse-cookie-header.js +0 -30
package/dist/deploy.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ensureESModule as ensureESModule$1, renameCJSConfigs as renameCJSConfigs$1 } from "./utils/project.js";
2
+ import { execFileSync } from "node:child_process";
2
3
 
3
4
  //#region src/deploy.d.ts
4
5
  type DeployOptions = {
@@ -151,21 +152,39 @@ type WranglerDeployArgs = {
151
152
  args: string[];
152
153
  env: string | undefined;
153
154
  };
155
+ declare function validateWranglerEnvName(env: string): string;
154
156
  declare function buildWranglerDeployArgs(options: Pick<DeployOptions, "preview" | "env">): WranglerDeployArgs;
155
157
  /**
156
- * Resolve the wrangler executable in node_modules.
158
+ * Resolve Wrangler's JavaScript CLI entrypoint in node_modules.
157
159
  *
158
- * Walks up ancestor directories so the binary is found even when node_modules
159
- * is hoisted to the workspace root in a monorepo.
160
+ * Invoking the JavaScript file through `process.execPath` avoids the `.cmd`
161
+ * shim and command shell that package managers create on Windows.
162
+ */
163
+ declare function resolveWranglerBin(root: string, resolvePackageJson?: (root: string) => string | null): string;
164
+ declare function buildNodeCliInvocation(scriptPath: string, args: string[], nodeExecutable?: string): {
165
+ file: string;
166
+ args: string[];
167
+ };
168
+ declare function buildWranglerInvocation(root: string, options: Pick<DeployOptions, "preview" | "env">, nodeExecutable?: string): {
169
+ file: string;
170
+ args: string[];
171
+ env: string | undefined;
172
+ };
173
+ declare function runWranglerDeploy(root: string, options: Pick<DeployOptions, "preview" | "env">, execute?: typeof execFileSync): string;
174
+ /**
175
+ * Read the prerender manifest and inject pregenerated concrete paths into the
176
+ * App Router Worker bundle so the PPR fallback-shell guard is populated at
177
+ * module init time without calling `seedMemoryCacheFromPrerender`.
178
+ *
179
+ * The paths are injected as `globalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS`
180
+ * wrapped in replaceable marker comments, and consumed by
181
+ * `initPregeneratedPathsFromGlobals` in the generated RSC entry.
160
182
  *
161
- * On Windows, `node_modules/.bin/` contains both a Unix shebang script (no
162
- * extension) and a `.CMD` shim. Node's `execFileSync` uses CreateProcess(),
163
- * which only resolves PATHEXT extensions (`.cmd`, `.exe`, ...) spawning the
164
- * bare-name shebang file fails with ENOENT even though the file exists. So on
165
- * Windows we prefer the `.CMD` shim and only fall back to the bare name for a
166
- * clearer error message if neither is present.
183
+ * Idempotent: repeated calls strip the previous injection before writing the
184
+ * new one. If the manifest is missing, corrupt, or empty, any prior injection
185
+ * is stripped and nothing new is writtenfailing closed to empty.
167
186
  */
168
- declare function resolveWranglerBin(root: string, platform?: NodeJS.Platform): string;
187
+ declare function injectPregeneratedConcretePaths(root: string): void;
169
188
  declare function deploy(options: DeployOptions): Promise<void>;
170
189
  //#endregion
171
- export { buildWranglerDeployArgs, deploy, detectProject, ensureESModule, formatMissingCacheAdapterError, formatMissingCloudflarePluginError, generateAppRouterViteConfig, generateAppRouterWorkerEntry, generatePagesRouterViteConfig, generatePagesRouterWorkerEntry, generateWranglerConfig, getFilesToGenerate, getMissingDeps, hasWranglerConfig, isPackageResolvable, parseDeployArgs, renameCJSConfigs, resolveWranglerBin, viteConfigHasCacheAdapter, viteConfigHasCloudflarePlugin, withCloudflareEnv, workerEntryHasCacheHandler };
190
+ export { buildNodeCliInvocation, buildWranglerDeployArgs, buildWranglerInvocation, deploy, detectProject, ensureESModule, formatMissingCacheAdapterError, formatMissingCloudflarePluginError, generateAppRouterViteConfig, generateAppRouterWorkerEntry, generatePagesRouterViteConfig, generatePagesRouterWorkerEntry, generateWranglerConfig, getFilesToGenerate, getMissingDeps, hasWranglerConfig, injectPregeneratedConcretePaths, isPackageResolvable, parseDeployArgs, renameCJSConfigs, resolveWranglerBin, runWranglerDeploy, validateWranglerEnvName, viteConfigHasCacheAdapter, viteConfigHasCloudflarePlugin, withCloudflareEnv, workerEntryHasCacheHandler };
package/dist/deploy.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { detectPackageManager as detectPackageManager$1, ensureESModule as ensureESModule$1, findInNodeModules, renameCJSConfigs as renameCJSConfigs$1 } from "./utils/project.js";
2
2
  import { parsePositiveIntegerArg } from "./cli-args.js";
3
+ import { escapeRegExp } from "./utils/regex.js";
3
4
  import { loadNextConfig, resolveNextConfig } from "./config/next-config.js";
4
5
  import { getReactUpgradeDeps } from "./init.js";
5
6
  import { runTPR } from "./cloudflare/tpr.js";
7
+ import { buildPregeneratedConcretePathTable, readPrerenderManifest } from "./server/prerender-manifest.js";
6
8
  import { runPrerender } from "./build/run-prerender.js";
7
9
  import { loadDotenv } from "./config/dotenv.js";
8
10
  import { createRequire } from "node:module";
@@ -123,14 +125,33 @@ function detectProject(root) {
123
125
  } catch {}
124
126
  let projectName = path.basename(root);
125
127
  if (pkg?.name && typeof pkg.name === "string") projectName = pkg.name.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
126
- const hasISR = detectISR(root, isAppRouter);
127
128
  const hasTypeModule = pkg?.type === "module";
128
- const hasMDX = detectMDX(root, isAppRouter, hasPages);
129
- const hasCodeHike = "codehike" in {
129
+ let hasISR = false;
130
+ let hasMDX = detectMDXFromConfig(root);
131
+ if (isAppRouter) {
132
+ const appDir = resolveProjectDir(root, "app");
133
+ if (appDir) {
134
+ const found = scanTreeForDetection(appDir, {
135
+ isr: true,
136
+ mdx: !hasMDX
137
+ });
138
+ hasISR = found.isr;
139
+ hasMDX = hasMDX || found.mdx;
140
+ }
141
+ }
142
+ if (hasPages && !hasMDX) {
143
+ const pagesDir = resolveProjectDir(root, "pages");
144
+ if (pagesDir) hasMDX = scanTreeForDetection(pagesDir, {
145
+ isr: false,
146
+ mdx: true
147
+ }).mdx;
148
+ }
149
+ const allDeps = {
130
150
  ...pkg?.dependencies,
131
151
  ...pkg?.devDependencies
132
152
  };
133
- const nativeModulesToStub = detectNativeModules(root);
153
+ const hasCodeHike = "codehike" in allDeps;
154
+ const nativeModulesToStub = detectNativeModules(allDeps);
134
155
  return {
135
156
  root,
136
157
  isAppRouter,
@@ -149,35 +170,69 @@ function detectProject(root) {
149
170
  nativeModulesToStub
150
171
  };
151
172
  }
152
- function detectISR(root, isAppRouter) {
153
- if (!isAppRouter) return false;
154
- try {
155
- let appDir = path.join(root, "app");
156
- if (!fs.existsSync(appDir)) appDir = path.join(root, "src", "app");
157
- if (!fs.existsSync(appDir)) return false;
158
- return scanDirForPattern(appDir, /export\s+const\s+revalidate\s*=/);
159
- } catch {
160
- return false;
161
- }
173
+ /** Matches `export const revalidate = …` (ISR opt-in) in App Router source. */
174
+ const ISR_REVALIDATE_PATTERN = /export\s+const\s+revalidate\s*=/;
175
+ /** Source extensions whose contents are scanned for the ISR pattern. */
176
+ const ISR_SCANNABLE_EXTENSION = /\.(ts|tsx|js|jsx)$/;
177
+ /**
178
+ * Resolve a project subdirectory (`app`/`pages`), preferring the root-level
179
+ * location and falling back to the `src/` variant. Returns null when neither
180
+ * exists.
181
+ */
182
+ function resolveProjectDir(root, name) {
183
+ const rootDir = path.join(root, name);
184
+ if (fs.existsSync(rootDir)) return rootDir;
185
+ const srcDir = path.join(root, "src", name);
186
+ if (fs.existsSync(srcDir)) return srcDir;
187
+ return null;
162
188
  }
163
- function scanDirForPattern(dir, pattern) {
164
- const entries = fs.readdirSync(dir, { withFileTypes: true });
165
- for (const entry of entries) {
166
- const fullPath = path.join(dir, entry.name);
167
- if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
168
- if (scanDirForPattern(fullPath, pattern)) return true;
169
- } else if (entry.isFile() && /\.(ts|tsx|js|jsx)$/.test(entry.name)) try {
170
- const content = fs.readFileSync(fullPath, "utf-8");
171
- if (pattern.test(content)) return true;
172
- } catch {}
173
- }
174
- return false;
189
+ /**
190
+ * Recursively walk `dir` once, evaluating the requested detection predicates
191
+ * per entry. Each flag short-circuits independently: an `.mdx` file sets `mdx`;
192
+ * a scannable source file containing `export const revalidate` sets `isr`. The
193
+ * walk stops as soon as every requested flag is satisfied, so callers that only
194
+ * want one signal don't pay for the other.
195
+ *
196
+ * Replaces the previous pair of single-purpose recursive walkers
197
+ * (`scanDirForPattern` + `scanDirForExtension`) that traversed the same tree
198
+ * twice. Detection semantics are unchanged: same dirs skipped (dotfiles,
199
+ * node_modules), same extension and content tests.
200
+ */
201
+ function scanTreeForDetection(dir, want) {
202
+ const found = {
203
+ isr: false,
204
+ mdx: false
205
+ };
206
+ const walk = (current) => {
207
+ let entries;
208
+ try {
209
+ entries = fs.readdirSync(current, { withFileTypes: true });
210
+ } catch {
211
+ return;
212
+ }
213
+ for (const entry of entries) {
214
+ if ((!want.isr || found.isr) && (!want.mdx || found.mdx)) return;
215
+ const fullPath = path.join(current, entry.name);
216
+ if (entry.isDirectory()) {
217
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
218
+ walk(fullPath);
219
+ } else if (entry.isFile()) {
220
+ if (want.mdx && !found.mdx && entry.name.endsWith(".mdx")) found.mdx = true;
221
+ if (want.isr && !found.isr && ISR_SCANNABLE_EXTENSION.test(entry.name)) try {
222
+ if (ISR_REVALIDATE_PATTERN.test(fs.readFileSync(fullPath, "utf-8"))) found.isr = true;
223
+ } catch {}
224
+ }
225
+ }
226
+ };
227
+ walk(dir);
228
+ return found;
175
229
  }
176
230
  /**
177
- * Detect .mdx files in the project's app/ or pages/ directory,
178
- * or `pageExtensions` including "mdx" in next.config.
231
+ * Detect MDX usage declared in next.config (`pageExtensions` including "mdx" or
232
+ * an `@next/mdx` import). Filesystem `.mdx` detection is handled separately by
233
+ * the shared app/pages tree walk in `detectProject`.
179
234
  */
180
- function detectMDX(root, isAppRouter, hasPages) {
235
+ function detectMDXFromConfig(root) {
181
236
  for (const f of [
182
237
  "next.config.ts",
183
238
  "next.config.mts",
@@ -191,26 +246,6 @@ function detectMDX(root, isAppRouter, hasPages) {
191
246
  if (/pageExtensions.*mdx/i.test(content) || /@next\/mdx/.test(content)) return true;
192
247
  } catch {}
193
248
  }
194
- const dirs = [];
195
- if (isAppRouter) {
196
- const appDir = fs.existsSync(path.join(root, "app")) ? path.join(root, "app") : path.join(root, "src", "app");
197
- dirs.push(appDir);
198
- }
199
- if (hasPages) {
200
- const pagesDir = fs.existsSync(path.join(root, "pages")) ? path.join(root, "pages") : path.join(root, "src", "pages");
201
- dirs.push(pagesDir);
202
- }
203
- for (const dir of dirs) if (fs.existsSync(dir) && scanDirForExtension(dir, ".mdx")) return true;
204
- return false;
205
- }
206
- function scanDirForExtension(dir, ext) {
207
- const entries = fs.readdirSync(dir, { withFileTypes: true });
208
- for (const entry of entries) {
209
- const fullPath = path.join(dir, entry.name);
210
- if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
211
- if (scanDirForExtension(fullPath, ext)) return true;
212
- } else if (entry.isFile() && entry.name.endsWith(ext)) return true;
213
- }
214
249
  return false;
215
250
  }
216
251
  /** Known native Node modules that can't run in Workers */
@@ -222,21 +257,12 @@ const NATIVE_MODULES_TO_STUB = [
222
257
  "sharp"
223
258
  ];
224
259
  /**
225
- * Detect native Node modules in dependencies that need stubbing for Workers.
260
+ * Detect native Node modules in the project's merged dependency map that need
261
+ * stubbing for Workers. Accepts the already-built `allDeps` (dependencies +
262
+ * devDependencies) so package.json is not re-read or re-parsed.
226
263
  */
227
- function detectNativeModules(root) {
228
- const pkgPath = path.join(root, "package.json");
229
- if (!fs.existsSync(pkgPath)) return [];
230
- try {
231
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
232
- const allDeps = {
233
- ...pkg.dependencies,
234
- ...pkg.devDependencies
235
- };
236
- return NATIVE_MODULES_TO_STUB.filter((mod) => mod in allDeps);
237
- } catch {
238
- return [];
239
- }
264
+ function detectNativeModules(allDeps) {
265
+ return NATIVE_MODULES_TO_STUB.filter((mod) => mod in allDeps);
240
266
  }
241
267
  /** @see {@link _ensureESModule} */
242
268
  const ensureESModule = ensureESModule$1;
@@ -280,6 +306,17 @@ import { handleImageOptimization, DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isI
280
306
  import type { ImageConfig } from "vinext/server/image-optimization";
281
307
  import handler from "vinext/server/app-router-entry";
282
308
 
309
+ const imageConfig: ImageConfig = {
310
+ deviceSizes: JSON.parse(
311
+ process.env.__VINEXT_IMAGE_DEVICE_SIZES ?? JSON.stringify(DEFAULT_DEVICE_SIZES),
312
+ ),
313
+ imageSizes: JSON.parse(
314
+ process.env.__VINEXT_IMAGE_SIZES ?? JSON.stringify(DEFAULT_IMAGE_SIZES),
315
+ ),
316
+ qualities: JSON.parse(process.env.__VINEXT_IMAGE_QUALITIES ?? "null") ?? undefined,
317
+ dangerouslyAllowSVG: process.env.__VINEXT_IMAGE_DANGEROUSLY_ALLOW_SVG === "true",
318
+ };
319
+
283
320
  interface Env {
284
321
  ASSETS: Fetcher;
285
322
  IMAGES: {
@@ -310,14 +347,17 @@ export default {
310
347
  // The parseImageParams validation inside handleImageOptimization
311
348
  // normalizes backslashes and validates the origin hasn't changed.
312
349
  if (isImageOptimizationPath(url.pathname)) {
313
- const allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES];
350
+ const allowedWidths = [
351
+ ...(imageConfig.deviceSizes ?? DEFAULT_DEVICE_SIZES),
352
+ ...(imageConfig.imageSizes ?? DEFAULT_IMAGE_SIZES),
353
+ ];
314
354
  return handleImageOptimization(request, {
315
355
  fetchAsset: (path) => env.ASSETS.fetch(new Request(new URL(path, request.url))),
316
356
  transformImage: async (body, { width, format, quality }) => {
317
357
  const result = await env.IMAGES.input(body).transform(width > 0 ? { width } : {}).output({ format, quality });
318
358
  return result.response();
319
359
  },
320
- }, allowedWidths);
360
+ }, allowedWidths, imageConfig);
321
361
  }
322
362
 
323
363
  // Delegate everything else to vinext, forwarding ctx so that
@@ -334,17 +374,17 @@ function generatePagesRouterWorkerEntry() {
334
374
  * Cloudflare Worker entry point -- auto-generated by vinext deploy.
335
375
  * Edit freely or delete to regenerate on next deploy.
336
376
  */
337
- import { runPagesRequest, wrapMiddlewareWithBasePath } from "vinext/server/pages-request-pipeline";
377
+ import { fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath } from "vinext/server/pages-request-pipeline";
338
378
  import type { PagesPipelineDeps } from "vinext/server/pages-request-pipeline";
339
379
  import { handleImageOptimization, DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath } from "vinext/server/image-optimization";
340
380
  import type { ImageConfig } from "vinext/server/image-optimization";
341
- import { cloneRequestWithHeaders, filterInternalHeaders, isOpenRedirectShaped } from "vinext/server/request-pipeline";
381
+ import { cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders, isOpenRedirectShaped } from "vinext/server/request-pipeline";
342
382
  import { notFoundStaticAssetResponse } from "vinext/server/http-error-responses";
343
383
  import { assetPrefixPathname, isNextStaticPath } from "vinext/utils/asset-prefix";
344
384
  import { hasBasePath, stripBasePath } from "vinext/utils/base-path";
345
385
 
346
386
  // @ts-expect-error -- virtual module resolved by vinext at build time
347
- import { renderPage, handleApiRoute, runMiddleware, vinextConfig, matchPageRoute } from "virtual:vinext-server-entry";
387
+ import { renderPage, handleApiRoute, runMiddleware, normalizeDataRequest, vinextConfig, matchPageRoute } from "virtual:vinext-server-entry";
348
388
  // @ts-expect-error -- virtual module resolved by vinext at build time
349
389
  import { registerConfiguredCacheAdapters } from "virtual:vinext-cache-adapters";
350
390
 
@@ -373,6 +413,7 @@ const configRedirects = vinextConfig?.redirects ?? [];
373
413
  const configRewrites = vinextConfig?.rewrites ?? { beforeFiles: [], afterFiles: [], fallback: [] };
374
414
  const configHeaders = vinextConfig?.headers ?? [];
375
415
  const imageConfig: ImageConfig | undefined = vinextConfig?.images ? {
416
+ qualities: vinextConfig.images.qualities,
376
417
  dangerouslyAllowSVG: vinextConfig.images.dangerouslyAllowSVG,
377
418
  dangerouslyAllowLocalIP: vinextConfig.images.dangerouslyAllowLocalIP,
378
419
  contentDispositionType: vinextConfig.images.contentDispositionType,
@@ -407,11 +448,6 @@ export default {
407
448
  return notFoundStaticAssetResponse();
408
449
  }
409
450
 
410
- // Capture x-nextjs-data before filterInternalHeaders strips it -- the
411
- // middleware redirect protocol needs to know whether the inbound request
412
- // was a _next/data fetch to emit x-nextjs-redirect instead of a 3xx.
413
- const isDataRequest = request.headers.get("x-nextjs-data") === "1";
414
-
415
451
  // Strip internal headers from inbound requests so they cannot be
416
452
  // forged to influence routing or impersonate internal state.
417
453
  // Request.headers is immutable in Workers, so build a clean copy.
@@ -430,15 +466,26 @@ export default {
430
466
  if (stripped !== pathname) {
431
467
  const strippedUrl = new URL(request.url);
432
468
  strippedUrl.pathname = stripped;
433
- request = new Request(strippedUrl, request);
469
+ request = cloneRequestWithUrl(request, strippedUrl.toString());
434
470
  pathname = stripped;
435
471
  }
436
472
  }
437
473
 
474
+ const dataNorm = normalizeDataRequest(request);
475
+ if (dataNorm.notFoundResponse) return dataNorm.notFoundResponse;
476
+ const isDataReq = dataNorm.isDataReq;
477
+ if (isDataReq) {
478
+ request = dataNorm.request;
479
+ pathname = dataNorm.normalizedPathname;
480
+ }
481
+
438
482
  // ── Image optimization via Cloudflare Images binding ──────────
439
483
  // Checked after basePath stripping so /<basePath>/_next/image works.
440
484
  if (isImageOptimizationPath(pathname)) {
441
- const allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES];
485
+ const allowedWidths = [
486
+ ...(vinextConfig?.images?.deviceSizes ?? DEFAULT_DEVICE_SIZES),
487
+ ...(vinextConfig?.images?.imageSizes ?? DEFAULT_IMAGE_SIZES),
488
+ ];
442
489
  return handleImageOptimization(request, {
443
490
  fetchAsset: (path) => env.ASSETS.fetch(new Request(new URL(path, request.url))),
444
491
  transformImage: async (body, { width, format, quality }) => {
@@ -460,12 +507,8 @@ export default {
460
507
  configRewrites,
461
508
  configHeaders,
462
509
  hadBasePath,
463
- // The worker adapter does not do _next/data URL normalization (no
464
- // buildId available at request time). isDataReq is used by the pipeline
465
- // only for renderPage options and shouldDeferErrorPageOnMiss -- false
466
- // is correct here.
467
- isDataReq: false,
468
- isDataRequest,
510
+ isDataReq,
511
+ isDataRequest: isDataReq,
469
512
  ctx,
470
513
  matchPageRoute: typeof matchPageRoute === "function" ? matchPageRoute : null,
471
514
  // Pass the original (pre-basePath-stripping) URL to middleware so that
@@ -483,6 +526,14 @@ export default {
483
526
  handleApi: typeof handleApiRoute === "function"
484
527
  ? (req, apiUrl) => handleApiRoute(req, apiUrl, ctx)
485
528
  : null,
529
+ serveFilesystemRoute: async (requestPathname, _stagedHeaders, phase) => {
530
+ return fetchWorkerFilesystemRoute(
531
+ request,
532
+ requestPathname,
533
+ phase,
534
+ (assetRequest) => env.ASSETS.fetch(assetRequest),
535
+ );
536
+ },
486
537
  };
487
538
 
488
539
  const result = await runPagesRequest(request, deps);
@@ -797,58 +848,97 @@ async function runBuild(info, env) {
797
848
  await (await createBuilder({ root: info.root })).buildApp();
798
849
  });
799
850
  }
851
+ function validateWranglerEnvName(env) {
852
+ if (env.includes("\0")) throw new Error("Wrangler environment names cannot contain null bytes.");
853
+ return env;
854
+ }
800
855
  function buildWranglerDeployArgs(options) {
801
856
  const args = ["deploy"];
802
857
  const env = options.env || (options.preview ? "preview" : void 0);
803
- if (env) args.push("--env", env);
858
+ if (env) args.push("--env", validateWranglerEnvName(env));
804
859
  return {
805
860
  args,
806
861
  env
807
862
  };
808
863
  }
809
864
  /**
810
- * Resolve the wrangler executable in node_modules.
865
+ * Resolve Wrangler's JavaScript CLI entrypoint in node_modules.
811
866
  *
812
- * Walks up ancestor directories so the binary is found even when node_modules
813
- * is hoisted to the workspace root in a monorepo.
814
- *
815
- * On Windows, `node_modules/.bin/` contains both a Unix shebang script (no
816
- * extension) and a `.CMD` shim. Node's `execFileSync` uses CreateProcess(),
817
- * which only resolves PATHEXT extensions (`.cmd`, `.exe`, ...) — spawning the
818
- * bare-name shebang file fails with ENOENT even though the file exists. So on
819
- * Windows we prefer the `.CMD` shim and only fall back to the bare name for a
820
- * clearer error message if neither is present.
867
+ * Invoking the JavaScript file through `process.execPath` avoids the `.cmd`
868
+ * shim and command shell that package managers create on Windows.
821
869
  */
822
- function resolveWranglerBin(root, platform = process.platform) {
823
- const candidates = platform === "win32" ? [
824
- ".bin/wrangler.CMD",
825
- ".bin/wrangler.cmd",
826
- ".bin/wrangler"
827
- ] : [".bin/wrangler"];
828
- for (const candidate of candidates) {
829
- const found = findInNodeModules(root, candidate);
830
- if (found) return found;
870
+ function resolveWranglerBin(root, resolvePackageJson = (projectRoot) => {
871
+ try {
872
+ return createRequire(path.join(projectRoot, "package.json")).resolve("wrangler/package.json");
873
+ } catch {
874
+ return findInNodeModules(projectRoot, "wrangler/package.json");
831
875
  }
832
- return path.join(root, "node_modules", ...candidates[0].split("/"));
876
+ }) {
877
+ const packageJsonPath = resolvePackageJson(root);
878
+ if (packageJsonPath) {
879
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
880
+ const bin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.wrangler;
881
+ if (bin) return path.resolve(path.dirname(packageJsonPath), bin);
882
+ }
883
+ return path.join(root, "node_modules", "wrangler", "bin", "wrangler.js");
833
884
  }
834
- function runWranglerDeploy(root, options) {
885
+ function buildNodeCliInvocation(scriptPath, args, nodeExecutable = process.execPath) {
886
+ return {
887
+ file: nodeExecutable,
888
+ args: [scriptPath, ...args]
889
+ };
890
+ }
891
+ function buildWranglerInvocation(root, options, nodeExecutable = process.execPath) {
835
892
  const wranglerBin = resolveWranglerBin(root);
893
+ const { args, env } = buildWranglerDeployArgs(options);
894
+ return {
895
+ ...buildNodeCliInvocation(wranglerBin, args, nodeExecutable),
896
+ env
897
+ };
898
+ }
899
+ function runWranglerDeploy(root, options, execute = execFileSync) {
836
900
  const execOpts = {
837
901
  cwd: root,
838
902
  stdio: "pipe",
839
903
  encoding: "utf-8",
840
- shell: process.platform === "win32"
904
+ shell: false
841
905
  };
842
- const { args, env } = buildWranglerDeployArgs(options);
906
+ const { file, args, env } = buildWranglerInvocation(root, options);
843
907
  if (env) console.log(`\n Deploying to env: ${env}...`);
844
908
  else console.log("\n Deploying to production...");
845
- const output = execFileSync(wranglerBin, args, execOpts);
909
+ const output = execute(file, args, execOpts);
846
910
  const urlMatch = output.match(/https:\/\/[^\s]+\.workers\.dev[^\s]*/);
847
911
  const deployedUrl = urlMatch ? urlMatch[0] : null;
848
912
  if (output.trim()) for (const line of output.trim().split("\n")) console.log(` ${line}`);
849
913
  return deployedUrl ?? "(URL not detected in wrangler output)";
850
914
  }
915
+ const VINEXT_PREGEN_START = "/* __VINEXT_PREGENERATED_CONCRETE_PATHS_START__ */";
916
+ const VINEXT_PREGEN_END = "/* __VINEXT_PREGENERATED_CONCRETE_PATHS_END__ */";
917
+ const VINEXT_PREGEN_RE = new RegExp(`${escapeRegExp(VINEXT_PREGEN_START)}[\\s\\S]*?${escapeRegExp(VINEXT_PREGEN_END)}\\n?`, "g");
918
+ /**
919
+ * Read the prerender manifest and inject pregenerated concrete paths into the
920
+ * App Router Worker bundle so the PPR fallback-shell guard is populated at
921
+ * module init time without calling `seedMemoryCacheFromPrerender`.
922
+ *
923
+ * The paths are injected as `globalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS`
924
+ * wrapped in replaceable marker comments, and consumed by
925
+ * `initPregeneratedPathsFromGlobals` in the generated RSC entry.
926
+ *
927
+ * Idempotent: repeated calls strip the previous injection before writing the
928
+ * new one. If the manifest is missing, corrupt, or empty, any prior injection
929
+ * is stripped and nothing new is written — failing closed to empty.
930
+ */
931
+ function injectPregeneratedConcretePaths(root) {
932
+ const workerEntry = path.resolve(root, "dist", "server", "index.js");
933
+ if (!fs.existsSync(workerEntry)) return;
934
+ let code = fs.readFileSync(workerEntry, "utf-8");
935
+ code = code.replace(VINEXT_PREGEN_RE, "");
936
+ const table = buildPregeneratedConcretePathTable(readPrerenderManifest(path.join(root, "dist", "server", "vinext-prerender.json")) ?? {});
937
+ if (table.length > 0) code = `${VINEXT_PREGEN_START}\nglobalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS = ${JSON.stringify(table)};\n${VINEXT_PREGEN_END}\n` + code;
938
+ fs.writeFileSync(workerEntry, code);
939
+ }
851
940
  async function deploy(options) {
941
+ const deployEnv = validateWranglerEnvName(options.env || (options.preview ? "preview" : "production"));
852
942
  const root = path.resolve(options.root);
853
943
  loadDotenv({
854
944
  root,
@@ -905,7 +995,7 @@ async function deploy(options) {
905
995
  console.log("\n Dry run complete. Files generated but no build or deploy performed.\n");
906
996
  return;
907
997
  }
908
- if (!options.skipBuild) await runBuild(info, options.env || (options.preview ? "preview" : void 0));
998
+ if (!options.skipBuild) await runBuild(info, deployEnv === "production" && !options.env ? void 0 : deployEnv);
909
999
  else console.log("\n Skipping build (--skip-build)");
910
1000
  {
911
1001
  const nextConfig = await resolveNextConfig(await loadNextConfig(info.root), info.root);
@@ -922,6 +1012,7 @@ async function deploy(options) {
922
1012
  concurrency: options.prerenderConcurrency
923
1013
  });
924
1014
  }
1015
+ injectPregeneratedConcretePaths(root);
925
1016
  }
926
1017
  if (options.experimentalTPR) {
927
1018
  console.log();
@@ -933,13 +1024,10 @@ async function deploy(options) {
933
1024
  });
934
1025
  if (tprResult.skipped) console.log(` TPR: Skipped (${tprResult.skipped})`);
935
1026
  }
936
- const url = runWranglerDeploy(root, {
937
- preview: options.preview ?? false,
938
- env: options.env
939
- });
1027
+ const url = runWranglerDeploy(root, { env: deployEnv === "production" && !options.env ? void 0 : deployEnv });
940
1028
  console.log("\n ─────────────────────────────────────────");
941
1029
  console.log(` Deployed to: ${url}`);
942
1030
  console.log(" ─────────────────────────────────────────\n");
943
1031
  }
944
1032
  //#endregion
945
- export { buildWranglerDeployArgs, deploy, detectProject, ensureESModule, formatMissingCacheAdapterError, formatMissingCloudflarePluginError, generateAppRouterViteConfig, generateAppRouterWorkerEntry, generatePagesRouterViteConfig, generatePagesRouterWorkerEntry, generateWranglerConfig, getFilesToGenerate, getMissingDeps, hasWranglerConfig, isPackageResolvable, parseDeployArgs, renameCJSConfigs, resolveWranglerBin, viteConfigHasCacheAdapter, viteConfigHasCloudflarePlugin, withCloudflareEnv, workerEntryHasCacheHandler };
1033
+ export { buildNodeCliInvocation, buildWranglerDeployArgs, buildWranglerInvocation, deploy, detectProject, ensureESModule, formatMissingCacheAdapterError, formatMissingCloudflarePluginError, generateAppRouterViteConfig, generateAppRouterWorkerEntry, generatePagesRouterViteConfig, generatePagesRouterWorkerEntry, generateWranglerConfig, getFilesToGenerate, getMissingDeps, hasWranglerConfig, injectPregeneratedConcretePaths, isPackageResolvable, parseDeployArgs, renameCJSConfigs, resolveWranglerBin, runWranglerDeploy, validateWranglerEnvName, viteConfigHasCacheAdapter, viteConfigHasCloudflarePlugin, withCloudflareEnv, workerEntryHasCacheHandler };
@@ -1,5 +1,6 @@
1
+ import { NextRewrite } from "../config/next-config.js";
1
2
  import { AppRoute, RouteManifest } from "../routing/app-route-graph.js";
2
- import { VinextLinkPrefetchRoute } from "../client/vinext-next-data.js";
3
+ import { VinextLinkPrefetchRoute, VinextPagesLinkPrefetchRoute } from "../client/vinext-next-data.js";
3
4
 
4
5
  //#region src/entries/app-browser-entry.d.ts
5
6
  /**
@@ -9,7 +10,11 @@ import { VinextLinkPrefetchRoute } from "../client/vinext-next-data.js";
9
10
  * embedded RSC payload and handles client-side navigation by re-fetching
10
11
  * RSC streams.
11
12
  */
12
- declare function generateBrowserEntry(routes?: readonly AppRoute[], routeManifest?: RouteManifest | null): string;
13
+ declare function generateBrowserEntry(routes?: readonly AppRoute[], routeManifest?: RouteManifest | null, pagesPrefetchRoutes?: readonly VinextPagesLinkPrefetchRoute[], rewrites?: {
14
+ afterFiles: NextRewrite[];
15
+ beforeFiles: NextRewrite[];
16
+ fallback: NextRewrite[];
17
+ }): string;
13
18
  /**
14
19
  * Filter for routes that should appear in the `__VINEXT_LINK_PREFETCH_ROUTES__`
15
20
  * manifest. Exported so the Pages Router client entry can reuse it when
@@ -17,7 +22,8 @@ declare function generateBrowserEntry(routes?: readonly AppRoute[], routeManifes
17
22
  * `pages-client-entry.ts`.
18
23
  */
19
24
  declare function isLinkPrefetchRoute(route: AppRoute): boolean;
25
+ declare function toDocumentOnlyAppRoute(route: AppRoute): VinextLinkPrefetchRoute;
20
26
  /** Project an `AppRoute` down to the public `VinextLinkPrefetchRoute` shape. */
21
27
  declare function toLinkPrefetchRoute(route: AppRoute): VinextLinkPrefetchRoute;
22
28
  //#endregion
23
- export { generateBrowserEntry, isLinkPrefetchRoute, toLinkPrefetchRoute };
29
+ export { generateBrowserEntry, isLinkPrefetchRoute, toDocumentOnlyAppRoute, toLinkPrefetchRoute };
@@ -7,13 +7,23 @@ import { resolveClientRuntimeModule, resolveRuntimeEntryModule } from "./runtime
7
7
  * embedded RSC payload and handles client-side navigation by re-fetching
8
8
  * RSC streams.
9
9
  */
10
- function generateBrowserEntry(routes = [], routeManifest = null) {
10
+ function generateBrowserEntry(routes = [], routeManifest = null, pagesPrefetchRoutes = [], rewrites = {
11
+ afterFiles: [],
12
+ beforeFiles: [],
13
+ fallback: []
14
+ }) {
11
15
  const entryPath = resolveRuntimeEntryModule("app-browser-entry");
12
16
  const navigationRuntimePath = resolveClientRuntimeModule("navigation-runtime");
13
- const prefetchRoutes = routes.filter(isLinkPrefetchRoute).map(toLinkPrefetchRoute);
17
+ const prefetchRoutes = routes.map((route) => isLinkPrefetchRoute(route) ? toLinkPrefetchRoute(route) : toDocumentOnlyAppRoute(route));
14
18
  return `import { registerNavigationRuntimeBootstrap } from ${JSON.stringify(navigationRuntimePath)};
15
19
 
16
20
  window.__VINEXT_LINK_PREFETCH_ROUTES__ = ${JSON.stringify(prefetchRoutes)};
21
+ // Pages route manifest for hybrid ownership decisions. In a hybrid
22
+ // app+pages build the user can land on an App page, so the App browser
23
+ // entry must also expose the Pages manifest (the Pages client entry does
24
+ // the same — whichever entry runs first emits both globals).
25
+ window.__VINEXT_PAGES_LINK_PREFETCH_ROUTES__ = ${JSON.stringify(pagesPrefetchRoutes)};
26
+ window.__VINEXT_CLIENT_REWRITES__ = ${JSON.stringify(rewrites)};
17
27
  registerNavigationRuntimeBootstrap({
18
28
  routeManifest: ${buildRouteManifestExpression(routeManifest)}
19
29
  });
@@ -29,6 +39,14 @@ function isLinkPrefetchRoute(route) {
29
39
  if (route.pagePath !== null) return true;
30
40
  return route.routePath === null && route.layouts.length > 0;
31
41
  }
42
+ function toDocumentOnlyAppRoute(route) {
43
+ return {
44
+ canPrefetchLoadingShell: false,
45
+ documentOnly: true,
46
+ patternParts: [...route.patternParts],
47
+ isDynamic: route.isDynamic
48
+ };
49
+ }
32
50
  /** Project an `AppRoute` down to the public `VinextLinkPrefetchRoute` shape. */
33
51
  function toLinkPrefetchRoute(route) {
34
52
  return {
@@ -62,4 +80,4 @@ function buildMapExpression(map) {
62
80
  return `new Map(${JSON.stringify(Array.from(map.entries()))})`;
63
81
  }
64
82
  //#endregion
65
- export { generateBrowserEntry, isLinkPrefetchRoute, toLinkPrefetchRoute };
83
+ export { generateBrowserEntry, isLinkPrefetchRoute, toDocumentOnlyAppRoute, toLinkPrefetchRoute };
@@ -1,6 +1,7 @@
1
1
  import { NextHeader, NextI18nConfig, NextRedirect, NextRewrite } from "../config/next-config.js";
2
2
  import { AppRoute } from "../routing/app-route-graph.js";
3
3
  import { MetadataFileRoute } from "../server/metadata-routes.js";
4
+ import { ImageConfig } from "../server/image-optimization.js";
4
5
 
5
6
  //#region src/entries/app-rsc-entry.d.ts
6
7
  /**
@@ -45,6 +46,7 @@ type AppRouterConfig = {
45
46
  inlineCss?: boolean; /** Enables Next.js Cache Components semantics for App Router document HTML. */
46
47
  cacheComponents?: boolean; /** Internationalization routing config for middleware matcher locale handling. */
47
48
  i18n?: NextI18nConfig | null;
49
+ imageConfig?: ImageConfig;
48
50
  /**
49
51
  * Absolute path to `app/global-not-found.{tsx,ts,js,jsx}` when present.
50
52
  * When provided, route-miss 404s render this module standalone (it owns its