vinext 1.0.0-beta.1 → 1.0.0-beta.2

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 (175) hide show
  1. package/README.md +4 -1
  2. package/dist/build/client-build-config.js +1 -0
  3. package/dist/build/report.d.ts +8 -1
  4. package/dist/build/report.js +12 -2
  5. package/dist/check.js +15 -2
  6. package/dist/cli.js +3 -2
  7. package/dist/config/config-matchers.d.ts +13 -37
  8. package/dist/config/config-matchers.js +31 -121
  9. package/dist/config/next-config.d.ts +3 -2
  10. package/dist/config/next-config.js +3 -1
  11. package/dist/config/request-context.d.ts +14 -0
  12. package/dist/config/request-context.js +26 -0
  13. package/dist/entries/app-rsc-entry.js +27 -9
  14. package/dist/entries/app-rsc-manifest.js +17 -0
  15. package/dist/entries/pages-server-entry.js +3 -2
  16. package/dist/index.js +82 -58
  17. package/dist/init.d.ts +6 -4
  18. package/dist/init.js +20 -22
  19. package/dist/plugins/og-assets.js +1 -1
  20. package/dist/routing/app-route-graph.d.ts +13 -5
  21. package/dist/routing/app-route-graph.js +59 -13
  22. package/dist/routing/app-router.js +12 -6
  23. package/dist/routing/file-matcher.d.ts +11 -22
  24. package/dist/routing/file-matcher.js +21 -33
  25. package/dist/routing/pages-router.js +23 -29
  26. package/dist/routing/route-matching.d.ts +14 -1
  27. package/dist/routing/route-matching.js +13 -1
  28. package/dist/routing/route-pattern.d.ts +2 -1
  29. package/dist/routing/route-pattern.js +7 -4
  30. package/dist/routing/route-trie.d.ts +5 -1
  31. package/dist/routing/route-trie.js +5 -2
  32. package/dist/server/app-browser-entry.js +7 -6
  33. package/dist/server/app-browser-history-controller.d.ts +1 -1
  34. package/dist/server/app-browser-history-controller.js +3 -3
  35. package/dist/server/app-browser-navigation-controller.js +1 -1
  36. package/dist/server/app-browser-state.js +1 -1
  37. package/dist/server/app-browser-visible-commit.js +1 -1
  38. package/dist/server/app-fallback-renderer.d.ts +3 -0
  39. package/dist/server/app-fallback-renderer.js +4 -2
  40. package/dist/server/app-middleware.js +2 -1
  41. package/dist/server/app-page-boundary-render.d.ts +12 -1
  42. package/dist/server/app-page-boundary-render.js +84 -2
  43. package/dist/server/app-page-cache-finalizer.d.ts +1 -1
  44. package/dist/server/app-page-cache.d.ts +1 -1
  45. package/dist/server/app-page-dispatch.d.ts +11 -2
  46. package/dist/server/app-page-dispatch.js +14 -17
  47. package/dist/server/app-page-element-builder.d.ts +7 -2
  48. package/dist/server/app-page-element-builder.js +112 -27
  49. package/dist/server/app-page-head.d.ts +54 -2
  50. package/dist/server/app-page-head.js +164 -78
  51. package/dist/server/app-page-http-access-fallback-metadata.d.ts +29 -0
  52. package/dist/server/app-page-http-access-fallback-metadata.js +106 -0
  53. package/dist/server/app-page-render.d.ts +2 -2
  54. package/dist/server/app-page-render.js +1 -1
  55. package/dist/server/app-page-request.d.ts +12 -0
  56. package/dist/server/app-page-request.js +133 -13
  57. package/dist/server/app-page-route-wiring.d.ts +9 -0
  58. package/dist/server/app-page-route-wiring.js +43 -3
  59. package/dist/server/app-post-middleware-context.d.ts +1 -1
  60. package/dist/server/app-post-middleware-context.js +1 -1
  61. package/dist/server/app-route-handler-dispatch.js +31 -17
  62. package/dist/server/app-route-handler-execution.d.ts +6 -1
  63. package/dist/server/app-route-handler-execution.js +42 -16
  64. package/dist/server/app-route-handler-policy.d.ts +2 -0
  65. package/dist/server/app-route-handler-policy.js +3 -3
  66. package/dist/server/app-route-module-loader.d.ts +3 -1
  67. package/dist/server/app-route-module-loader.js +1 -0
  68. package/dist/server/app-rsc-cache-busting.d.ts +1 -1
  69. package/dist/server/app-rsc-errors.d.ts +1 -1
  70. package/dist/server/app-rsc-errors.js +12 -2
  71. package/dist/server/app-rsc-handler.d.ts +10 -5
  72. package/dist/server/app-rsc-handler.js +52 -24
  73. package/dist/server/app-rsc-request-normalization.d.ts +3 -2
  74. package/dist/server/app-rsc-request-normalization.js +8 -2
  75. package/dist/server/app-rsc-response-finalizer.d.ts +2 -2
  76. package/dist/server/app-rsc-response-finalizer.js +5 -11
  77. package/dist/server/app-rsc-route-matching.d.ts +20 -0
  78. package/dist/server/app-rsc-route-matching.js +87 -11
  79. package/dist/server/app-segment-config.js +13 -3
  80. package/dist/server/app-server-action-execution.d.ts +3 -1
  81. package/dist/server/app-server-action-execution.js +9 -7
  82. package/dist/server/app-ssr-entry.js +2 -2
  83. package/dist/server/app-ssr-stream.d.ts +1 -7
  84. package/dist/server/app-ssr-stream.js +2 -10
  85. package/dist/server/config-headers.d.ts +24 -0
  86. package/dist/server/config-headers.js +52 -0
  87. package/dist/server/dev-initial-server-error.d.ts +1 -1
  88. package/dist/server/dev-server.js +29 -13
  89. package/dist/server/isr-cache.d.ts +1 -1
  90. package/dist/server/middleware-matcher-pattern.d.ts +22 -0
  91. package/dist/server/middleware-matcher-pattern.js +219 -0
  92. package/dist/server/middleware-matcher.d.ts +2 -7
  93. package/dist/server/middleware-matcher.js +13 -60
  94. package/dist/server/middleware-path-to-regexp.d.ts +14 -0
  95. package/dist/server/middleware-path-to-regexp.js +228 -0
  96. package/dist/server/middleware-runtime.js +4 -3
  97. package/dist/server/middleware.d.ts +1 -1
  98. package/dist/server/middleware.js +2 -1
  99. package/dist/server/pages-data-route.d.ts +11 -1
  100. package/dist/server/pages-data-route.js +16 -1
  101. package/dist/server/pages-get-initial-props.d.ts +15 -2
  102. package/dist/server/pages-get-initial-props.js +16 -6
  103. package/dist/server/pages-page-data.d.ts +3 -1
  104. package/dist/server/pages-page-data.js +16 -7
  105. package/dist/server/pages-page-handler.d.ts +4 -2
  106. package/dist/server/pages-page-handler.js +16 -17
  107. package/dist/server/pages-page-response.js +1 -1
  108. package/dist/server/pages-readiness.d.ts +3 -3
  109. package/dist/server/pages-request-pipeline.d.ts +1 -0
  110. package/dist/server/pages-request-pipeline.js +25 -11
  111. package/dist/server/pages-router-entry.js +6 -0
  112. package/dist/server/prerender-route-params.js +2 -2
  113. package/dist/server/prod-server.d.ts +1 -4
  114. package/dist/server/prod-server.js +36 -23
  115. package/dist/server/request-pipeline.d.ts +15 -29
  116. package/dist/server/request-pipeline.js +24 -59
  117. package/dist/server/rsc-stream-hints.js +122 -18
  118. package/dist/shims/app.d.ts +1 -1
  119. package/dist/shims/cache-request-state.d.ts +27 -1
  120. package/dist/shims/cache-request-state.js +60 -1
  121. package/dist/shims/cache-runtime.d.ts +12 -1
  122. package/dist/shims/cache-runtime.js +22 -7
  123. package/dist/shims/cache.d.ts +5 -5
  124. package/dist/shims/cache.js +26 -10
  125. package/dist/shims/document.d.ts +42 -83
  126. package/dist/shims/document.js +62 -53
  127. package/dist/shims/dynamic.d.ts +5 -25
  128. package/dist/shims/dynamic.js +29 -8
  129. package/dist/shims/error.d.ts +1 -1
  130. package/dist/shims/error.js +1 -1
  131. package/dist/shims/fetch-cache.js +12 -3
  132. package/dist/shims/font-google-base.d.ts +11 -4
  133. package/dist/shims/font-local.d.ts +10 -3
  134. package/dist/shims/head.d.ts +13 -2
  135. package/dist/shims/head.js +3 -3
  136. package/dist/shims/headers.d.ts +28 -31
  137. package/dist/shims/headers.js +109 -49
  138. package/dist/shims/image.d.ts +27 -37
  139. package/dist/shims/image.js +21 -14
  140. package/dist/shims/internal/cookie-serialize.d.ts +3 -1
  141. package/dist/shims/internal/cookie-serialize.js +3 -1
  142. package/dist/shims/internal/hybrid-client-route-owner.js +3 -1
  143. package/dist/shims/internal/utils.d.ts +3 -3
  144. package/dist/shims/legacy-image.d.ts +3 -37
  145. package/dist/shims/legacy-image.js +6 -16
  146. package/dist/shims/link.d.ts +15 -22
  147. package/dist/shims/link.js +20 -11
  148. package/dist/shims/navigation-context-state.d.ts +1 -1
  149. package/dist/shims/navigation.d.ts +7 -3
  150. package/dist/shims/navigation.js +24 -13
  151. package/dist/shims/navigation.react-server.js +1 -1
  152. package/dist/shims/next-shims-augmentations.d.ts +1 -0
  153. package/dist/shims/next-shims-public.d.ts +1 -0
  154. package/dist/shims/public-shim-map.json.js +103 -0
  155. package/dist/shims/readonly-url-search-params.d.ts +3 -3
  156. package/dist/shims/readonly-url-search-params.js +3 -3
  157. package/dist/shims/request-state-types.d.ts +1 -1
  158. package/dist/shims/router.d.ts +55 -21
  159. package/dist/shims/router.js +162 -37
  160. package/dist/shims/script.d.ts +6 -10
  161. package/dist/shims/script.js +6 -3
  162. package/dist/shims/server.d.ts +45 -22
  163. package/dist/shims/server.js +44 -5
  164. package/dist/shims/unified-request-context.d.ts +1 -1
  165. package/dist/shims/unified-request-context.js +2 -0
  166. package/dist/shims/url-safety.d.ts +1 -1
  167. package/dist/shims/url-safety.js +2 -2
  168. package/dist/typegen.d.ts +8 -2
  169. package/dist/typegen.js +76 -15
  170. package/dist/utils/domain-locale.d.ts +2 -2
  171. package/dist/utils/external-url.d.ts +5 -0
  172. package/dist/utils/external-url.js +7 -0
  173. package/dist/utils/regex-safety.d.ts +8 -0
  174. package/dist/utils/regex-safety.js +737 -0
  175. package/package.json +13 -2
@@ -25,7 +25,10 @@ type InterceptingRoute = {
25
25
  sourcePageSegments?: string[]; /** Absolute layout paths inside the intercepting route tree, outermost to innermost */
26
26
  layoutPaths: string[]; /** Normalized branch segments accumulated at each intercept layout. */
27
27
  layoutSegments?: string[][]; /** Full normalized interception branch segments through the page. */
28
- branchSegments?: string[]; /** Parameter names for dynamic segments */
28
+ branchSegments?: string[]; /** Nearest not-found convention inside the interception branch. */
29
+ notFoundPath?: string | null; /** Slot-root-relative branch segments used to scope not-found params. */
30
+ notFoundBranchSegments?: string[]; /** Tree position relative to the normalized interception branch. */
31
+ notFoundTreePosition?: number | null; /** Parameter names for dynamic segments */
29
32
  params: string[];
30
33
  /**
31
34
  * Synthetic page-carrier slot id for sibling (slot-less) interception.
@@ -46,7 +49,9 @@ type ParallelSlot = {
46
49
  configLayoutPaths?: string[]; /** Tree positions of configLayoutPaths relative to the slot root. */
47
50
  configLayoutTreePositions?: number[]; /** Absolute path to the slot's loading component */
48
51
  loadingPath: string | null; /** Absolute path to the slot's error component */
49
- errorPath: string | null; /** Intercepting routes within this slot */
52
+ errorPath: string | null; /** Nearest not-found convention for the slot's active branch. */
53
+ notFoundPath?: string | null; /** Tree position of that convention relative to the slot root. */
54
+ notFoundTreePosition?: number | null; /** Intercepting routes within this slot */
50
55
  interceptingRoutes: InterceptingRoute[];
51
56
  /**
52
57
  * The layout index (0-based, in route.layouts[]) that this slot belongs to.
@@ -105,7 +110,8 @@ type AppRoute = {
105
110
  layoutErrorPaths: (string | null)[]; /** Per-segment error boundary paths, aligned with errorTreePositions. */
106
111
  errorPaths?: string[]; /** Tree position (directory depth from app/ root) for each error boundary. */
107
112
  errorTreePositions?: number[]; /** Not-found component path (nearest, walking up from page dir) */
108
- notFoundPath: string | null;
113
+ notFoundPath: string | null; /** Tree position of the nearest not-found component's owning segment. */
114
+ notFoundTreePosition?: number | null;
109
115
  /**
110
116
  * Not-found component paths per layout level (aligned with layouts array).
111
117
  * Each entry is the not-found.tsx at that layout's directory, or null.
@@ -119,8 +125,10 @@ type AppRoute = {
119
125
  * Used to create per-layout ForbiddenBoundary.
120
126
  */
121
127
  forbiddenPaths: (string | null)[]; /** Forbidden component path (403) at the route's directory level */
122
- forbiddenPath: string | null; /** Unauthorized component path (401) at the route's directory level */
123
- unauthorizedPath: string | null; /** Unauthorized component paths per layout level (aligned with layouts array). */
128
+ forbiddenPath: string | null; /** Tree position of the nearest forbidden component's owning segment. */
129
+ forbiddenTreePosition?: number | null; /** Unauthorized component path (401) at the route's directory level */
130
+ unauthorizedPath: string | null; /** Tree position of the nearest unauthorized component's owning segment. */
131
+ unauthorizedTreePosition?: number | null; /** Unauthorized component paths per layout level (aligned with layouts array). */
124
132
  unauthorizedPaths: (string | null)[];
125
133
  /**
126
134
  * Filesystem segments from app/ root to the route's directory.
@@ -459,11 +459,14 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
459
459
  const subPage = slotPages.get(slot.key);
460
460
  if (subPage !== void 0) {
461
461
  const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, subPage, matcher);
462
+ const notFoundBoundary = findSlotNotFoundBoundary(slot.ownerDir, subPage, matcher);
462
463
  return {
463
464
  ...slot,
464
465
  pagePath: subPage,
465
466
  configLayoutPaths,
466
467
  configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
468
+ notFoundPath: notFoundBoundary.path,
469
+ notFoundTreePosition: notFoundBoundary.treePosition,
467
470
  routeSegments: rawSegments
468
471
  };
469
472
  }
@@ -545,11 +548,14 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
545
548
  const subSlots = parentRoute.parallelSlots.map((slot) => {
546
549
  const subPage = slotPages.get(slot.key);
547
550
  const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, subPage ?? null, matcher);
551
+ const notFoundBoundary = findSlotNotFoundBoundary(slot.ownerDir, subPage ?? null, matcher);
548
552
  return {
549
553
  ...slot,
550
554
  pagePath: subPage || null,
551
555
  configLayoutPaths,
552
556
  configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
557
+ notFoundPath: notFoundBoundary.path,
558
+ notFoundTreePosition: notFoundBoundary.treePosition,
553
559
  routeSegments: subPage ? rawSegments : null
554
560
  };
555
561
  });
@@ -578,10 +584,13 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
578
584
  errorPath: parentRoute.errorPath,
579
585
  layoutErrorPaths: parentRoute.layoutErrorPaths,
580
586
  notFoundPath: parentRoute.notFoundPath,
587
+ notFoundTreePosition: parentRoute.notFoundTreePosition,
581
588
  notFoundPaths: parentRoute.notFoundPaths,
582
589
  forbiddenPaths: parentRoute.forbiddenPaths,
583
590
  forbiddenPath: parentRoute.forbiddenPath,
591
+ forbiddenTreePosition: parentRoute.forbiddenTreePosition,
584
592
  unauthorizedPath: parentRoute.unauthorizedPath,
593
+ unauthorizedTreePosition: parentRoute.unauthorizedTreePosition,
585
594
  unauthorizedPaths: parentRoute.unauthorizedPaths,
586
595
  routeSegments: [...parentRoute.routeSegments, ...rawSegments],
587
596
  childrenRouteSegments: childrenDefault ? parentRoute.routeSegments : void 0,
@@ -669,6 +678,25 @@ function findSlotConfigLayoutTreePositions(slotDir, layoutPaths) {
669
678
  return relativeDir ? relativeDir.split(path.sep).filter(Boolean).length : 0;
670
679
  });
671
680
  }
681
+ function findSlotNotFoundBoundary(slotDir, pagePath, matcher) {
682
+ let dir = pagePath ? path.dirname(pagePath) : slotDir;
683
+ while (dir === slotDir || dir.startsWith(`${slotDir}${path.sep}`)) {
684
+ const boundaryPath = findFile(dir, "not-found", matcher);
685
+ if (boundaryPath) {
686
+ const relativeDir = path.relative(slotDir, dir);
687
+ return {
688
+ path: boundaryPath,
689
+ treePosition: relativeDir ? relativeDir.split(path.sep).filter(Boolean).length : 0
690
+ };
691
+ }
692
+ if (dir === slotDir) break;
693
+ dir = path.dirname(dir);
694
+ }
695
+ return {
696
+ path: null,
697
+ treePosition: null
698
+ };
699
+ }
672
700
  /**
673
701
  * Find a sibling catch-all page directly under `dir`, i.e. a `[...slug]` or
674
702
  * `[[...slug]]` directory that contains a `page` file. Returns the absolute
@@ -730,9 +758,12 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath, includeN
730
758
  const effectivePagePath = pagePath ?? (routePath ? null : findFile(routeDir, "default", matcher));
731
759
  const loadingPath = findFile(routeDir, "loading", matcher);
732
760
  const errorPath = findFile(routeDir, "error", matcher);
733
- const notFoundPath = discoverBoundaryFile(segments, appDir, "not-found", matcher);
734
- const forbiddenPath = discoverBoundaryFile(segments, appDir, "forbidden", matcher);
735
- const unauthorizedPath = discoverBoundaryFile(segments, appDir, "unauthorized", matcher);
761
+ const notFoundEntry = discoverBoundaryFileEntry(segments, appDir, "not-found", matcher);
762
+ const notFoundPath = notFoundEntry?.path ?? null;
763
+ const forbiddenEntry = discoverBoundaryFileEntry(segments, appDir, "forbidden", matcher);
764
+ const forbiddenPath = forbiddenEntry?.path ?? null;
765
+ const unauthorizedEntry = discoverBoundaryFileEntry(segments, appDir, "unauthorized", matcher);
766
+ const unauthorizedPath = unauthorizedEntry?.path ?? null;
736
767
  const notFoundPaths = discoverBoundaryFilePerLayout(layouts, "not-found", matcher);
737
768
  const forbiddenPaths = discoverBoundaryFilePerLayout(layouts, "forbidden", matcher);
738
769
  const unauthorizedPaths = discoverBoundaryFilePerLayout(layouts, "unauthorized", matcher);
@@ -759,10 +790,13 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath, includeN
759
790
  errorPaths,
760
791
  errorTreePositions,
761
792
  notFoundPath,
793
+ notFoundTreePosition: notFoundEntry?.treePosition ?? null,
762
794
  notFoundPaths,
763
795
  forbiddenPaths,
764
796
  forbiddenPath,
797
+ forbiddenTreePosition: forbiddenEntry?.treePosition ?? null,
765
798
  unauthorizedPath,
799
+ unauthorizedTreePosition: unauthorizedEntry?.treePosition ?? null,
766
800
  unauthorizedPaths,
767
801
  routeSegments: segments,
768
802
  templateTreePositions,
@@ -921,7 +955,7 @@ function discoverLayoutAlignedErrors(segments, appDir, matcher) {
921
955
  * by walking from the route's directory up to the app root.
922
956
  * Returns the first (closest) file found, or null.
923
957
  */
924
- function discoverBoundaryFile(segments, appDir, fileName, matcher) {
958
+ function discoverBoundaryFileEntry(segments, appDir, fileName, matcher) {
925
959
  const dirs = [];
926
960
  let dir = appDir;
927
961
  dirs.push(dir);
@@ -931,7 +965,10 @@ function discoverBoundaryFile(segments, appDir, fileName, matcher) {
931
965
  }
932
966
  for (let i = dirs.length - 1; i >= 0; i--) {
933
967
  const f = findFile(dirs[i], fileName, matcher);
934
- if (f) return f;
968
+ if (f) return {
969
+ path: f,
970
+ treePosition: i
971
+ };
935
972
  }
936
973
  return null;
937
974
  }
@@ -1172,6 +1209,7 @@ function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = fa
1172
1209
  const ownerSegments = path.relative(appDir, dir).split(path.sep).filter((segment) => segment.length > 0);
1173
1210
  const ownerTreePath = createAppRouteGraphTreePath(ownerSegments, ownerSegments.length);
1174
1211
  const configLayoutPaths = findSlotConfigLayoutPaths(slotDir, pagePath, matcher);
1212
+ const notFoundBoundary = findSlotNotFoundBoundary(slotDir, pagePath, matcher);
1175
1213
  slots.push({
1176
1214
  id: createAppRouteGraphSlotId(slotName, ownerTreePath),
1177
1215
  key: `${slotName}@${path.relative(appDir, slotDir)}`,
@@ -1186,6 +1224,8 @@ function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = fa
1186
1224
  configLayoutTreePositions: findSlotConfigLayoutTreePositions(slotDir, configLayoutPaths),
1187
1225
  loadingPath: findFile(slotDir, "loading", matcher),
1188
1226
  errorPath: findFile(slotDir, "error", matcher),
1227
+ notFoundPath: notFoundBoundary.path,
1228
+ notFoundTreePosition: notFoundBoundary.treePosition,
1189
1229
  interceptingRoutes,
1190
1230
  layoutIndex: -1,
1191
1231
  routeSegments: pagePath ? [] : null
@@ -1240,7 +1280,7 @@ function isInterceptionMarkerDir(name) {
1240
1280
  function discoverInterceptingRoutes(slotDir, routeDir, appDir, matcher) {
1241
1281
  if (!fs.existsSync(slotDir)) return [];
1242
1282
  const results = [];
1243
- scanForInterceptingPages(slotDir, routeDir, appDir, results, matcher);
1283
+ scanForInterceptingPages(slotDir, slotDir, routeDir, appDir, results, matcher);
1244
1284
  return results;
1245
1285
  }
1246
1286
  /**
@@ -1277,7 +1317,7 @@ function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1277
1317
  const restOfName = entry.name.slice(marker.prefix.length);
1278
1318
  const parentDir = dir;
1279
1319
  const results = [];
1280
- collectInterceptingPages(childDir, childDir, marker.convention, restOfName, parentDir, appDir, parentDir, results, matcher);
1320
+ collectInterceptingPages(childDir, childDir, marker.convention, restOfName, parentDir, appDir, parentDir, null, results, matcher);
1281
1321
  for (const ir of results) {
1282
1322
  ir.slotId = createAppRouteGraphSiblingInterceptSlotId(ir.sourceMatchPattern);
1283
1323
  const owner = findOwnerRouteForDir(parentDir, appDir, routes, routesByDir);
@@ -1334,7 +1374,7 @@ function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
1334
1374
  * Recursively scan a directory tree for page.tsx files that are inside
1335
1375
  * intercepting route directories.
1336
1376
  */
1337
- function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher) {
1377
+ function scanForInterceptingPages(currentDir, slotRootDir, routeDir, appDir, results, matcher) {
1338
1378
  if (!fs.existsSync(currentDir)) return;
1339
1379
  const entries = fs.readdirSync(currentDir, { withFileTypes: true });
1340
1380
  for (const entry of entries) {
@@ -1344,8 +1384,8 @@ function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher
1344
1384
  const interceptDir = path.join(currentDir, entry.name);
1345
1385
  if (interceptMatch) {
1346
1386
  const restOfName = entry.name.slice(interceptMatch.prefix.length);
1347
- collectInterceptingPages(interceptDir, interceptDir, interceptMatch.convention, restOfName, routeDir, appDir, currentDir, results, matcher);
1348
- } else scanForInterceptingPages(interceptDir, routeDir, appDir, results, matcher);
1387
+ collectInterceptingPages(interceptDir, interceptDir, interceptMatch.convention, restOfName, routeDir, appDir, currentDir, slotRootDir, results, matcher);
1388
+ } else scanForInterceptingPages(interceptDir, slotRootDir, routeDir, appDir, results, matcher);
1349
1389
  }
1350
1390
  }
1351
1391
  /**
@@ -1359,7 +1399,7 @@ function matchInterceptConvention(name) {
1359
1399
  * Collect page.tsx files inside an intercepting route directory tree
1360
1400
  * and compute their target URL patterns.
1361
1401
  */
1362
- function collectInterceptingPages(currentDir, interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, results, matcher, parentLayoutPaths = []) {
1402
+ function collectInterceptingPages(currentDir, interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, slotRootDir, results, matcher, parentLayoutPaths = []) {
1363
1403
  const currentLayoutPath = findFile(currentDir, "layout", matcher);
1364
1404
  const layoutPaths = currentLayoutPath ? [...parentLayoutPaths, currentLayoutPath] : parentLayoutPaths;
1365
1405
  const page = findFile(currentDir, "page", matcher);
@@ -1367,13 +1407,19 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1367
1407
  const targetPattern = computeInterceptTarget(convention, interceptSegment, currentDir, interceptRoot, routeDir, appDir);
1368
1408
  if (targetPattern) {
1369
1409
  const sourceMatchPattern = computeInterceptSourceMatchPattern(interceptParentDir, appDir);
1410
+ const notFoundBoundary = findSlotNotFoundBoundary(slotRootDir ?? interceptRoot, page, matcher);
1411
+ const branchSegments = [interceptSegment, ...path.relative(interceptRoot, path.dirname(page)).split(path.sep).filter(Boolean)];
1412
+ const slotParentSegments = slotRootDir ? path.relative(slotRootDir, interceptParentDir).split(path.sep).filter(Boolean) : [];
1370
1413
  results.push({
1371
- branchSegments: [interceptSegment, ...path.relative(interceptRoot, path.dirname(page)).split(path.sep).filter(Boolean)],
1414
+ branchSegments,
1372
1415
  convention,
1373
1416
  layoutPaths: [...layoutPaths],
1374
1417
  layoutSegments: layoutPaths.map((layoutPath) => {
1375
1418
  return [interceptSegment, ...path.relative(interceptRoot, path.dirname(layoutPath)).split(path.sep).filter(Boolean)];
1376
1419
  }),
1420
+ notFoundBranchSegments: [...slotParentSegments, ...branchSegments],
1421
+ notFoundPath: notFoundBoundary.path,
1422
+ notFoundTreePosition: notFoundBoundary.treePosition === null ? null : slotRootDir ? notFoundBoundary.treePosition : notFoundBoundary.treePosition + 1,
1377
1423
  targetPattern: targetPattern.pattern,
1378
1424
  sourceMatchPattern,
1379
1425
  pagePath: page,
@@ -1387,7 +1433,7 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1387
1433
  for (const entry of entries) {
1388
1434
  if (!entry.isDirectory()) continue;
1389
1435
  if (entry.name.startsWith("_")) continue;
1390
- collectInterceptingPages(path.join(currentDir, entry.name), interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, results, matcher, layoutPaths);
1436
+ collectInterceptingPages(path.join(currentDir, entry.name), interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, slotRootDir, results, matcher, layoutPaths);
1391
1437
  }
1392
1438
  }
1393
1439
  /**
@@ -20,7 +20,9 @@ import { buildAppRouteGraph, computeAppRouteStaticSiblings, computeRootParamName
20
20
  let cachedGraph = null;
21
21
  let cachedAppDir = null;
22
22
  let cachedPageExtensionsKey = null;
23
+ let cacheGeneration = 0;
23
24
  function invalidateAppRouteCache() {
25
+ cacheGeneration++;
24
26
  cachedGraph = null;
25
27
  cachedAppDir = null;
26
28
  cachedPageExtensionsKey = null;
@@ -35,12 +37,16 @@ function invalidateAppRouteCache() {
35
37
  async function appRouteGraph(appDir, pageExtensions, matcher) {
36
38
  matcher ??= createValidFileMatcher(pageExtensions);
37
39
  const pageExtensionsKey = JSON.stringify(matcher.extensions);
38
- if (cachedGraph && cachedAppDir === appDir && cachedPageExtensionsKey === pageExtensionsKey) return cachedGraph;
39
- const graph = await buildAppRouteGraph(appDir, matcher);
40
- cachedGraph = graph;
41
- cachedAppDir = appDir;
42
- cachedPageExtensionsKey = pageExtensionsKey;
43
- return graph;
40
+ while (true) {
41
+ if (cachedGraph && cachedAppDir === appDir && cachedPageExtensionsKey === pageExtensionsKey) return cachedGraph;
42
+ const scanGeneration = cacheGeneration;
43
+ const graph = await buildAppRouteGraph(appDir, matcher);
44
+ if (scanGeneration !== cacheGeneration) continue;
45
+ cachedGraph = graph;
46
+ cachedAppDir = appDir;
47
+ cachedPageExtensionsKey = pageExtensionsKey;
48
+ return graph;
49
+ }
44
50
  }
45
51
  /**
46
52
  * Scan the app/ directory and return a list of routes.
@@ -24,32 +24,21 @@ declare function findFileWithExtensions(basePath: string, matcher: ValidFileMatc
24
24
  */
25
25
  declare function findFileWithExts(dir: string, name: string, matcher: ValidFileMatcher): string | null;
26
26
  /**
27
- * Vite's default `resolve.extensions` covers `.tsx/.ts/.jsx/.js/.json` (and
28
- * `.mjs/.mts`). When the user configures `pageExtensions` with values Vite
29
- * does not know about — e.g. `["platform.tsx", "tsx", "mdx"]` from the
30
- * Next.js `resolve-extensions` fixture — extensionless imports of those
31
- * files fail to resolve, and the build crashes with "Custom deploy script
32
- * failed: undefined (1)".
27
+ * Add the config extensions produced by `vinext init` to vinext's resolver.
33
28
  *
34
- * Build the merged extension list that Vite should use:
29
+ * `pageExtensions` is intentionally not part of module resolution. Next.js
30
+ * uses it to discover route files; custom module extensions are configured
31
+ * separately through `turbopack.resolveExtensions` or webpack
32
+ * `resolve.extensions`.
35
33
  *
36
- * 1. User-configured pageExtensions go first (each prefixed with `.`) so
37
- * the user's priority wins. e.g. `.platform.tsx` resolves before `.tsx`.
38
- * 2. Vite's defaults follow, with duplicates removed.
39
- * 3. `.cjs`/`.cts` go last (lowest priority). Neither Vite's defaults nor the
40
- * user's pageExtensions include them, but `vinext init` renames CJS config
41
- * files (e.g. `tailwind.config.js` → `tailwind.config.cjs`) when it adds
42
- * `"type": "module"`, and app code imports those extensionlessly
43
- * (`import cfg from "../tailwind.config"`). Without these, the bundle fails
44
- * with "[UNRESOLVED_IMPORT] Could not resolve '../tailwind.config'".
34
+ * The default order preserves vinext's existing module-resolution behavior
35
+ * and matches Next.js Turbopack for overlapping JavaScript and TypeScript
36
+ * extensions.
45
37
  *
46
- * The user's pageExtensions retain their relative order, which is what
47
- * Next.js / Turbopack do via the `resolveExtensions` config option.
48
- *
49
- * See: cloudflare/vinext#1502 for page-extension ordering, and
50
- * cloudflare/vinext#2435 for extensionless `.cjs` config imports.
38
+ * `.cjs`/`.cts` go last because `vinext init` renames CJS config files when it
39
+ * adds `"type": "module"`, and app code may import those files extensionlessly.
51
40
  */
52
- declare function buildViteResolveExtensions(pageExtensions?: readonly string[] | null, viteDefaults?: readonly string[]): string[];
41
+ declare function buildViteResolveExtensions(viteExtensions?: readonly string[]): string[];
53
42
  /**
54
43
  * Normalize an explicit Next.js resolver extension list for Vite.
55
44
  *
@@ -9,6 +9,15 @@ const DEFAULT_PAGE_EXTENSIONS = [
9
9
  "jsx",
10
10
  "js"
11
11
  ];
12
+ const DEFAULT_VINEXT_RESOLVE_EXTENSIONS = [
13
+ ".tsx",
14
+ ".ts",
15
+ ".jsx",
16
+ ".js",
17
+ ".mjs",
18
+ ".mts",
19
+ ".json"
20
+ ];
12
21
  function normalizePageExtensions(pageExtensions) {
13
22
  if (!Array.isArray(pageExtensions) || pageExtensions.length === 0) return [...DEFAULT_PAGE_EXTENSIONS];
14
23
  const filtered = pageExtensions.filter((ext) => typeof ext === "string").map((ext) => ext.trim().replace(/^\.+/, "")).filter((ext) => ext.length > 0);
@@ -79,46 +88,25 @@ function findFileWithExts(dir, name, matcher) {
79
88
  return null;
80
89
  }
81
90
  /**
82
- * Vite's default `resolve.extensions` covers `.tsx/.ts/.jsx/.js/.json` (and
83
- * `.mjs/.mts`). When the user configures `pageExtensions` with values Vite
84
- * does not know about — e.g. `["platform.tsx", "tsx", "mdx"]` from the
85
- * Next.js `resolve-extensions` fixture — extensionless imports of those
86
- * files fail to resolve, and the build crashes with "Custom deploy script
87
- * failed: undefined (1)".
91
+ * Add the config extensions produced by `vinext init` to vinext's resolver.
88
92
  *
89
- * Build the merged extension list that Vite should use:
93
+ * `pageExtensions` is intentionally not part of module resolution. Next.js
94
+ * uses it to discover route files; custom module extensions are configured
95
+ * separately through `turbopack.resolveExtensions` or webpack
96
+ * `resolve.extensions`.
90
97
  *
91
- * 1. User-configured pageExtensions go first (each prefixed with `.`) so
92
- * the user's priority wins. e.g. `.platform.tsx` resolves before `.tsx`.
93
- * 2. Vite's defaults follow, with duplicates removed.
94
- * 3. `.cjs`/`.cts` go last (lowest priority). Neither Vite's defaults nor the
95
- * user's pageExtensions include them, but `vinext init` renames CJS config
96
- * files (e.g. `tailwind.config.js` → `tailwind.config.cjs`) when it adds
97
- * `"type": "module"`, and app code imports those extensionlessly
98
- * (`import cfg from "../tailwind.config"`). Without these, the bundle fails
99
- * with "[UNRESOLVED_IMPORT] Could not resolve '../tailwind.config'".
98
+ * The default order preserves vinext's existing module-resolution behavior
99
+ * and matches Next.js Turbopack for overlapping JavaScript and TypeScript
100
+ * extensions.
100
101
  *
101
- * The user's pageExtensions retain their relative order, which is what
102
- * Next.js / Turbopack do via the `resolveExtensions` config option.
103
- *
104
- * See: cloudflare/vinext#1502 for page-extension ordering, and
105
- * cloudflare/vinext#2435 for extensionless `.cjs` config imports.
102
+ * `.cjs`/`.cts` go last because `vinext init` renames CJS config files when it
103
+ * adds `"type": "module"`, and app code may import those files extensionlessly.
106
104
  */
107
- function buildViteResolveExtensions(pageExtensions, viteDefaults = [
108
- ".mjs",
109
- ".js",
110
- ".mts",
111
- ".ts",
112
- ".jsx",
113
- ".tsx",
114
- ".json"
115
- ]) {
116
- const dotted = normalizePageExtensions(pageExtensions).map((ext) => `.${ext}`);
105
+ function buildViteResolveExtensions(viteExtensions = DEFAULT_VINEXT_RESOLVE_EXTENSIONS) {
117
106
  const seen = /* @__PURE__ */ new Set();
118
107
  const result = [];
119
108
  for (const ext of [
120
- ...dotted,
121
- ...viteDefaults,
109
+ ...viteExtensions,
122
110
  ".cjs",
123
111
  ".cts"
124
112
  ]) {
@@ -2,7 +2,7 @@ import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
2
  import { decodeRouteSegment, sortRoutes } from "./utils.js";
3
3
  import { createValidFileMatcher, scanWithExtensions } from "./file-matcher.js";
4
4
  import { patternToNextFormat, validateRoutePatterns } from "./route-validation.js";
5
- import { createRouteTrieCache, matchRouteWithTrie } from "./route-matching.js";
5
+ import { createRouteTrieCache, matchRouteWithTrieRawPathname } from "./route-matching.js";
6
6
  //#region src/routing/pages-router.ts
7
7
  /** Next.js special pages that should not produce routes. */
8
8
  const RESERVED_PAGE_NAMES = /* @__PURE__ */ new Set([
@@ -11,11 +11,13 @@ const RESERVED_PAGE_NAMES = /* @__PURE__ */ new Set([
11
11
  "_error"
12
12
  ]);
13
13
  const routeCache = /* @__PURE__ */ new Map();
14
+ let routeCacheGeneration = 0;
14
15
  /**
15
16
  * Invalidate cached routes for a given pages directory.
16
17
  * Called by the file watcher when pages are added/removed.
17
18
  */
18
19
  function invalidateRouteCache(pagesDir) {
20
+ routeCacheGeneration++;
19
21
  for (const key of routeCache.keys()) if (key.startsWith(`pages:${pagesDir}:`) || key.startsWith(`api:${pagesDir}:`)) routeCache.delete(key);
20
22
  }
21
23
  /**
@@ -32,20 +34,7 @@ function invalidateRouteCache(pagesDir) {
32
34
  */
33
35
  async function pagesRouter(pagesDir, pageExtensions, matcher) {
34
36
  matcher ??= createValidFileMatcher(pageExtensions);
35
- const cacheKey = `pages:${pagesDir}:${JSON.stringify(matcher.extensions)}`;
36
- const cached = routeCache.get(cacheKey);
37
- if (cached) return cached.promise;
38
- const promise = scanPageRoutes(pagesDir, matcher);
39
- routeCache.set(cacheKey, {
40
- routes: [],
41
- promise
42
- });
43
- const routes = await promise;
44
- routeCache.set(cacheKey, {
45
- routes,
46
- promise
47
- });
48
- return routes;
37
+ return getCachedRoutes(`pages:${pagesDir}:${JSON.stringify(matcher.extensions)}`, () => scanPageRoutes(pagesDir, matcher));
49
38
  }
50
39
  async function scanPageRoutes(pagesDir, matcher) {
51
40
  const routes = [];
@@ -114,7 +103,7 @@ const trieCache = createRouteTrieCache();
114
103
  * Returns the matched params or null if no match.
115
104
  */
116
105
  function matchRoute(url, routes) {
117
- return matchRouteWithTrie(url, routes, trieCache);
106
+ return matchRouteWithTrieRawPathname(url, routes, trieCache);
118
107
  }
119
108
  /**
120
109
  * Scan the pages/api/ directory and return API routes.
@@ -126,20 +115,25 @@ function matchRoute(url, routes) {
126
115
  */
127
116
  async function apiRouter(pagesDir, pageExtensions, matcher) {
128
117
  matcher ??= createValidFileMatcher(pageExtensions);
129
- const cacheKey = `api:${pagesDir}:${JSON.stringify(matcher.extensions)}`;
118
+ return getCachedRoutes(`api:${pagesDir}:${JSON.stringify(matcher.extensions)}`, () => scanApiRoutes(pagesDir, matcher));
119
+ }
120
+ async function getCachedRoutes(cacheKey, scan) {
130
121
  const cached = routeCache.get(cacheKey);
131
- if (cached) return cached.promise;
132
- const promise = scanApiRoutes(pagesDir, matcher);
133
- routeCache.set(cacheKey, {
134
- routes: [],
135
- promise
136
- });
137
- const routes = await promise;
138
- routeCache.set(cacheKey, {
139
- routes,
140
- promise
141
- });
142
- return routes;
122
+ if (cached) return cached;
123
+ const promise = (async () => {
124
+ while (true) {
125
+ const scanGeneration = routeCacheGeneration;
126
+ const routes = await scan();
127
+ if (scanGeneration === routeCacheGeneration) return routes;
128
+ }
129
+ })();
130
+ routeCache.set(cacheKey, promise);
131
+ try {
132
+ return await promise;
133
+ } catch (error) {
134
+ if (routeCache.get(cacheKey) === promise) routeCache.delete(cacheKey);
135
+ throw error;
136
+ }
143
137
  }
144
138
  async function scanApiRoutes(pagesDir, matcher) {
145
139
  const apiDir = path.join(pagesDir, "api");
@@ -23,5 +23,18 @@ declare function matchRouteWithTrie<R extends {
23
23
  route: R;
24
24
  params: Record<string, string | string[]>;
25
25
  } | null;
26
+ /**
27
+ * Match a filesystem route against the request's raw encoded pathname.
28
+ *
29
+ * Next.js compares static route identity before decoding, so `/%61bout` does
30
+ * not select the filesystem route `/about`. Dynamic captures are still decoded
31
+ * exactly once by `trieMatch`, matching `getRouteMatcher` upstream.
32
+ */
33
+ declare function matchRouteWithTrieRawPathname<R extends {
34
+ patternParts: string[];
35
+ }>(url: string, routes: R[], cache: RouteTrieCache<R>): {
36
+ route: R;
37
+ params: Record<string, string | string[]>;
38
+ } | null;
26
39
  //#endregion
27
- export { createRouteTrieCache, matchRouteWithTrie };
40
+ export { createRouteTrieCache, matchRouteWithTrie, matchRouteWithTrieRawPathname };
@@ -38,5 +38,17 @@ function matchRouteWithTrie(url, routes, cache) {
38
38
  const urlParts = normalizedUrl.split("/").filter(Boolean);
39
39
  return trieMatch(getOrBuildTrie(cache, routes), urlParts);
40
40
  }
41
+ /**
42
+ * Match a filesystem route against the request's raw encoded pathname.
43
+ *
44
+ * Next.js compares static route identity before decoding, so `/%61bout` does
45
+ * not select the filesystem route `/about`. Dynamic captures are still decoded
46
+ * exactly once by `trieMatch`, matching `getRouteMatcher` upstream.
47
+ */
48
+ function matchRouteWithTrieRawPathname(url, routes, cache) {
49
+ const pathname = url.split("?")[0];
50
+ const urlParts = (pathname === "/" ? "/" : pathname.replace(/\/$/, "")).split("/").filter(Boolean);
51
+ return trieMatch(getOrBuildTrie(cache, routes), urlParts);
52
+ }
41
53
  //#endregion
42
- export { createRouteTrieCache, matchRouteWithTrie };
54
+ export { createRouteTrieCache, matchRouteWithTrie, matchRouteWithTrieRawPathname };
@@ -4,6 +4,7 @@ declare function routePatternParts(pathname: string): string[];
4
4
  declare function routePattern(pathname: string): string;
5
5
  declare function fillRoutePatternSegments(pathname: string, params: RoutePatternParams): string | null;
6
6
  declare function matchRoutePattern(urlParts: readonly string[], patternParts: readonly string[]): RoutePatternParams | null;
7
+ declare function matchRoutePatternRaw(urlParts: readonly string[], patternParts: readonly string[]): RoutePatternParams | null;
7
8
  declare function matchRoutePatternPrefix(pathParts: readonly string[], patternParts: readonly string[]): boolean;
8
9
  declare function matchRoutePatternWithOptionalDynamicSegments(pathParts: readonly string[], patternParts: readonly string[]): boolean;
9
10
  /**
@@ -61,4 +62,4 @@ declare function normalizeStaticPathname(pathname: string): string;
61
62
  */
62
63
  declare function normalizeStaticPathsEntry(entry: StaticPathsEntry, routePattern: string): NormalizedStaticPathsEntry;
63
64
  //#endregion
64
- export { RoutePatternParams, StaticPathsEntry, fillRoutePatternSegments, matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments, normalizeStaticPathname, normalizeStaticPathsEntry, routePattern, routePatternParts };
65
+ export { RoutePatternParams, StaticPathsEntry, fillRoutePatternSegments, matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternRaw, matchRoutePatternWithOptionalDynamicSegments, normalizeStaticPathname, normalizeStaticPathsEntry, routePattern, routePatternParts };
@@ -56,6 +56,11 @@ function fillRoutePatternSegments(pathname, params) {
56
56
  return resolvedSegments.length > 0 ? `/${resolvedSegments.join("/")}` : "/";
57
57
  }
58
58
  function matchRoutePattern(urlParts, patternParts) {
59
+ const params = matchRoutePatternRaw(urlParts, patternParts);
60
+ if (params) decodeMatchedParams(params);
61
+ return params;
62
+ }
63
+ function matchRoutePatternRaw(urlParts, patternParts) {
59
64
  const params = Object.create(null);
60
65
  function matchFrom(urlIndex, patternIndex) {
61
66
  if (patternIndex === patternParts.length) return urlIndex === urlParts.length;
@@ -83,9 +88,7 @@ function matchRoutePattern(urlParts, patternParts) {
83
88
  if (urlIndex >= urlParts.length || urlParts[urlIndex] !== patternPart) return false;
84
89
  return matchFrom(urlIndex + 1, patternIndex + 1);
85
90
  }
86
- if (!matchFrom(0, 0)) return null;
87
- decodeMatchedParams(params);
88
- return params;
91
+ return matchFrom(0, 0) ? params : null;
89
92
  }
90
93
  function matchRoutePatternPrefix(pathParts, patternParts) {
91
94
  let pathIndex = 0;
@@ -162,4 +165,4 @@ function normalizeStaticPathsEntry(entry, routePattern) {
162
165
  return { params };
163
166
  }
164
167
  //#endregion
165
- export { fillRoutePatternSegments, matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments, normalizeStaticPathname, normalizeStaticPathsEntry, routePattern, routePatternParts };
168
+ export { fillRoutePatternSegments, matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternRaw, matchRoutePatternWithOptionalDynamicSegments, normalizeStaticPathname, normalizeStaticPathsEntry, routePattern, routePatternParts };
@@ -63,5 +63,9 @@ declare function trieMatch<R>(root: TrieNode<R>, urlParts: string[]): {
63
63
  route: R;
64
64
  params: Record<string, string | string[]>;
65
65
  } | null;
66
+ declare function trieMatchRaw<R>(root: TrieNode<R>, urlParts: string[]): {
67
+ route: R;
68
+ params: Record<string, string | string[]>;
69
+ } | null;
66
70
  //#endregion
67
- export { TrieNode, buildRouteTrie, trieMatch };
71
+ export { TrieNode, buildRouteTrie, trieMatch, trieMatchRaw };
@@ -92,10 +92,13 @@ function buildRouteTrie(routes) {
92
92
  * @returns Match result with route and extracted params, or null
93
93
  */
94
94
  function trieMatch(root, urlParts) {
95
- const result = match(root, urlParts, 0, []);
95
+ const result = trieMatchRaw(root, urlParts);
96
96
  if (result) decodeMatchedParams(result.params);
97
97
  return result;
98
98
  }
99
+ function trieMatchRaw(root, urlParts) {
100
+ return match(root, urlParts, 0, []);
101
+ }
99
102
  function match(node, urlParts, index, entries) {
100
103
  if (index === urlParts.length) {
101
104
  if (node.route !== null) return {
@@ -140,4 +143,4 @@ function match(node, urlParts, index, entries) {
140
143
  return null;
141
144
  }
142
145
  //#endregion
143
- export { buildRouteTrie, trieMatch };
146
+ export { buildRouteTrie, trieMatch, trieMatchRaw };