vinext 0.1.2 → 0.1.3

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 (199) hide show
  1. package/dist/build/prerender.d.ts +9 -1
  2. package/dist/build/prerender.js +41 -12
  3. package/dist/build/run-prerender.d.ts +10 -2
  4. package/dist/build/run-prerender.js +15 -1
  5. package/dist/client/app-nav-failure-handler.d.ts +8 -0
  6. package/dist/client/app-nav-failure-handler.js +44 -0
  7. package/dist/client/vinext-next-data.d.ts +18 -1
  8. package/dist/client/window-next.d.ts +2 -1
  9. package/dist/client/window-next.js +12 -1
  10. package/dist/cloudflare/src/cache/cdn-adapter.runtime.js +6 -1
  11. package/dist/config/config-matchers.js +73 -14
  12. package/dist/config/next-config.d.ts +46 -4
  13. package/dist/config/next-config.js +147 -48
  14. package/dist/deploy.d.ts +30 -11
  15. package/dist/deploy.js +180 -99
  16. package/dist/entries/app-browser-entry.d.ts +9 -3
  17. package/dist/entries/app-browser-entry.js +21 -3
  18. package/dist/entries/app-rsc-entry.d.ts +2 -0
  19. package/dist/entries/app-rsc-entry.js +64 -5
  20. package/dist/entries/app-rsc-manifest.js +2 -0
  21. package/dist/entries/app-ssr-entry.js +1 -1
  22. package/dist/entries/pages-client-entry.js +53 -8
  23. package/dist/entries/pages-server-entry.js +41 -5
  24. package/dist/index.js +200 -62
  25. package/dist/plugins/extensionless-dynamic-import.d.ts +6 -0
  26. package/dist/plugins/extensionless-dynamic-import.js +152 -0
  27. package/dist/plugins/optimize-imports.d.ts +2 -1
  28. package/dist/plugins/optimize-imports.js +11 -9
  29. package/dist/plugins/postcss.js +7 -7
  30. package/dist/plugins/typeof-window.d.ts +14 -0
  31. package/dist/plugins/typeof-window.js +150 -0
  32. package/dist/routing/app-route-graph.d.ts +2 -1
  33. package/dist/routing/app-route-graph.js +44 -14
  34. package/dist/routing/file-matcher.d.ts +10 -1
  35. package/dist/routing/file-matcher.js +22 -1
  36. package/dist/routing/pages-router.js +3 -3
  37. package/dist/routing/utils.d.ts +35 -6
  38. package/dist/routing/utils.js +59 -7
  39. package/dist/server/api-handler.d.ts +6 -1
  40. package/dist/server/api-handler.js +21 -15
  41. package/dist/server/app-browser-action-result.d.ts +19 -6
  42. package/dist/server/app-browser-action-result.js +19 -10
  43. package/dist/server/app-browser-entry.js +167 -90
  44. package/dist/server/app-browser-error.d.ts +10 -6
  45. package/dist/server/app-browser-error.js +43 -8
  46. package/dist/server/app-browser-hydration.d.ts +2 -0
  47. package/dist/server/app-browser-hydration.js +1 -0
  48. package/dist/server/app-browser-navigation-controller.d.ts +4 -2
  49. package/dist/server/app-browser-navigation-controller.js +23 -2
  50. package/dist/server/app-browser-server-action-navigation.d.ts +6 -0
  51. package/dist/server/app-browser-server-action-navigation.js +9 -0
  52. package/dist/server/app-browser-stream.js +86 -43
  53. package/dist/server/app-elements-wire.d.ts +6 -1
  54. package/dist/server/app-elements-wire.js +14 -4
  55. package/dist/server/app-elements.d.ts +2 -2
  56. package/dist/server/app-elements.js +2 -2
  57. package/dist/server/app-fallback-renderer.d.ts +1 -0
  58. package/dist/server/app-fallback-renderer.js +3 -1
  59. package/dist/server/app-optimistic-routing.js +2 -2
  60. package/dist/server/app-page-boundary-render.d.ts +1 -0
  61. package/dist/server/app-page-boundary-render.js +27 -14
  62. package/dist/server/app-page-cache-render.d.ts +53 -0
  63. package/dist/server/app-page-cache-render.js +91 -0
  64. package/dist/server/app-page-cache.d.ts +16 -2
  65. package/dist/server/app-page-cache.js +62 -1
  66. package/dist/server/app-page-dispatch.d.ts +26 -0
  67. package/dist/server/app-page-dispatch.js +149 -92
  68. package/dist/server/app-page-element-builder.d.ts +1 -0
  69. package/dist/server/app-page-element-builder.js +5 -2
  70. package/dist/server/app-page-execution.d.ts +6 -1
  71. package/dist/server/app-page-execution.js +21 -1
  72. package/dist/server/app-page-probe.d.ts +1 -0
  73. package/dist/server/app-page-probe.js +4 -0
  74. package/dist/server/app-page-render-observation.d.ts +3 -1
  75. package/dist/server/app-page-render-observation.js +17 -1
  76. package/dist/server/app-page-render.d.ts +12 -1
  77. package/dist/server/app-page-render.js +42 -4
  78. package/dist/server/app-page-request.d.ts +2 -0
  79. package/dist/server/app-page-request.js +2 -1
  80. package/dist/server/app-page-route-wiring.d.ts +3 -1
  81. package/dist/server/app-page-route-wiring.js +14 -5
  82. package/dist/server/app-page-stream.d.ts +15 -3
  83. package/dist/server/app-page-stream.js +11 -5
  84. package/dist/server/app-pages-bridge.d.ts +18 -0
  85. package/dist/server/app-pages-bridge.js +22 -5
  86. package/dist/server/app-ppr-fallback-shell-render.d.ts +17 -0
  87. package/dist/server/app-ppr-fallback-shell-render.js +26 -0
  88. package/dist/server/app-ppr-fallback-shell.d.ts +13 -1
  89. package/dist/server/app-ppr-fallback-shell.js +8 -1
  90. package/dist/server/app-route-handler-dispatch.js +9 -2
  91. package/dist/server/app-route-handler-policy.d.ts +1 -0
  92. package/dist/server/app-router-entry.js +5 -0
  93. package/dist/server/app-rsc-cache-busting.js +2 -0
  94. package/dist/server/app-rsc-handler.d.ts +25 -0
  95. package/dist/server/app-rsc-handler.js +154 -54
  96. package/dist/server/app-rsc-route-matching.d.ts +3 -0
  97. package/dist/server/app-rsc-route-matching.js +2 -0
  98. package/dist/server/app-segment-config.d.ts +9 -1
  99. package/dist/server/app-segment-config.js +12 -3
  100. package/dist/server/app-server-action-execution.d.ts +1 -0
  101. package/dist/server/app-server-action-execution.js +42 -13
  102. package/dist/server/app-ssr-entry.d.ts +2 -0
  103. package/dist/server/app-ssr-entry.js +83 -10
  104. package/dist/server/cache-control.js +4 -0
  105. package/dist/server/dev-server.d.ts +2 -2
  106. package/dist/server/dev-server.js +244 -51
  107. package/dist/server/hybrid-route-priority.d.ts +22 -0
  108. package/dist/server/hybrid-route-priority.js +33 -0
  109. package/dist/server/image-optimization.d.ts +18 -9
  110. package/dist/server/image-optimization.js +37 -23
  111. package/dist/server/implicit-tags.d.ts +2 -1
  112. package/dist/server/implicit-tags.js +4 -1
  113. package/dist/server/navigation-planner.d.ts +133 -30
  114. package/dist/server/navigation-planner.js +114 -0
  115. package/dist/server/navigation-trace.d.ts +8 -1
  116. package/dist/server/navigation-trace.js +8 -1
  117. package/dist/server/pages-api-route.d.ts +6 -0
  118. package/dist/server/pages-api-route.js +13 -2
  119. package/dist/server/pages-asset-tags.d.ts +2 -1
  120. package/dist/server/pages-asset-tags.js +6 -2
  121. package/dist/server/pages-data-route.d.ts +8 -1
  122. package/dist/server/pages-data-route.js +11 -2
  123. package/dist/server/pages-get-initial-props.d.ts +54 -4
  124. package/dist/server/pages-get-initial-props.js +43 -1
  125. package/dist/server/pages-node-compat.js +2 -2
  126. package/dist/server/pages-page-data.d.ts +11 -2
  127. package/dist/server/pages-page-data.js +204 -33
  128. package/dist/server/pages-page-handler.d.ts +4 -2
  129. package/dist/server/pages-page-handler.js +59 -22
  130. package/dist/server/pages-page-response.d.ts +2 -1
  131. package/dist/server/pages-page-response.js +7 -4
  132. package/dist/server/pages-request-pipeline.d.ts +1 -0
  133. package/dist/server/pages-request-pipeline.js +73 -36
  134. package/dist/server/pregenerated-concrete-paths.d.ts +1 -17
  135. package/dist/server/pregenerated-concrete-paths.js +2 -19
  136. package/dist/server/prerender-manifest.d.ts +33 -0
  137. package/dist/server/prerender-manifest.js +54 -0
  138. package/dist/server/prerender-route-params.d.ts +1 -2
  139. package/dist/server/prod-server.js +9 -3
  140. package/dist/server/request-pipeline.d.ts +3 -15
  141. package/dist/server/request-pipeline.js +58 -47
  142. package/dist/server/rsc-stream-hints.d.ts +5 -1
  143. package/dist/server/rsc-stream-hints.js +6 -1
  144. package/dist/server/seed-cache.js +10 -18
  145. package/dist/shims/app-router-scroll-state.d.ts +3 -1
  146. package/dist/shims/app-router-scroll-state.js +14 -2
  147. package/dist/shims/app-router-scroll.d.ts +3 -0
  148. package/dist/shims/app-router-scroll.js +28 -18
  149. package/dist/shims/cache-runtime.js +3 -2
  150. package/dist/shims/cache.d.ts +1 -0
  151. package/dist/shims/cache.js +1 -1
  152. package/dist/shims/cdn-cache.d.ts +5 -5
  153. package/dist/shims/dynamic-preload-chunks.js +6 -4
  154. package/dist/shims/error-boundary.d.ts +2 -0
  155. package/dist/shims/error-boundary.js +7 -0
  156. package/dist/shims/error.js +3 -2
  157. package/dist/shims/error.react-server.d.ts +9 -0
  158. package/dist/shims/error.react-server.js +6 -0
  159. package/dist/shims/fetch-cache.d.ts +3 -1
  160. package/dist/shims/fetch-cache.js +45 -20
  161. package/dist/shims/hash-scroll.js +6 -1
  162. package/dist/shims/headers.js +29 -4
  163. package/dist/shims/internal/als-registry.js +28 -1
  164. package/dist/shims/internal/app-route-detection.js +8 -17
  165. package/dist/shims/internal/hybrid-client-route-owner.d.ts +31 -0
  166. package/dist/shims/internal/hybrid-client-route-owner.js +143 -0
  167. package/dist/shims/internal/navigation-untracked.d.ts +35 -0
  168. package/dist/shims/internal/navigation-untracked.js +55 -0
  169. package/dist/shims/internal/pages-data-target.d.ts +7 -2
  170. package/dist/shims/internal/pages-data-target.js +17 -8
  171. package/dist/shims/internal/pages-router-accessor.d.ts +19 -0
  172. package/dist/shims/internal/pages-router-accessor.js +13 -0
  173. package/dist/shims/internal/router-context.d.ts +2 -1
  174. package/dist/shims/internal/router-context.js +3 -1
  175. package/dist/shims/link.js +12 -5
  176. package/dist/shims/navigation.d.ts +8 -2
  177. package/dist/shims/navigation.js +61 -31
  178. package/dist/shims/ppr-fallback-shell.d.ts +5 -1
  179. package/dist/shims/ppr-fallback-shell.js +28 -7
  180. package/dist/shims/router.d.ts +13 -2
  181. package/dist/shims/router.js +419 -128
  182. package/dist/shims/server.d.ts +16 -1
  183. package/dist/shims/server.js +44 -12
  184. package/dist/shims/unified-request-context.js +1 -0
  185. package/dist/utils/built-asset-url.d.ts +4 -0
  186. package/dist/utils/built-asset-url.js +11 -0
  187. package/dist/utils/commonjs-loader.d.ts +16 -0
  188. package/dist/utils/commonjs-loader.js +100 -0
  189. package/dist/utils/deployment-id.d.ts +8 -0
  190. package/dist/utils/deployment-id.js +22 -0
  191. package/dist/utils/html-limited-bots.d.ts +18 -1
  192. package/dist/utils/html-limited-bots.js +23 -1
  193. package/dist/utils/parse-cookie.d.ts +13 -0
  194. package/dist/utils/parse-cookie.js +52 -0
  195. package/dist/utils/path.d.ts +7 -1
  196. package/dist/utils/path.js +9 -1
  197. package/package.json +2 -2
  198. package/dist/shims/internal/parse-cookie-header.d.ts +0 -14
  199. package/dist/shims/internal/parse-cookie-header.js +0 -30
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
@@ -338,7 +378,7 @@ import { runPagesRequest, wrapMiddlewareWithBasePath } from "vinext/server/pages
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";
@@ -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,
@@ -430,7 +471,7 @@ export default {
430
471
  if (stripped !== pathname) {
431
472
  const strippedUrl = new URL(request.url);
432
473
  strippedUrl.pathname = stripped;
433
- request = new Request(strippedUrl, request);
474
+ request = cloneRequestWithUrl(request, strippedUrl.toString());
434
475
  pathname = stripped;
435
476
  }
436
477
  }
@@ -438,7 +479,10 @@ export default {
438
479
  // ── Image optimization via Cloudflare Images binding ──────────
439
480
  // Checked after basePath stripping so /<basePath>/_next/image works.
440
481
  if (isImageOptimizationPath(pathname)) {
441
- const allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES];
482
+ const allowedWidths = [
483
+ ...(vinextConfig?.images?.deviceSizes ?? DEFAULT_DEVICE_SIZES),
484
+ ...(vinextConfig?.images?.imageSizes ?? DEFAULT_IMAGE_SIZES),
485
+ ];
442
486
  return handleImageOptimization(request, {
443
487
  fetchAsset: (path) => env.ASSETS.fetch(new Request(new URL(path, request.url))),
444
488
  transformImage: async (body, { width, format, quality }) => {
@@ -797,58 +841,97 @@ async function runBuild(info, env) {
797
841
  await (await createBuilder({ root: info.root })).buildApp();
798
842
  });
799
843
  }
844
+ function validateWranglerEnvName(env) {
845
+ if (env.includes("\0")) throw new Error("Wrangler environment names cannot contain null bytes.");
846
+ return env;
847
+ }
800
848
  function buildWranglerDeployArgs(options) {
801
849
  const args = ["deploy"];
802
850
  const env = options.env || (options.preview ? "preview" : void 0);
803
- if (env) args.push("--env", env);
851
+ if (env) args.push("--env", validateWranglerEnvName(env));
804
852
  return {
805
853
  args,
806
854
  env
807
855
  };
808
856
  }
809
857
  /**
810
- * Resolve the wrangler executable in node_modules.
858
+ * Resolve Wrangler's JavaScript CLI entrypoint in node_modules.
811
859
  *
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.
860
+ * Invoking the JavaScript file through `process.execPath` avoids the `.cmd`
861
+ * shim and command shell that package managers create on Windows.
821
862
  */
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;
863
+ function resolveWranglerBin(root, resolvePackageJson = (projectRoot) => {
864
+ try {
865
+ return createRequire(path.join(projectRoot, "package.json")).resolve("wrangler/package.json");
866
+ } catch {
867
+ return findInNodeModules(projectRoot, "wrangler/package.json");
831
868
  }
832
- return path.join(root, "node_modules", ...candidates[0].split("/"));
869
+ }) {
870
+ const packageJsonPath = resolvePackageJson(root);
871
+ if (packageJsonPath) {
872
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
873
+ const bin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.wrangler;
874
+ if (bin) return path.resolve(path.dirname(packageJsonPath), bin);
875
+ }
876
+ return path.join(root, "node_modules", "wrangler", "bin", "wrangler.js");
833
877
  }
834
- function runWranglerDeploy(root, options) {
878
+ function buildNodeCliInvocation(scriptPath, args, nodeExecutable = process.execPath) {
879
+ return {
880
+ file: nodeExecutable,
881
+ args: [scriptPath, ...args]
882
+ };
883
+ }
884
+ function buildWranglerInvocation(root, options, nodeExecutable = process.execPath) {
835
885
  const wranglerBin = resolveWranglerBin(root);
886
+ const { args, env } = buildWranglerDeployArgs(options);
887
+ return {
888
+ ...buildNodeCliInvocation(wranglerBin, args, nodeExecutable),
889
+ env
890
+ };
891
+ }
892
+ function runWranglerDeploy(root, options, execute = execFileSync) {
836
893
  const execOpts = {
837
894
  cwd: root,
838
895
  stdio: "pipe",
839
896
  encoding: "utf-8",
840
- shell: process.platform === "win32"
897
+ shell: false
841
898
  };
842
- const { args, env } = buildWranglerDeployArgs(options);
899
+ const { file, args, env } = buildWranglerInvocation(root, options);
843
900
  if (env) console.log(`\n Deploying to env: ${env}...`);
844
901
  else console.log("\n Deploying to production...");
845
- const output = execFileSync(wranglerBin, args, execOpts);
902
+ const output = execute(file, args, execOpts);
846
903
  const urlMatch = output.match(/https:\/\/[^\s]+\.workers\.dev[^\s]*/);
847
904
  const deployedUrl = urlMatch ? urlMatch[0] : null;
848
905
  if (output.trim()) for (const line of output.trim().split("\n")) console.log(` ${line}`);
849
906
  return deployedUrl ?? "(URL not detected in wrangler output)";
850
907
  }
908
+ const VINEXT_PREGEN_START = "/* __VINEXT_PREGENERATED_CONCRETE_PATHS_START__ */";
909
+ const VINEXT_PREGEN_END = "/* __VINEXT_PREGENERATED_CONCRETE_PATHS_END__ */";
910
+ const VINEXT_PREGEN_RE = new RegExp(`${escapeRegExp(VINEXT_PREGEN_START)}[\\s\\S]*?${escapeRegExp(VINEXT_PREGEN_END)}\\n?`, "g");
911
+ /**
912
+ * Read the prerender manifest and inject pregenerated concrete paths into the
913
+ * App Router Worker bundle so the PPR fallback-shell guard is populated at
914
+ * module init time without calling `seedMemoryCacheFromPrerender`.
915
+ *
916
+ * The paths are injected as `globalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS`
917
+ * wrapped in replaceable marker comments, and consumed by
918
+ * `initPregeneratedPathsFromGlobals` in the generated RSC entry.
919
+ *
920
+ * Idempotent: repeated calls strip the previous injection before writing the
921
+ * new one. If the manifest is missing, corrupt, or empty, any prior injection
922
+ * is stripped and nothing new is written — failing closed to empty.
923
+ */
924
+ function injectPregeneratedConcretePaths(root) {
925
+ const workerEntry = path.resolve(root, "dist", "server", "index.js");
926
+ if (!fs.existsSync(workerEntry)) return;
927
+ let code = fs.readFileSync(workerEntry, "utf-8");
928
+ code = code.replace(VINEXT_PREGEN_RE, "");
929
+ const table = buildPregeneratedConcretePathTable(readPrerenderManifest(path.join(root, "dist", "server", "vinext-prerender.json")) ?? {});
930
+ if (table.length > 0) code = `${VINEXT_PREGEN_START}\nglobalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS = ${JSON.stringify(table)};\n${VINEXT_PREGEN_END}\n` + code;
931
+ fs.writeFileSync(workerEntry, code);
932
+ }
851
933
  async function deploy(options) {
934
+ const deployEnv = validateWranglerEnvName(options.env || (options.preview ? "preview" : "production"));
852
935
  const root = path.resolve(options.root);
853
936
  loadDotenv({
854
937
  root,
@@ -905,7 +988,7 @@ async function deploy(options) {
905
988
  console.log("\n Dry run complete. Files generated but no build or deploy performed.\n");
906
989
  return;
907
990
  }
908
- if (!options.skipBuild) await runBuild(info, options.env || (options.preview ? "preview" : void 0));
991
+ if (!options.skipBuild) await runBuild(info, deployEnv === "production" && !options.env ? void 0 : deployEnv);
909
992
  else console.log("\n Skipping build (--skip-build)");
910
993
  {
911
994
  const nextConfig = await resolveNextConfig(await loadNextConfig(info.root), info.root);
@@ -922,6 +1005,7 @@ async function deploy(options) {
922
1005
  concurrency: options.prerenderConcurrency
923
1006
  });
924
1007
  }
1008
+ injectPregeneratedConcretePaths(root);
925
1009
  }
926
1010
  if (options.experimentalTPR) {
927
1011
  console.log();
@@ -933,13 +1017,10 @@ async function deploy(options) {
933
1017
  });
934
1018
  if (tprResult.skipped) console.log(` TPR: Skipped (${tprResult.skipped})`);
935
1019
  }
936
- const url = runWranglerDeploy(root, {
937
- preview: options.preview ?? false,
938
- env: options.env
939
- });
1020
+ const url = runWranglerDeploy(root, { env: deployEnv === "production" && !options.env ? void 0 : deployEnv });
940
1021
  console.log("\n ─────────────────────────────────────────");
941
1022
  console.log(` Deployed to: ${url}`);
942
1023
  console.log(" ─────────────────────────────────────────\n");
943
1024
  }
944
1025
  //#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 };
1026
+ 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