vinext 0.1.3 → 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 (82) 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.js +1 -0
  4. package/dist/client/pages-router-link-navigation.d.ts +33 -7
  5. package/dist/client/pages-router-link-navigation.js +32 -2
  6. package/dist/client/vinext-next-data.js +2 -0
  7. package/dist/config/config-matchers.d.ts +11 -1
  8. package/dist/config/config-matchers.js +14 -2
  9. package/dist/config/tsconfig-paths.js +14 -1
  10. package/dist/deploy.js +20 -13
  11. package/dist/entries/app-rsc-entry.js +3 -2
  12. package/dist/entries/pages-client-entry.js +14 -13
  13. package/dist/entries/pages-server-entry.js +6 -26
  14. package/dist/index.js +217 -40
  15. package/dist/plugins/dynamic-preload-metadata.js +2 -4
  16. package/dist/plugins/fonts.js +5 -4
  17. package/dist/plugins/strip-server-exports.d.ts +9 -7
  18. package/dist/plugins/strip-server-exports.js +493 -46
  19. package/dist/routing/app-route-graph.js +2 -2
  20. package/dist/server/app-browser-action-result.js +1 -1
  21. package/dist/server/app-browser-entry.js +8 -1
  22. package/dist/server/app-browser-navigation-controller.d.ts +1 -1
  23. package/dist/server/app-browser-state.d.ts +1 -1
  24. package/dist/server/app-browser-state.js +19 -11
  25. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  26. package/dist/server/app-pages-bridge.d.ts +5 -1
  27. package/dist/server/app-pages-bridge.js +5 -13
  28. package/dist/server/app-rsc-handler.d.ts +3 -0
  29. package/dist/server/app-rsc-handler.js +51 -15
  30. package/dist/server/app-rsc-route-matching.js +6 -2
  31. package/dist/server/app-server-action-execution.js +5 -2
  32. package/dist/server/app-ssr-entry.js +1 -29
  33. package/dist/server/before-interactive-head.d.ts +17 -0
  34. package/dist/server/before-interactive-head.js +35 -0
  35. package/dist/server/csp.js +1 -4
  36. package/dist/server/dev-server.js +81 -36
  37. package/dist/server/middleware-matcher.js +12 -3
  38. package/dist/server/middleware-runtime.d.ts +3 -4
  39. package/dist/server/middleware-runtime.js +2 -0
  40. package/dist/server/navigation-planner.d.ts +3 -12
  41. package/dist/server/navigation-planner.js +24 -0
  42. package/dist/server/navigation-trace.d.ts +2 -1
  43. package/dist/server/navigation-trace.js +1 -0
  44. package/dist/server/operation-token.d.ts +40 -0
  45. package/dist/server/operation-token.js +85 -0
  46. package/dist/server/pages-data-route.d.ts +1 -1
  47. package/dist/server/pages-data-route.js +7 -4
  48. package/dist/server/pages-dev-module-url.d.ts +4 -0
  49. package/dist/server/pages-dev-module-url.js +15 -0
  50. package/dist/server/pages-document-initial-props.d.ts +4 -15
  51. package/dist/server/pages-document-initial-props.js +27 -56
  52. package/dist/server/pages-i18n.js +2 -2
  53. package/dist/server/pages-page-data.js +3 -1
  54. package/dist/server/pages-page-handler.js +3 -1
  55. package/dist/server/pages-page-response.d.ts +2 -0
  56. package/dist/server/pages-page-response.js +4 -4
  57. package/dist/server/pages-readiness.js +1 -1
  58. package/dist/server/pages-request-pipeline.d.ts +7 -7
  59. package/dist/server/pages-request-pipeline.js +63 -21
  60. package/dist/server/prod-server.d.ts +3 -1
  61. package/dist/server/prod-server.js +41 -10
  62. package/dist/server/static-file-cache.js +16 -4
  63. package/dist/shims/before-interactive-context.d.ts +14 -3
  64. package/dist/shims/document.d.ts +15 -20
  65. package/dist/shims/document.js +5 -8
  66. package/dist/shims/image.js +9 -2
  67. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +6 -7
  68. package/dist/shims/internal/pages-data-fetch-dedup.js +67 -14
  69. package/dist/shims/internal/pages-data-target.js +1 -1
  70. package/dist/shims/link.js +37 -16
  71. package/dist/shims/metadata.js +4 -4
  72. package/dist/shims/navigation.js +2 -0
  73. package/dist/shims/router.d.ts +6 -2
  74. package/dist/shims/router.js +99 -20
  75. package/dist/shims/script.js +8 -4
  76. package/dist/utils/has-trailing-comma.d.ts +24 -0
  77. package/dist/utils/has-trailing-comma.js +62 -0
  78. package/dist/utils/text-stream.d.ts +1 -1
  79. package/dist/utils/text-stream.js +2 -2
  80. package/dist/utils/vite-version.d.ts +12 -1
  81. package/dist/utils/vite-version.js +9 -1
  82. package/package.json +1 -1
@@ -7,6 +7,13 @@ type ClientAssetFileNameInfo = {
7
7
  readonly originalFileName?: string;
8
8
  readonly originalFileNames?: readonly string[];
9
9
  };
10
+ type RscFrameworkModuleInfo = {
11
+ importers: string[];
12
+ isEntry: boolean;
13
+ };
14
+ type RscFrameworkManualChunksMeta = {
15
+ getModuleInfo(id: string): RscFrameworkModuleInfo | null;
16
+ };
10
17
  /**
11
18
  * Routes client assets into Next-compatible subtrees: `.css` sources go to
12
19
  * `<assetsDir>/css/`, everything else to `<assetsDir>/media/`. Returned as a
@@ -80,18 +87,20 @@ declare function isRscFrameworkModule(id: string): boolean;
80
87
  * dedicated "framework" chunk in the RSC server build. Returns the bundler-
81
88
  * appropriate shape: rolldown's `codeSplitting` for Vite 8+, Rollup's
82
89
  * `manualChunks` for Vite 7. See {@link RSC_FRAMEWORK_CHUNK_TEST} for the
83
- * motivation (issue #1549).
90
+ * motivation (issue #1549). Framework modules that are only reachable through
91
+ * dynamic imports must stay out of the eager framework chunk (issue #2073).
84
92
  */
85
93
  declare function createRscFrameworkChunkOutputConfig(viteMajorVersion: number): {
86
94
  codeSplitting: {
87
95
  groups: {
88
96
  name: string;
89
97
  test: RegExp;
98
+ entriesAware: boolean;
90
99
  }[];
91
100
  };
92
101
  manualChunks?: undefined;
93
102
  } | {
94
- manualChunks(id: string): string | undefined;
103
+ manualChunks(id: string, meta: RscFrameworkManualChunksMeta): string | undefined;
95
104
  codeSplitting?: undefined;
96
105
  };
97
106
  /**
@@ -33,9 +33,10 @@ function createClientAssetFileNames(assetsDir) {
33
33
  * (node_modules/.pnpm/pkg@ver/node_modules/pkg).
34
34
  */
35
35
  function getPackageName(id) {
36
- const nmIdx = id.lastIndexOf("node_modules/");
36
+ const normalizedId = id.replaceAll("\\", "/");
37
+ const nmIdx = normalizedId.lastIndexOf("node_modules/");
37
38
  if (nmIdx === -1) return null;
38
- const rest = id.slice(nmIdx + 13);
39
+ const rest = normalizedId.slice(nmIdx + 13);
39
40
  if (rest.startsWith("@")) {
40
41
  const parts = rest.split("/");
41
42
  return parts.length >= 2 ? parts[0] + "/" + parts[1] : null;
@@ -153,20 +154,30 @@ function isRscFrameworkModule(id) {
153
154
  const pkg = getPackageName(id);
154
155
  return pkg !== null && FRAMEWORK_PACKAGES.includes(pkg);
155
156
  }
157
+ function isStaticallyReachableFromEntry(id, meta, visited = /* @__PURE__ */ new Set()) {
158
+ if (visited.has(id)) return false;
159
+ visited.add(id);
160
+ const moduleInfo = meta.getModuleInfo(id);
161
+ if (!moduleInfo) return false;
162
+ if (moduleInfo.isEntry) return true;
163
+ return moduleInfo.importers.some((importer) => isStaticallyReachableFromEntry(importer, meta, visited));
164
+ }
156
165
  /**
157
166
  * Output config that isolates React (and the RSC flight runtime) into a
158
167
  * dedicated "framework" chunk in the RSC server build. Returns the bundler-
159
168
  * appropriate shape: rolldown's `codeSplitting` for Vite 8+, Rollup's
160
169
  * `manualChunks` for Vite 7. See {@link RSC_FRAMEWORK_CHUNK_TEST} for the
161
- * motivation (issue #1549).
170
+ * motivation (issue #1549). Framework modules that are only reachable through
171
+ * dynamic imports must stay out of the eager framework chunk (issue #2073).
162
172
  */
163
173
  function createRscFrameworkChunkOutputConfig(viteMajorVersion) {
164
174
  if (viteMajorVersion >= 8) return { codeSplitting: { groups: [{
165
175
  name: "framework",
166
- test: RSC_FRAMEWORK_CHUNK_TEST
176
+ test: RSC_FRAMEWORK_CHUNK_TEST,
177
+ entriesAware: true
167
178
  }] } };
168
- return { manualChunks(id) {
169
- return isRscFrameworkModule(id) ? "framework" : void 0;
179
+ return { manualChunks(id, meta) {
180
+ return isRscFrameworkModule(id) && isStaticallyReachableFromEntry(id, meta) ? "framework" : void 0;
170
181
  } };
171
182
  }
172
183
  /**
@@ -659,6 +659,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
659
659
  for (const params of paramSets) {
660
660
  if (params === null || params === void 0) throw new Error(`generateStaticParams() for ${route.pattern} returned an entry with no params object.`);
661
661
  const urlPath = buildUrlFromParams(route.pattern, params);
662
+ if (queuedRouteUrls.has(urlPath)) continue;
662
663
  queuedRouteUrls.add(urlPath);
663
664
  urlsToRender.push({
664
665
  urlPath,
@@ -3,23 +3,49 @@ type PagesRouterLinkTransitionOptions = {
3
3
  scroll?: boolean;
4
4
  shallow?: boolean;
5
5
  locale?: string | false;
6
+ _vinextInterpolateDynamicRoute?: boolean;
6
7
  };
7
8
  type PagesRouterLinkRuntime = {
8
9
  push(url: string, as?: string, options?: PagesRouterLinkTransitionOptions): Promise<boolean>;
9
10
  replace(url: string, as?: string, options?: PagesRouterLinkTransitionOptions): Promise<boolean>;
10
11
  };
12
+ type PagesRouterLinkNavigation = {
13
+ href: string;
14
+ replace: boolean;
15
+ scroll: boolean;
16
+ shallow?: boolean;
17
+ locale?: string | false;
18
+ interpolateDynamicRoute?: boolean;
19
+ };
20
+ declare function resolvePagesRouterQueryOnlyHref(href: string, {
21
+ asPath,
22
+ basePath,
23
+ fallbackHref,
24
+ locales
25
+ }: {
26
+ asPath?: string;
27
+ basePath: string;
28
+ fallbackHref: string;
29
+ locales?: readonly string[];
30
+ }): string;
11
31
  declare function navigatePagesRouterLink(router: PagesRouterLinkRuntime, {
12
32
  href,
13
33
  replace,
14
34
  scroll,
15
35
  shallow,
16
- locale
36
+ locale,
37
+ interpolateDynamicRoute
38
+ }: PagesRouterLinkNavigation): Promise<void>;
39
+ declare function navigatePagesRouterLinkWithFallback({
40
+ router,
41
+ loadRouter,
42
+ navigation,
43
+ fallback
17
44
  }: {
18
- href: string;
19
- replace: boolean;
20
- scroll: boolean;
21
- shallow?: boolean;
22
- locale?: string | false;
45
+ router?: PagesRouterLinkRuntime;
46
+ loadRouter: () => Promise<PagesRouterLinkRuntime | undefined>;
47
+ navigation: PagesRouterLinkNavigation;
48
+ fallback: () => void;
23
49
  }): Promise<void>;
24
50
  //#endregion
25
- export { navigatePagesRouterLink };
51
+ export { navigatePagesRouterLink, navigatePagesRouterLinkWithFallback, resolvePagesRouterQueryOnlyHref };
@@ -1,12 +1,42 @@
1
+ import { stripBasePath } from "../utils/base-path.js";
2
+ import { getLocalePathPrefix } from "../utils/domain-locale.js";
1
3
  //#region src/client/pages-router-link-navigation.ts
2
- async function navigatePagesRouterLink(router, { href, replace, scroll, shallow, locale }) {
4
+ function resolvePagesRouterQueryOnlyHref(href, { asPath, basePath, fallbackHref, locales }) {
5
+ if (!href.startsWith("?")) return href;
6
+ try {
7
+ const fallbackUrl = new URL(fallbackHref);
8
+ const base = new URL(asPath ?? `${stripBasePath(fallbackUrl.pathname, basePath)}${fallbackUrl.search}${fallbackUrl.hash}`, "http://vinext.local");
9
+ const locale = getLocalePathPrefix(base.pathname, locales);
10
+ if (locale) base.pathname = base.pathname.slice(locale.length + 1) || "/";
11
+ const resolved = new URL(href, base);
12
+ return resolved.href.slice(resolved.origin.length);
13
+ } catch {
14
+ return href;
15
+ }
16
+ }
17
+ async function navigatePagesRouterLink(router, { href, replace, scroll, shallow, locale, interpolateDynamicRoute = false }) {
3
18
  const routerOptions = {
4
19
  scroll,
5
20
  locale
6
21
  };
22
+ if (interpolateDynamicRoute) routerOptions._vinextInterpolateDynamicRoute = true;
7
23
  if (shallow !== void 0) routerOptions.shallow = shallow;
8
24
  if (replace) await router.replace(href, void 0, routerOptions);
9
25
  else await router.push(href, void 0, routerOptions);
10
26
  }
27
+ async function navigatePagesRouterLinkWithFallback({ router, loadRouter, navigation, fallback }) {
28
+ let pagesRouter = router;
29
+ if (!pagesRouter) try {
30
+ pagesRouter = await loadRouter();
31
+ } catch {
32
+ fallback();
33
+ return;
34
+ }
35
+ if (!pagesRouter) {
36
+ fallback();
37
+ return;
38
+ }
39
+ await navigatePagesRouterLink(pagesRouter, navigation);
40
+ }
11
41
  //#endregion
12
- export { navigatePagesRouterLink };
42
+ export { navigatePagesRouterLink, navigatePagesRouterLinkWithFallback, resolvePagesRouterQueryOnlyHref };
@@ -1,6 +1,8 @@
1
1
  import { isUnknownRecord } from "../utils/record.js";
2
2
  //#region src/client/vinext-next-data.ts
3
3
  function extractVinextNextDataJson(html) {
4
+ const canonical = /<script\b(?=[^>]*\bid=["']__NEXT_DATA__["'])(?=[^>]*\btype=["']application\/json["'])[^>]*>([\s\S]*?)<\/script>/.exec(html);
5
+ if (canonical) return canonical[1];
4
6
  const assignment = /<script(?:\s[^>]*)?>\s*window\.__NEXT_DATA__\s*=\s*/.exec(html);
5
7
  if (!assignment || assignment.index === void 0) return null;
6
8
  let start = assignment.index + assignment[0].length;
@@ -148,6 +148,16 @@ declare function matchRedirect(pathname: string, redirects: NextRedirect[], ctx:
148
148
  * when evaluating rewrites, so this parameter is required.
149
149
  */
150
150
  declare function matchRewrite(pathname: string, rewrites: NextRewrite[], ctx: RequestContext, basePathState?: BasePathMatchState): string | null;
151
+ /**
152
+ * Check whether a rewrite source can match a pathname without evaluating its
153
+ * request-dependent `has` / `missing` conditions.
154
+ *
155
+ * Dev uses this only as a conservative preflight before middleware runs. The
156
+ * conditions may become true after middleware overrides request headers, so
157
+ * evaluating them against the original request would incorrectly skip the
158
+ * Pages request pipeline for file-looking paths.
159
+ */
160
+ declare function matchesRewriteSource(pathname: string, rewrite: NextRewrite, basePathState?: BasePathMatchState): boolean;
151
161
  /**
152
162
  * Sanitize a redirect/rewrite destination to collapse protocol-relative URLs.
153
163
  *
@@ -244,4 +254,4 @@ declare function applyLocaleToRoutes<T extends NextRedirect | NextRewrite>(route
244
254
  trailingSlash?: boolean;
245
255
  }): T[];
246
256
  //#endregion
247
- export { BasePathMatchState, RequestContext, applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, normalizeHost, parseCookies, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };
257
+ export { BasePathMatchState, RequestContext, applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, matchesRewriteSource, normalizeHost, parseCookies, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };
@@ -268,7 +268,7 @@ function isSafeRegex(pattern) {
268
268
  */
269
269
  function safeRegExp(pattern, flags) {
270
270
  if (!isSafeRegex(pattern)) {
271
- console.warn(`[vinext] Ignoring potentially unsafe regex pattern (ReDoS risk): ${pattern}\n Patterns with nested quantifiers (e.g. (a+)+) can cause catastrophic backtracking.\n Simplify the pattern to avoid nested repetition.`);
271
+ console.warn(`[vinext] Rejecting potentially unsafe regex pattern (ReDoS risk): ${pattern}\n Patterns with nested quantifiers (e.g. (a+)+) can cause catastrophic backtracking.\n Simplify the pattern to avoid nested repetition.`);
272
272
  return null;
273
273
  }
274
274
  try {
@@ -718,6 +718,18 @@ function matchRewrite(pathname, rewrites, ctx, basePathState = _BASEPATH_DEFAULT
718
718
  return null;
719
719
  }
720
720
  /**
721
+ * Check whether a rewrite source can match a pathname without evaluating its
722
+ * request-dependent `has` / `missing` conditions.
723
+ *
724
+ * Dev uses this only as a conservative preflight before middleware runs. The
725
+ * conditions may become true after middleware overrides request headers, so
726
+ * evaluating them against the original request would incorrectly skip the
727
+ * Pages request pipeline for file-looking paths.
728
+ */
729
+ function matchesRewriteSource(pathname, rewrite, basePathState = _BASEPATH_DEFAULT) {
730
+ return shouldEvaluateRule(rewrite.basePath, basePathState) && matchConfigPattern(pathname, rewrite.source) !== null;
731
+ }
732
+ /**
721
733
  * Substitute all matched route params into a redirect/rewrite destination.
722
734
  *
723
735
  * Handles repeated params (e.g. `/api/:id/:id`) and catch-all suffix forms
@@ -1031,4 +1043,4 @@ function applyLocaleToRoutes(routes, i18n, type, options = {}) {
1031
1043
  return out;
1032
1044
  }
1033
1045
  //#endregion
1034
- export { applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, normalizeHost, parseCookies, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };
1046
+ export { applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, matchesRewriteSource, normalizeHost, parseCookies, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };
@@ -38,6 +38,19 @@ function resolveTsconfigPathCandidate(candidate) {
38
38
  for (const item of candidates) if (fs.existsSync(item) && fs.statSync(item).isFile()) return item;
39
39
  return null;
40
40
  }
41
+ /**
42
+ * Normalize a tsconfig `extends` field into a list of specifier strings.
43
+ *
44
+ * TypeScript 5.0+ allows `extends` to be either a string or an array of
45
+ * strings. Matches Next.js's handling in
46
+ * packages/next/src/build/next-config-ts/transpile-config.ts, where parents
47
+ * are iterated in order and later entries override earlier ones.
48
+ */
49
+ function normalizeExtends(extendsField) {
50
+ if (typeof extendsField === "string") return [extendsField];
51
+ if (Array.isArray(extendsField)) return extendsField.filter((value) => typeof value === "string");
52
+ return [];
53
+ }
41
54
  function resolveTsconfigExtends(configPath, specifier) {
42
55
  const fromDir = path.dirname(configPath);
43
56
  if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("\\")) return resolveTsconfigPathCandidate(path.resolve(fromDir, specifier));
@@ -87,7 +100,7 @@ function loadResolutionFromTsconfigFile(configPath, seen) {
87
100
  }
88
101
  if (!parsed) return emptyResolution();
89
102
  let resolution = emptyResolution();
90
- const extendsList = typeof parsed.extends === "string" ? [parsed.extends] : [];
103
+ const extendsList = normalizeExtends(parsed.extends);
91
104
  for (const extendsSpecifier of extendsList) {
92
105
  const extendedPath = resolveTsconfigExtends(configPath, extendsSpecifier);
93
106
  if (extendedPath) {
package/dist/deploy.js CHANGED
@@ -374,7 +374,7 @@ function generatePagesRouterWorkerEntry() {
374
374
  * Cloudflare Worker entry point -- auto-generated by vinext deploy.
375
375
  * Edit freely or delete to regenerate on next deploy.
376
376
  */
377
- import { runPagesRequest, wrapMiddlewareWithBasePath } from "vinext/server/pages-request-pipeline";
377
+ import { fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath } from "vinext/server/pages-request-pipeline";
378
378
  import type { PagesPipelineDeps } from "vinext/server/pages-request-pipeline";
379
379
  import { handleImageOptimization, DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath } from "vinext/server/image-optimization";
380
380
  import type { ImageConfig } from "vinext/server/image-optimization";
@@ -384,7 +384,7 @@ import { assetPrefixPathname, isNextStaticPath } from "vinext/utils/asset-prefix
384
384
  import { hasBasePath, stripBasePath } from "vinext/utils/base-path";
385
385
 
386
386
  // @ts-expect-error -- virtual module resolved by vinext at build time
387
- import { renderPage, handleApiRoute, runMiddleware, vinextConfig, matchPageRoute } from "virtual:vinext-server-entry";
387
+ import { renderPage, handleApiRoute, runMiddleware, normalizeDataRequest, vinextConfig, matchPageRoute } from "virtual:vinext-server-entry";
388
388
  // @ts-expect-error -- virtual module resolved by vinext at build time
389
389
  import { registerConfiguredCacheAdapters } from "virtual:vinext-cache-adapters";
390
390
 
@@ -448,11 +448,6 @@ export default {
448
448
  return notFoundStaticAssetResponse();
449
449
  }
450
450
 
451
- // Capture x-nextjs-data before filterInternalHeaders strips it -- the
452
- // middleware redirect protocol needs to know whether the inbound request
453
- // was a _next/data fetch to emit x-nextjs-redirect instead of a 3xx.
454
- const isDataRequest = request.headers.get("x-nextjs-data") === "1";
455
-
456
451
  // Strip internal headers from inbound requests so they cannot be
457
452
  // forged to influence routing or impersonate internal state.
458
453
  // Request.headers is immutable in Workers, so build a clean copy.
@@ -476,6 +471,14 @@ export default {
476
471
  }
477
472
  }
478
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
+
479
482
  // ── Image optimization via Cloudflare Images binding ──────────
480
483
  // Checked after basePath stripping so /<basePath>/_next/image works.
481
484
  if (isImageOptimizationPath(pathname)) {
@@ -504,12 +507,8 @@ export default {
504
507
  configRewrites,
505
508
  configHeaders,
506
509
  hadBasePath,
507
- // The worker adapter does not do _next/data URL normalization (no
508
- // buildId available at request time). isDataReq is used by the pipeline
509
- // only for renderPage options and shouldDeferErrorPageOnMiss -- false
510
- // is correct here.
511
- isDataReq: false,
512
- isDataRequest,
510
+ isDataReq,
511
+ isDataRequest: isDataReq,
513
512
  ctx,
514
513
  matchPageRoute: typeof matchPageRoute === "function" ? matchPageRoute : null,
515
514
  // Pass the original (pre-basePath-stripping) URL to middleware so that
@@ -527,6 +526,14 @@ export default {
527
526
  handleApi: typeof handleApiRoute === "function"
528
527
  ? (req, apiUrl) => handleApiRoute(req, apiUrl, ctx)
529
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
+ },
530
537
  };
531
538
 
532
539
  const result = await runPagesRequest(request, deps);
@@ -452,6 +452,7 @@ ${rootParamNameEntries.join("\n")}
452
452
 
453
453
  export default __createAppRscHandler({
454
454
  basePath: __basePath,
455
+ buildId: process.env.__VINEXT_BUILD_ID ?? null,
455
456
  ensureRouteLoaded: __ensureRouteLoaded,
456
457
  clearRequestContext() {
457
458
  __clearRequestContext();
@@ -867,9 +868,9 @@ export default __createAppRscHandler({
867
868
  const __isEdge = route ? __isEdgeRuntime(__resolveAppPageSegmentConfig({ layouts: route.layouts, page: route.page }).runtime) : false;
868
869
  return __fallbackRenderer.renderNotFound(route, isRscRequest, request, matchedParams, scriptNonce, middlewareContext, { isEdgeRuntime: __isEdge });
869
870
  },
870
- ${hasPagesDir ? `async renderPagesFallback({ allowRscDocumentFallback, appRouteMatch, isRscRequest, matchKind, middlewareContext, pathname, request, url }) {
871
+ ${hasPagesDir ? `async renderPagesFallback({ allowRscDocumentFallback, appRouteMatch, isDataRequest, isRscRequest, matchKind, middlewareContext, pathname, pagesDataRequest, request, url }) {
871
872
  return __renderPagesFallback(
872
- { allowRscDocumentFallback, appRouteMatch, isRscRequest, matchKind, middlewareContext, pathname, request, url },
873
+ { allowRscDocumentFallback, appRouteMatch, isDataRequest, isRscRequest, matchKind, middlewareContext, pathname, pagesDataRequest, request, url },
873
874
  {
874
875
  loadPagesEntry() {
875
876
  return import.meta.viteRsc.loadModule("ssr", "index");
@@ -50,19 +50,10 @@ async function generateClientEntry(pagesDir, nextConfig, fileMatcher, options =
50
50
  import "vinext/instrumentation-client";
51
51
  import React from "react";
52
52
  import { hydrateRoot } from "react-dom/client";
53
- // Statically import next/router as the very first vinext shim so that
54
- // (a) installWindowNext runs at top-level — \`window.next.router\` is
55
- // available to test harnesses and third-party scripts BEFORE
56
- // hydrate() resolves (see .nextjs-ref/packages/next/src/client/next.ts
57
- // line 13, which also sets window.next as a top-level side effect),
58
- // and (b) the popstate handler is registered before
59
- // installPagesRouterRuntime() runs, removing the race window where a
60
- // popstate event could fire between hydration and runtime install.
61
- //
62
- // Mirrors Next.js's bootstrap order: client/next.ts statically imports
63
- // from './' before calling initialize/hydrate, so window.next is set up
64
- // before any async work.
65
- import Router, { wrapWithRouterContext } from "next/router";
53
+ import Router, {
54
+ wrapWithRouterContext,
55
+ _initializePagesRouterReadyFromNextData,
56
+ } from "next/router";
66
57
 
67
58
  const pageLoaders = {
68
59
  ${loaderEntries.join(",\n")}
@@ -110,6 +101,14 @@ window.__VINEXT_LINK_PREFETCH_ROUTES__ = ${JSON.stringify(appPrefetchRoutes)};
110
101
  window.__VINEXT_PAGES_LINK_PREFETCH_ROUTES__ = ${JSON.stringify(pagesPrefetchRoutes)};
111
102
  window.__VINEXT_CLIENT_REWRITES__ = ${JSON.stringify(nextConfig.rewrites)};
112
103
 
104
+ const nextDataElement = document.getElementById("__NEXT_DATA__");
105
+ if (nextDataElement?.textContent) {
106
+ window.__NEXT_DATA__ = JSON.parse(nextDataElement.textContent);
107
+ window.__VINEXT_LOCALE__ = window.__NEXT_DATA__.locale;
108
+ window.__VINEXT_LOCALES__ = window.__NEXT_DATA__.locales;
109
+ window.__VINEXT_DEFAULT_LOCALE__ = window.__NEXT_DATA__.defaultLocale;
110
+ }
111
+
113
112
  async function hydrate() {
114
113
  const nextData = window.__NEXT_DATA__;
115
114
  if (!nextData) {
@@ -117,6 +116,8 @@ async function hydrate() {
117
116
  return;
118
117
  }
119
118
 
119
+ _initializePagesRouterReadyFromNextData(nextData);
120
+
120
121
  let hydrateRootOptions;
121
122
  if (import.meta.env.DEV) {
122
123
  const overlay = await import("vinext/dev-error-overlay");
@@ -100,42 +100,19 @@ if (typeof _instrumentation.onRequestError === "function") {
100
100
  const middlewareImportCode = middlewarePath ? `import * as middlewareModule from ${JSON.stringify(normalizePathSeparators(middlewarePath))};` : "";
101
101
  const middlewareExportCode = middlewarePath ? `
102
102
  export async function runMiddleware(request, ctx, options) {
103
- // Auto-detect /_next/data/<buildId>/<page>.json requests so user-written
104
- // worker entries don't need to know about the data endpoint protocol.
105
- // Mismatched buildId → JSON 404 short-circuit. Matched → middleware sees
106
- // the normalized page path via the request URL, and the worker sees the
107
- // normalized URL via result.rewriteUrl (if middleware didn't already
108
- // rewrite to something else).
109
- const __dataNorm = __normalizePagesDataRequest(request, buildId);
110
- if (__dataNorm.notFoundResponse) {
111
- return { continue: false, response: __dataNorm.notFoundResponse };
112
- }
113
- const __result = await __runGeneratedMiddleware({
103
+ return __runGeneratedMiddleware({
114
104
  basePath: vinextConfig.basePath,
115
105
  ctx,
116
106
  i18nConfig,
117
- isDataRequest: options?.isDataRequest === true || __dataNorm.isDataReq,
107
+ isDataRequest: options?.isDataRequest === true,
118
108
  isProxy: ${JSON.stringify(isProxyFile(middlewarePath))},
119
109
  module: middlewareModule,
120
- request: __dataNorm.request,
110
+ request,
121
111
  trailingSlash: vinextConfig.trailingSlash,
122
112
  });
123
- if (__dataNorm.isDataReq && __result.continue && !__result.rewriteUrl && !__result.redirectUrl) {
124
- return { ...__result, rewriteUrl: __dataNorm.normalizedPathname + __dataNorm.search };
125
- }
126
- return __result;
127
113
  }
128
114
  ` : `
129
115
  export async function runMiddleware(request) {
130
- // Even without user middleware, the data-endpoint URL must be normalized so
131
- // the worker pipeline sees the page path. Mismatched buildId → JSON 404.
132
- const __dataNorm = __normalizePagesDataRequest(request, buildId);
133
- if (__dataNorm.notFoundResponse) {
134
- return { continue: false, response: __dataNorm.notFoundResponse };
135
- }
136
- if (__dataNorm.isDataReq) {
137
- return { continue: true, rewriteUrl: __dataNorm.normalizedPathname + __dataNorm.search };
138
- }
139
116
  return { continue: true };
140
117
  }
141
118
  `;
@@ -181,6 +158,9 @@ const i18nConfig = ${i18nConfigJson};
181
158
  // match _next/data requests against the embedded buildId without needing
182
159
  // to load next.config.js at runtime.
183
160
  export const buildId = ${buildIdJson};
161
+ export function normalizeDataRequest(request) {
162
+ return __normalizePagesDataRequest(request, buildId);
163
+ }
184
164
  const __hasMiddleware = ${JSON.stringify(Boolean(middlewarePath))};
185
165
 
186
166
  // Full resolved config for production server (embedded at build time)