@qzsy/vinext 0.1.12 → 0.1.122

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 (77) hide show
  1. package/dist/check.d.ts +8 -0
  2. package/dist/check.js +20 -9
  3. package/dist/cli.js +2 -2
  4. package/dist/deploy.js +10 -11
  5. package/dist/entries/app-rsc-entry.js +37 -8
  6. package/dist/entries/app-rsc-manifest.js +8 -0
  7. package/dist/entries/pages-client-entry.js +1 -0
  8. package/dist/entries/pages-server-entry.js +1 -0
  9. package/dist/index.js +12 -8
  10. package/dist/init.js +2 -1
  11. package/dist/plugins/middleware-server-only.d.ts +8 -6
  12. package/dist/plugins/middleware-server-only.js +8 -7
  13. package/dist/routing/app-route-graph.d.ts +6 -2
  14. package/dist/routing/app-route-graph.js +60 -12
  15. package/dist/routing/app-router.d.ts +5 -0
  16. package/dist/routing/app-router.js +5 -0
  17. package/dist/routing/file-matcher.d.ts +5 -0
  18. package/dist/routing/file-matcher.js +7 -1
  19. package/dist/server/app-browser-history-controller.d.ts +2 -1
  20. package/dist/server/app-browser-history-controller.js +6 -2
  21. package/dist/server/app-fallback-renderer.d.ts +1 -1
  22. package/dist/server/app-fallback-renderer.js +2 -1
  23. package/dist/server/app-page-boundary-render.d.ts +1 -0
  24. package/dist/server/app-page-boundary-render.js +12 -3
  25. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  26. package/dist/server/app-page-cache-finalizer.js +8 -2
  27. package/dist/server/app-page-dispatch.d.ts +11 -3
  28. package/dist/server/app-page-dispatch.js +54 -15
  29. package/dist/server/app-page-element-builder.d.ts +5 -1
  30. package/dist/server/app-page-element-builder.js +55 -19
  31. package/dist/server/app-page-head.d.ts +12 -0
  32. package/dist/server/app-page-head.js +42 -19
  33. package/dist/server/app-page-params.d.ts +2 -1
  34. package/dist/server/app-page-params.js +8 -1
  35. package/dist/server/app-page-probe.d.ts +1 -0
  36. package/dist/server/app-page-probe.js +1 -1
  37. package/dist/server/app-page-render.d.ts +4 -1
  38. package/dist/server/app-page-render.js +8 -3
  39. package/dist/server/app-page-request.d.ts +8 -1
  40. package/dist/server/app-page-request.js +23 -11
  41. package/dist/server/app-page-route-wiring.d.ts +6 -1
  42. package/dist/server/app-page-route-wiring.js +30 -8
  43. package/dist/server/app-page-search-params-observation.d.ts +4 -2
  44. package/dist/server/app-page-search-params-observation.js +11 -7
  45. package/dist/server/app-route-handler-dispatch.js +1 -0
  46. package/dist/server/app-route-handler-execution.js +2 -1
  47. package/dist/server/app-route-module-loader.d.ts +2 -0
  48. package/dist/server/app-route-module-loader.js +1 -0
  49. package/dist/server/app-router-entry.js +7 -6
  50. package/dist/server/app-rsc-errors.js +7 -1
  51. package/dist/server/app-rsc-handler.js +4 -1
  52. package/dist/server/app-rsc-route-matching.d.ts +7 -0
  53. package/dist/server/app-rsc-route-matching.js +36 -3
  54. package/dist/server/app-segment-config.d.ts +1 -0
  55. package/dist/server/app-segment-config.js +32 -2
  56. package/dist/server/app-server-action-execution.d.ts +4 -0
  57. package/dist/server/app-server-action-execution.js +41 -10
  58. package/dist/server/app-static-generation.d.ts +1 -0
  59. package/dist/server/app-static-generation.js +1 -0
  60. package/dist/server/headers.d.ts +3 -1
  61. package/dist/server/headers.js +3 -1
  62. package/dist/server/prod-server.js +15 -6
  63. package/dist/server/worker-utils.d.ts +2 -1
  64. package/dist/server/worker-utils.js +7 -1
  65. package/dist/shims/error-boundary.d.ts +19 -1
  66. package/dist/shims/error-boundary.js +11 -1
  67. package/dist/shims/headers.d.ts +3 -1
  68. package/dist/shims/headers.js +16 -5
  69. package/dist/shims/metadata.d.ts +3 -2
  70. package/dist/shims/metadata.js +8 -4
  71. package/dist/shims/router.js +13 -2
  72. package/dist/typegen.js +6 -5
  73. package/dist/utils/path.d.ts +2 -1
  74. package/dist/utils/path.js +1 -1
  75. package/dist/utils/project.d.ts +4 -0
  76. package/dist/utils/project.js +5 -1
  77. package/package.json +1 -1
package/dist/check.d.ts CHANGED
@@ -59,6 +59,10 @@ type CheckResult = {
59
59
  declare function hasFreeCjsGlobal(content: string): boolean;
60
60
  /**
61
61
  * Scan source files for `import ... from 'next/...'` statements.
62
+ *
63
+ * `root` must be forward-slash: it is passed to `findSourceFiles` (which
64
+ * requires it) and used as the base of `path.posix.relative`, which only yields
65
+ * a canonical relative path when both operands are forward-slash.
62
66
  */
63
67
  declare function scanImports(root: string): CheckItem[];
64
68
  /**
@@ -75,6 +79,10 @@ declare function checkLibraries(root: string): CheckItem[];
75
79
  declare function checkConventions(root: string): CheckItem[];
76
80
  /**
77
81
  * Run the full compatibility check.
82
+ *
83
+ * `root` must be forward-slash — callers normalize it at the CLI entry, and it
84
+ * is forwarded to `scanImports` / `checkConventions` / `findDir`, which build
85
+ * paths with `path.posix.*`.
78
86
  */
79
87
  declare function runCheck(root: string): CheckResult;
80
88
  /**
package/dist/check.js CHANGED
@@ -331,6 +331,11 @@ const LIBRARY_SUPPORT = {
331
331
  };
332
332
  /**
333
333
  * Recursively find all source files in a directory.
334
+ *
335
+ * `dir` must be forward-slash, and the returned paths are forward-slash too:
336
+ * each entry is joined with `path.posix.join`, which only stays canonical when
337
+ * the base already is. This keeps downstream substring checks (e.g.
338
+ * `f.includes("/api/")`) and reported paths consistent across platforms.
334
339
  */
335
340
  function findSourceFiles(dir, extensions = [
336
341
  ".ts",
@@ -343,7 +348,7 @@ function findSourceFiles(dir, extensions = [
343
348
  if (!fs.existsSync(dir)) return results;
344
349
  const entries = fs.readdirSync(dir, { withFileTypes: true });
345
350
  for (const entry of entries) {
346
- const fullPath = normalizePathSeparators(path.join(dir, entry.name));
351
+ const fullPath = path.posix.join(dir, entry.name);
347
352
  if (entry.isDirectory()) {
348
353
  if (entry.name === "node_modules" || entry.name === ".next" || entry.name === "dist" || entry.name === ".git") continue;
349
354
  results.push(...findSourceFiles(fullPath, extensions));
@@ -547,6 +552,10 @@ function hasFreeCjsGlobal(content) {
547
552
  }
548
553
  /**
549
554
  * Scan source files for `import ... from 'next/...'` statements.
555
+ *
556
+ * `root` must be forward-slash: it is passed to `findSourceFiles` (which
557
+ * requires it) and used as the base of `path.posix.relative`, which only yields
558
+ * a canonical relative path when both operands are forward-slash.
550
559
  */
551
560
  function scanImports(root) {
552
561
  const files = findSourceFiles(root);
@@ -564,7 +573,7 @@ function scanImports(root) {
564
573
  if (mod.startsWith("next/") || mod === "next" || mod === "server-only" || mod === "client-only") {
565
574
  const normalized = mod === "next" ? "next" : mod;
566
575
  if (!importUsage.has(normalized)) importUsage.set(normalized, []);
567
- const relFile = normalizePathSeparators(path.relative(root, file));
576
+ const relFile = path.posix.relative(root, file);
568
577
  const usedInFiles = importUsage.get(normalized) ?? [];
569
578
  if (!usedInFiles.includes(relFile)) usedInFiles.push(relFile);
570
579
  }
@@ -780,14 +789,12 @@ function checkLibraries(root) {
780
789
  */
781
790
  function checkConventions(root) {
782
791
  const items = [];
783
- const pagesDir = findDir(root, "pages", path.join("src", "pages"));
784
- const appDirPath = findDir(root, "app", path.join("src", "app"));
785
- const hasPages = pagesDir !== null;
786
- const hasApp = appDirPath !== null;
792
+ const pagesDir = findDir(root, "pages", "src/pages");
793
+ const appDirPath = findDir(root, "app", "src/app");
787
794
  const hasProxy = fs.existsSync(path.join(root, "proxy.ts")) || fs.existsSync(path.join(root, "proxy.js"));
788
795
  const hasMiddleware = fs.existsSync(path.join(root, "middleware.ts")) || fs.existsSync(path.join(root, "middleware.js"));
789
796
  if (pagesDir !== null) {
790
- const isSrc = pagesDir.includes(path.join("src", "pages"));
797
+ const isSrc = pagesDir.includes("src/pages");
791
798
  items.push({
792
799
  name: isSrc ? "Pages Router (src/pages/)" : "Pages Router (pages/)",
793
800
  status: "supported"
@@ -813,7 +820,7 @@ function checkConventions(root) {
813
820
  });
814
821
  }
815
822
  if (appDirPath !== null) {
816
- const isSrc = appDirPath.includes(path.join("src", "app"));
823
+ const isSrc = appDirPath.includes("src/app");
817
824
  items.push({
818
825
  name: isSrc ? "App Router (src/app/)" : "App Router (app/)",
819
826
  status: "supported"
@@ -858,7 +865,7 @@ function checkConventions(root) {
858
865
  name: "middleware.ts (deprecated in Next.js 16)",
859
866
  status: "supported"
860
867
  });
861
- if (!hasPages && !hasApp) items.push({
868
+ if (pagesDir === null && appDirPath === null) items.push({
862
869
  name: "No pages/ or app/ directory found",
863
870
  status: "unsupported",
864
871
  detail: "vinext requires a pages/ or app/ directory"
@@ -913,6 +920,10 @@ function checkConventions(root) {
913
920
  }
914
921
  /**
915
922
  * Run the full compatibility check.
923
+ *
924
+ * `root` must be forward-slash — callers normalize it at the CLI entry, and it
925
+ * is forwarded to `scanImports` / `checkConventions` / `findDir`, which build
926
+ * paths with `path.posix.*`.
916
927
  */
917
928
  function runCheck(root) {
918
929
  const imports = scanImports(root);
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { detectPackageManager, ensureViteConfigCompatibility, hasAppDir, hasViteConfig } from "./utils/project.js";
3
+ import { normalizePathSeparators } from "./utils/path.js";
3
4
  import { formatReport, runCheck } from "./check.js";
4
5
  import { parseArgs } from "./cli-args.js";
5
6
  import { PHASE_PRODUCTION_BUILD } from "./shims/constants.js";
@@ -440,10 +441,9 @@ async function deployCommand() {
440
441
  }
441
442
  async function check() {
442
443
  if (parseArgs(rawArgs).help) return printHelp("check");
443
- const root = process.cwd();
444
444
  console.log(`\n vinext check\n`);
445
445
  console.log(" Scanning project...\n");
446
- const result = runCheck(root);
446
+ const result = runCheck(normalizePathSeparators(process.cwd()));
447
447
  console.log(formatReport(result));
448
448
  }
449
449
  async function typegen() {
package/dist/deploy.js CHANGED
@@ -380,6 +380,7 @@ import { handleImageOptimization, DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isI
380
380
  import type { ImageConfig } from "vinext/server/image-optimization";
381
381
  import { bufferRequestBodyForHeaderClone, cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders, isOpenRedirectShaped } from "vinext/server/request-pipeline";
382
382
  import { notFoundStaticAssetResponse } from "vinext/server/http-error-responses";
383
+ import { finalizeMissingStaticAssetResponse } from "vinext/server/worker-utils";
383
384
  import { assetPrefixPathname, isNextStaticPath } from "vinext/utils/asset-prefix";
384
385
  import { hasBasePath, stripBasePath } from "vinext/utils/base-path";
385
386
 
@@ -438,15 +439,11 @@ export default {
438
439
  return new Response("This page could not be found", { status: 404 });
439
440
  }
440
441
 
441
- // Invalid \`_next/static/*\` paths short-circuit with a plain-text 404
442
- // instead of falling through to renderPage (which would render the full
443
- // HTML 404 page with bootstrap scripts + CSS). Valid assets are served
444
- // by Cloudflare's ASSETS binding BEFORE the worker runs; only misses
445
- // reach this code. Matches Next.js (#1337):
446
- // packages/next/src/server/lib/router-server.ts
447
- if (isNextStaticPath(pathname, basePath, assetPathPrefix)) {
448
- return notFoundStaticAssetResponse();
449
- }
442
+ // Valid assets are served by Cloudflare's ASSETS binding before the
443
+ // worker runs. Missing asset-shaped requests still need to reach
444
+ // middleware so it can rewrite or respond; a final 404 is converted
445
+ // back to Next.js's canonical plain-text static-file response below.
446
+ const missingBuildAsset = isNextStaticPath(pathname, basePath, assetPathPrefix);
450
447
 
451
448
  // Strip internal headers from inbound requests so they cannot be
452
449
  // forged to influence routing or impersonate internal state.
@@ -539,10 +536,12 @@ export default {
539
536
 
540
537
  const result = await runPagesRequest(request, deps);
541
538
  if (result.type === "response") {
542
- return result.response;
539
+ return finalizeMissingStaticAssetResponse(result.response, missingBuildAsset);
543
540
  }
544
541
  // Should not reach here for prod/worker (all callbacks supplied).
545
- return new Response("This page could not be found", { status: 404 });
542
+ return missingBuildAsset
543
+ ? notFoundStaticAssetResponse()
544
+ : new Response("This page could not be found", { status: 404 });
546
545
 
547
546
  } catch (error) {
548
547
  console.error("[vinext] Worker error:", error);
@@ -245,6 +245,11 @@ function __resolveRouteFetchCacheMode(route) {
245
245
  return __resolveAppPageFetchCacheMode({
246
246
  layouts: route.layouts,
247
247
  page: route.page,
248
+ parallelSegments: Object.values(route.slots ?? {}).flatMap((slot) => [
249
+ slot.layout,
250
+ ...(slot.configLayouts ?? []),
251
+ slot.page ?? slot.default,
252
+ ]),
248
253
  });
249
254
  }
250
255
 
@@ -252,6 +257,11 @@ function __resolveRouteDynamicConfig(route) {
252
257
  return __resolveAppPageSegmentConfig({
253
258
  layouts: route.layouts,
254
259
  page: route.page,
260
+ parallelSegments: Object.values(route.slots ?? {}).flatMap((slot) => [
261
+ slot.layout,
262
+ ...(slot.configLayouts ?? []),
263
+ slot.page ?? slot.default,
264
+ ]),
255
265
  }).dynamicConfig ?? null;
256
266
  }
257
267
 
@@ -259,6 +269,11 @@ function __resolveRouteRuntime(route) {
259
269
  return __resolveAppPageSegmentConfig({
260
270
  layouts: route.layouts,
261
271
  page: route.page,
272
+ parallelSegments: Object.values(route.slots ?? {}).flatMap((slot) => [
273
+ slot.layout,
274
+ ...(slot.configLayouts ?? []),
275
+ slot.page ?? slot.default,
276
+ ]),
262
277
  }).runtime ?? null;
263
278
  }
264
279
 
@@ -506,7 +521,12 @@ export default createAppRscHandler({
506
521
  const __segmentConfig = __resolveAppPageSegmentConfig({
507
522
  layouts: route.layouts,
508
523
  page: route.page,
509
- parallelPages: Object.values(route.slots ?? {}).map((slot) => slot.page),
524
+ parallelPages: Object.values(route.slots ?? {}).map((slot) => slot.page ?? slot.default),
525
+ parallelSegments: Object.values(route.slots ?? {}).flatMap((slot) => [
526
+ slot.layout,
527
+ ...(slot.configLayouts ?? []),
528
+ slot.page ?? slot.default,
529
+ ]),
510
530
  });
511
531
  const __generateStaticParams = __resolveAppPageGenerateStaticParamsSources({
512
532
  layouts: route.layouts,
@@ -520,7 +540,7 @@ export default createAppRscHandler({
520
540
  ensureRouteLoaded: __ensureRouteLoaded,
521
541
  clientTraceMetadata: __clientTraceMetadata,
522
542
  reactMaxHeadersLength: __reactMaxHeadersLength,
523
- buildPageElement(targetRoute, targetParams, targetOpts, targetSearchParams, layoutParamAccess) {
543
+ buildPageElement(targetRoute, targetParams, targetOpts, targetSearchParams, layoutParamAccess, buildOptions) {
524
544
  return buildPageElements(targetRoute, targetParams, cleanPathname, {
525
545
  opts: targetOpts,
526
546
  searchParams: targetSearchParams,
@@ -528,6 +548,8 @@ export default createAppRscHandler({
528
548
  request,
529
549
  mountedSlotsHeader,
530
550
  renderMode,
551
+ observeMetadataSearchParamsAccess: buildOptions?.observeMetadataSearchParamsAccess === true,
552
+ observePageSearchParamsAccess: buildOptions?.observePageSearchParamsAccess === true,
531
553
  }, layoutParamAccess, displayPathname);
532
554
  },
533
555
  clientReuseManifest,
@@ -598,7 +620,7 @@ export default createAppRscHandler({
598
620
  route,
599
621
  });
600
622
  },
601
- async probePage() {
623
+ async probePage(probeSearchParams = searchParams) {
602
624
  const __probeIntercept = findIntercept(cleanPathname, interceptionContext);
603
625
  // The intercepting-route page module is lazy (page: null + __pageLoader).
604
626
  // Resolve it before probing so buildAppPageProbes inspects the real page
@@ -613,21 +635,21 @@ export default createAppRscHandler({
613
635
  route,
614
636
  pageComponent: PageComponent,
615
637
  asyncRouteParams: _asyncRouteParams,
616
- searchParams,
638
+ searchParams: probeSearchParams,
617
639
  intercept: __probeIntercept,
618
640
  isRscRequest,
619
641
  matchedParams: params,
620
642
  makeThenableParams,
621
643
  }));
622
644
  },
623
- renderErrorBoundaryPage(renderErr) {
645
+ renderErrorBoundaryPage(renderErr, errorOrigin) {
624
646
  const __activeIntercept = findIntercept(cleanPathname, interceptionContext);
625
647
  return __fallbackRenderer.renderErrorBoundary(route, renderErr, isRscRequest, request, params, scriptNonce, middlewareContext, {
626
648
  isEdgeRuntime: __isEdgeRuntime(__segmentConfig.runtime),
627
649
  sourcePageSegments: __activeIntercept?.slotKey === __SIBLING_PAGE_INTERCEPT_SLOT_KEY
628
650
  ? __activeIntercept.sourcePageSegments
629
651
  : null,
630
- });
652
+ }, errorOrigin);
631
653
  },
632
654
  renderHttpAccessFallbackPage(statusCode, opts, currentMiddlewareContext) {
633
655
  const __activeIntercept = findIntercept(cleanPathname, interceptionContext);
@@ -777,7 +799,7 @@ export default createAppRscHandler({
777
799
  const __actionMatch = matchRoute(cleanPathname);
778
800
  if (__actionMatch) await __ensureRouteLoaded(__actionMatch.route);
779
801
  const __actionIsEdgeRuntime = __actionMatch
780
- ? __isEdgeRuntime(__resolveAppPageSegmentConfig({ layouts: __actionMatch.route.layouts, page: __actionMatch.route.page }).runtime)
802
+ ? __isEdgeRuntime(__resolveRouteRuntime(__actionMatch.route))
781
803
  : false;
782
804
  return __handleServerActionRscRequest({
783
805
  actionId,
@@ -795,6 +817,8 @@ export default createAppRscHandler({
795
817
  request: actionRequest,
796
818
  mountedSlotsHeader: actionMountedSlotsHeader,
797
819
  renderMode: actionRenderMode,
820
+ observeMetadataSearchParamsAccess,
821
+ observePageSearchParamsAccess,
798
822
  }) {
799
823
  return buildPageElements(actionRoute, actionParams, actionCleanPathname, {
800
824
  opts: interceptOpts,
@@ -803,6 +827,8 @@ export default createAppRscHandler({
803
827
  request: actionRequest,
804
828
  mountedSlotsHeader: actionMountedSlotsHeader,
805
829
  renderMode: actionRenderMode,
830
+ observeMetadataSearchParamsAccess: observeMetadataSearchParamsAccess === true,
831
+ observePageSearchParamsAccess: observePageSearchParamsAccess === true,
806
832
  });
807
833
  },
808
834
  cleanPathname,
@@ -828,6 +854,7 @@ export default createAppRscHandler({
828
854
  },
829
855
  createTemporaryReferenceSet,
830
856
  decodeReply,
857
+ draftModeSecret: __draftModeSecret,
831
858
  findIntercept(pathnameToMatch) {
832
859
  return findIntercept(pathnameToMatch, interceptionContext);
833
860
  },
@@ -871,6 +898,8 @@ export default createAppRscHandler({
871
898
  return {
872
899
  interceptionContext,
873
900
  interceptLayouts: intercept.interceptLayouts,
901
+ interceptLayoutSegments: intercept.interceptLayoutSegments,
902
+ interceptBranchSegments: intercept.interceptBranchSegments,
874
903
  interceptSlotId: intercept.slotId,
875
904
  interceptSlotKey: intercept.slotKey,
876
905
  interceptSourceMatchedUrl: interceptionContext,
@@ -910,7 +939,7 @@ export default createAppRscHandler({
910
939
  },` : ""}
911
940
  publicFiles: __publicFiles,
912
941
  renderNotFound({ isRscRequest, matchedParams, middlewareContext, request, route, scriptNonce }) {
913
- const __isEdge = route ? __isEdgeRuntime(__resolveAppPageSegmentConfig({ layouts: route.layouts, page: route.page }).runtime) : false;
942
+ const __isEdge = route ? __isEdgeRuntime(__resolveRouteRuntime(route)) : false;
914
943
  return __fallbackRenderer.renderNotFound(route, isRscRequest, request, matchedParams, scriptNonce, middlewareContext, { isEdgeRuntime: __isEdge });
915
944
  },
916
945
  ${hasPagesDir ? `async renderPagesFallback({ allowRscDocumentFallback, appRouteMatch, isDataRequest, isRscRequest, matchKind, middlewareContext, pathname, pagesDataRequest, request, url }) {
@@ -74,6 +74,7 @@ function registerRouteModules(routes, imports) {
74
74
  if (slot.pagePath) imports.getLazyLoaderVar(slot.pagePath);
75
75
  if (slot.defaultPath) imports.getLazyLoaderVar(slot.defaultPath);
76
76
  if (slot.layoutPath) imports.getLazyLoaderVar(slot.layoutPath);
77
+ for (const layoutPath of slot.configLayoutPaths ?? []) imports.getLazyLoaderVar(layoutPath);
77
78
  if (slot.loadingPath) imports.getLazyLoaderVar(slot.loadingPath);
78
79
  if (slot.errorPath) imports.getLazyLoaderVar(slot.errorPath);
79
80
  for (const ir of slot.interceptingRoutes) {
@@ -112,6 +113,8 @@ function buildRouteEntries(routes, imports) {
112
113
  slotId: ${JSON.stringify(ir.slotId ?? null)},
113
114
  interceptLayouts: ${moduleArray(ir.layoutPaths.length)},
114
115
  __loadInterceptLayouts: ${lazyLoaderArray(ir.layoutPaths, imports)},
116
+ interceptLayoutSegments: ${JSON.stringify(ir.layoutSegments ?? [])},
117
+ interceptBranchSegments: ${JSON.stringify(ir.branchSegments ?? [])},
115
118
  page: null,
116
119
  __pageLoader: ${imports.getLazyLoaderVar(ir.pagePath)},
117
120
  params: ${JSON.stringify(ir.params)},
@@ -124,6 +127,8 @@ function buildRouteEntries(routes, imports) {
124
127
  sourcePageSegments: ${JSON.stringify(ir.sourcePageSegments)},
125
128
  interceptLayouts: ${moduleArray(ir.layoutPaths.length)},
126
129
  __loadInterceptLayouts: ${lazyLoaderArray(ir.layoutPaths, imports)},
130
+ interceptLayoutSegments: ${JSON.stringify(ir.layoutSegments ?? [])},
131
+ interceptBranchSegments: ${JSON.stringify(ir.branchSegments ?? [])},
127
132
  page: null,
128
133
  __pageLoader: ${imports.getLazyLoaderVar(ir.pagePath)},
129
134
  params: ${JSON.stringify(ir.params)},
@@ -137,6 +142,9 @@ function buildRouteEntries(routes, imports) {
137
142
  __loadDefault: ${slot.defaultPath ? imports.getLazyLoaderVar(slot.defaultPath) : "null"},
138
143
  layout: null,
139
144
  __loadLayout: ${slot.layoutPath ? imports.getLazyLoaderVar(slot.layoutPath) : "null"},
145
+ configLayouts: ${moduleArray(slot.configLayoutPaths?.length ?? 0)},
146
+ __loadConfigLayouts: ${lazyLoaderArray(slot.configLayoutPaths ?? [], imports)},
147
+ configLayoutTreePositions: ${JSON.stringify(slot.configLayoutTreePositions ?? [])},
140
148
  loading: null,
141
149
  __loadLoading: ${slot.loadingPath ? imports.getLazyLoaderVar(slot.loadingPath) : "null"},
142
150
  error: null,
@@ -2,6 +2,7 @@ import { normalizePathSeparators } from "../utils/path.js";
2
2
  import { findFileWithExts } from "../routing/file-matcher.js";
3
3
  import { patternToNextFormat } from "../routing/route-validation.js";
4
4
  import { apiRouter, pagesRouter } from "../routing/pages-router.js";
5
+ import "./pages-entry-helpers.js";
5
6
  //#region src/entries/pages-client-entry.ts
6
7
  /**
7
8
  * Pages Router client hydration entry generator.
@@ -3,6 +3,7 @@ import { findFileWithExts } from "../routing/file-matcher.js";
3
3
  import { apiRouter, pagesRouter } from "../routing/pages-router.js";
4
4
  import { resolveEntryPath } from "./runtime-entry-module.js";
5
5
  import { isProxyFile } from "../server/middleware.js";
6
+ import "./pages-entry-helpers.js";
6
7
  //#region src/entries/pages-server-entry.ts
7
8
  /**
8
9
  * Pages Router server entry generator.
package/dist/index.js CHANGED
@@ -636,6 +636,10 @@ function vinext(options = {}) {
636
636
  createMiddlewareServerOnlyPlugin({
637
637
  getMiddlewarePath: () => middlewarePath,
638
638
  getCanonicalMiddlewarePath: () => middlewarePath ? tryRealpathSync(middlewarePath) ?? middlewarePath : null,
639
+ isNeutralServerModule: (id) => {
640
+ const canonicalId = canonicalizePageTransformPath(id);
641
+ return isWithinPagesDirectory(canonicalId) && isApiPage(canonicalId);
642
+ },
639
643
  serverOnlyShimPath: resolveShimModulePath(shimsDir, "server-only")
640
644
  }),
641
645
  dataUrlCssPlugin(),
@@ -1146,7 +1150,7 @@ function vinext(options = {}) {
1146
1150
  resolveId: {
1147
1151
  filter: { id: /(?:next\/|vinext\/(?:shims\/|server\/app-rsc-handler)|virtual:vinext-|@vercel\/og(?:\.js)?$)/ },
1148
1152
  handler(id, importer) {
1149
- const cleanId = id.startsWith("\0") ? id.slice(1) : id;
1153
+ const cleanId = normalizePathSeparators(id.startsWith("\0") ? id.slice(1) : id);
1150
1154
  if (cleanId === "vinext/server/app-rsc-handler") {
1151
1155
  if (_canExternalizeAppRscHandler && this.environment?.name === "rsc" && this.environment.config?.command === "serve") return {
1152
1156
  id: _appRscHandlerPath,
@@ -1158,19 +1162,19 @@ function vinext(options = {}) {
1158
1162
  if (cleanId.startsWith("vinext/shims/")) return resolveShimModulePath(_shimsDir, stripJsExtension(stripViteModuleQuery(cleanId.slice(13))));
1159
1163
  if (cleanId === VIRTUAL_SERVER_ENTRY) return RESOLVED_SERVER_ENTRY;
1160
1164
  if (cleanId === VIRTUAL_CLIENT_ENTRY) return RESOLVED_CLIENT_ENTRY;
1161
- if (cleanId.endsWith("/virtual:vinext-server-entry") || cleanId.endsWith("\\virtual:vinext-server-entry")) return RESOLVED_SERVER_ENTRY;
1162
- if (cleanId.endsWith("/virtual:vinext-client-entry") || cleanId.endsWith("\\virtual:vinext-client-entry")) return RESOLVED_CLIENT_ENTRY;
1165
+ if (cleanId.endsWith("/virtual:vinext-server-entry")) return RESOLVED_SERVER_ENTRY;
1166
+ if (cleanId.endsWith("/virtual:vinext-client-entry")) return RESOLVED_CLIENT_ENTRY;
1163
1167
  if (cleanId === VIRTUAL_RSC_ENTRY) return RESOLVED_RSC_ENTRY;
1164
1168
  if (cleanId === VIRTUAL_APP_SSR_ENTRY) return RESOLVED_APP_SSR_ENTRY;
1165
1169
  if (cleanId === VIRTUAL_APP_BROWSER_ENTRY) return RESOLVED_APP_BROWSER_ENTRY;
1166
1170
  if (cleanId === VIRTUAL_APP_CAPABILITIES) return RESOLVED_APP_CAPABILITIES;
1167
1171
  if (cleanId === "next/root-params" || cleanId === "next/root-params.js") return RESOLVED_ROOT_PARAMS;
1168
- if (cleanId === "virtual:vinext-cache-adapters" || cleanId.endsWith("/virtual:vinext-cache-adapters") || cleanId.endsWith("\\virtual:vinext-cache-adapters")) return RESOLVED_CACHE_ADAPTERS;
1172
+ if (cleanId === "virtual:vinext-cache-adapters" || cleanId.endsWith("/virtual:vinext-cache-adapters")) return RESOLVED_CACHE_ADAPTERS;
1169
1173
  if (cleanId.startsWith("virtual:vinext-google-fonts?")) return RESOLVED_VIRTUAL_GOOGLE_FONTS + cleanId.slice(VIRTUAL_GOOGLE_FONTS.length);
1170
- if (cleanId.endsWith("/virtual:vinext-rsc-entry") || cleanId.endsWith("\\virtual:vinext-rsc-entry")) return RESOLVED_RSC_ENTRY;
1171
- if (cleanId.endsWith("/virtual:vinext-app-ssr-entry") || cleanId.endsWith("\\virtual:vinext-app-ssr-entry")) return RESOLVED_APP_SSR_ENTRY;
1172
- if (cleanId.endsWith("/virtual:vinext-app-browser-entry") || cleanId.endsWith("\\virtual:vinext-app-browser-entry")) return RESOLVED_APP_BROWSER_ENTRY;
1173
- if (cleanId.includes("/virtual:vinext-google-fonts?") || cleanId.includes("\\virtual:vinext-google-fonts?")) {
1174
+ if (cleanId.endsWith("/virtual:vinext-rsc-entry")) return RESOLVED_RSC_ENTRY;
1175
+ if (cleanId.endsWith("/virtual:vinext-app-ssr-entry")) return RESOLVED_APP_SSR_ENTRY;
1176
+ if (cleanId.endsWith("/virtual:vinext-app-browser-entry")) return RESOLVED_APP_BROWSER_ENTRY;
1177
+ if (cleanId.includes("/virtual:vinext-google-fonts?")) {
1174
1178
  const queryIndex = cleanId.indexOf(VIRTUAL_GOOGLE_FONTS + "?");
1175
1179
  return RESOLVED_VIRTUAL_GOOGLE_FONTS + cleanId.slice(queryIndex + VIRTUAL_GOOGLE_FONTS.length);
1176
1180
  }
package/dist/init.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { detectPackageManager, detectPackageManagerName, ensureESModule, hasAppDir, hasViteConfig, renameCJSConfigs } from "./utils/project.js";
2
+ import { normalizePathSeparators } from "./utils/path.js";
2
3
  import { formatReport, runCheck } from "./check.js";
3
4
  import { createRequire } from "node:module";
4
5
  import fs from "node:fs";
@@ -176,7 +177,7 @@ function updateGitignore(root) {
176
177
  return true;
177
178
  }
178
179
  async function init(options) {
179
- const root = path.resolve(options.root);
180
+ const root = normalizePathSeparators(path.resolve(options.root));
180
181
  const port = options.port ?? 3001;
181
182
  const exec = options._exec ?? ((cmd, opts) => {
182
183
  const [program, ...args] = cmd.split(" ");
@@ -2,8 +2,8 @@ import { Plugin } from "vite";
2
2
 
3
3
  //#region src/plugins/middleware-server-only.d.ts
4
4
  /**
5
- * Allow `import 'server-only'` from middleware (and any module reachable
6
- * from middleware) in the SSR environment.
5
+ * Allow `import 'server-only'` from neutral server targets (and any module
6
+ * reachable from them) in the SSR environment.
7
7
  *
8
8
  * Background: middleware runs server-side, so importing `server-only` is
9
9
  * semantically correct. However, vinext bundles middleware into the SSR
@@ -13,9 +13,9 @@ import { Plugin } from "vite";
13
13
  *
14
14
  * 'server-only' cannot be imported in client build ('ssr' environment)
15
15
  *
16
- * Next.js solves this with webpack `issuerLayer` rules: middleware (and
17
- * `instrumentation`) sit in the `neutralTarget` layer where `server-only`
18
- * is aliased to a no-op while `client-only` still errors. See
16
+ * Next.js solves this with webpack `issuerLayer` rules: middleware,
17
+ * instrumentation, and Pages API routes sit in server-only or neutral layers
18
+ * where `server-only` is aliased to a no-op while `client-only` still errors. See
19
19
  * packages/next/src/build/webpack-config.ts ("Alias server-only and
20
20
  * client-only to proper exports based on bundling layers")
21
21
  *
@@ -23,7 +23,8 @@ import { Plugin } from "vite";
23
23
  * the behavior with import-chain taint tracking:
24
24
  *
25
25
  * 1. Seed a `tainted` set with the middleware entry path (and its
26
- * canonical realpath).
26
+ * canonical realpath), and recognize other neutral server entry modules
27
+ * such as Pages API routes through `isNeutralServerModule`.
27
28
  * 2. For every resolveId call from a tainted importer, resolve the import
28
29
  * via `this.resolve(..., { skipSelf: true })` and add the resolved id
29
30
  * to the tainted set. This propagates the taint along the import graph
@@ -47,6 +48,7 @@ import { Plugin } from "vite";
47
48
  declare function createMiddlewareServerOnlyPlugin(options: {
48
49
  getMiddlewarePath: () => string | null;
49
50
  getCanonicalMiddlewarePath: () => string | null;
51
+ isNeutralServerModule?: (id: string) => boolean;
50
52
  serverOnlyShimPath: string;
51
53
  }): Plugin;
52
54
  //#endregion
@@ -1,8 +1,8 @@
1
1
  import { normalizePathSeparators } from "../utils/path.js";
2
2
  //#region src/plugins/middleware-server-only.ts
3
3
  /**
4
- * Allow `import 'server-only'` from middleware (and any module reachable
5
- * from middleware) in the SSR environment.
4
+ * Allow `import 'server-only'` from neutral server targets (and any module
5
+ * reachable from them) in the SSR environment.
6
6
  *
7
7
  * Background: middleware runs server-side, so importing `server-only` is
8
8
  * semantically correct. However, vinext bundles middleware into the SSR
@@ -12,9 +12,9 @@ import { normalizePathSeparators } from "../utils/path.js";
12
12
  *
13
13
  * 'server-only' cannot be imported in client build ('ssr' environment)
14
14
  *
15
- * Next.js solves this with webpack `issuerLayer` rules: middleware (and
16
- * `instrumentation`) sit in the `neutralTarget` layer where `server-only`
17
- * is aliased to a no-op while `client-only` still errors. See
15
+ * Next.js solves this with webpack `issuerLayer` rules: middleware,
16
+ * instrumentation, and Pages API routes sit in server-only or neutral layers
17
+ * where `server-only` is aliased to a no-op while `client-only` still errors. See
18
18
  * packages/next/src/build/webpack-config.ts ("Alias server-only and
19
19
  * client-only to proper exports based on bundling layers")
20
20
  *
@@ -22,7 +22,8 @@ import { normalizePathSeparators } from "../utils/path.js";
22
22
  * the behavior with import-chain taint tracking:
23
23
  *
24
24
  * 1. Seed a `tainted` set with the middleware entry path (and its
25
- * canonical realpath).
25
+ * canonical realpath), and recognize other neutral server entry modules
26
+ * such as Pages API routes through `isNeutralServerModule`.
26
27
  * 2. For every resolveId call from a tainted importer, resolve the import
27
28
  * via `this.resolve(..., { skipSelf: true })` and add the resolved id
28
29
  * to the tainted set. This propagates the taint along the import graph
@@ -72,7 +73,7 @@ function createMiddlewareServerOnlyPlugin(options) {
72
73
  async handler(id, importer, opts) {
73
74
  if (this.environment?.name === "rsc") return;
74
75
  if (!importer) return;
75
- if (!isTainted(importer)) return;
76
+ if (!isTainted(importer) && !options.isNeutralServerModule?.(canonicalizeId(importer))) return;
76
77
  if (id === "server-only") return {
77
78
  id: options.serverOnlyShimPath,
78
79
  moduleSideEffects: false
@@ -23,7 +23,9 @@ type InterceptingRoute = {
23
23
  sourceMatchPattern: string; /** Absolute path to the intercepting page component */
24
24
  pagePath: string; /** Filesystem segments from app/ root to the intercepting page directory. */
25
25
  sourcePageSegments?: string[]; /** Absolute layout paths inside the intercepting route tree, outermost to innermost */
26
- layoutPaths: string[]; /** Parameter names for dynamic segments */
26
+ layoutPaths: string[]; /** Normalized branch segments accumulated at each intercept layout. */
27
+ layoutSegments?: string[][]; /** Full normalized interception branch segments through the page. */
28
+ branchSegments?: string[]; /** Parameter names for dynamic segments */
27
29
  params: string[];
28
30
  /**
29
31
  * Synthetic page-carrier slot id for sibling (slot-less) interception.
@@ -40,7 +42,9 @@ type ParallelSlot = {
40
42
  hasPage: boolean; /** Absolute path to the slot's page component */
41
43
  pagePath: string | null; /** Absolute path to the slot's default.tsx fallback */
42
44
  defaultPath: string | null; /** Absolute path to the slot's layout component (wraps slot content) */
43
- layoutPath: string | null; /** Absolute path to the slot's loading component */
45
+ layoutPath: string | null; /** Nested active-branch layouts whose exports contribute route config. */
46
+ configLayoutPaths?: string[]; /** Tree positions of configLayoutPaths relative to the slot root. */
47
+ configLayoutTreePositions?: number[]; /** Absolute path to the slot's loading component */
44
48
  loadingPath: string | null; /** Absolute path to the slot's error component */
45
49
  errorPath: string | null; /** Intercepting routes within this slot */
46
50
  interceptingRoutes: InterceptingRoute[];