vinext 0.0.28 → 0.0.30

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 (173) hide show
  1. package/dist/build/report.d.ts +117 -0
  2. package/dist/build/report.d.ts.map +1 -0
  3. package/dist/build/report.js +303 -0
  4. package/dist/build/report.js.map +1 -0
  5. package/dist/check.d.ts.map +1 -1
  6. package/dist/check.js +11 -7
  7. package/dist/check.js.map +1 -1
  8. package/dist/cli.js +106 -9
  9. package/dist/cli.js.map +1 -1
  10. package/dist/cloudflare/kv-cache-handler.d.ts.map +1 -1
  11. package/dist/cloudflare/kv-cache-handler.js +58 -42
  12. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  13. package/dist/cloudflare/tpr.d.ts +10 -0
  14. package/dist/cloudflare/tpr.d.ts.map +1 -1
  15. package/dist/cloudflare/tpr.js +36 -41
  16. package/dist/cloudflare/tpr.js.map +1 -1
  17. package/dist/config/next-config.d.ts.map +1 -1
  18. package/dist/config/next-config.js +16 -0
  19. package/dist/config/next-config.js.map +1 -1
  20. package/dist/entries/app-rsc-entry.d.ts +1 -1
  21. package/dist/entries/app-rsc-entry.d.ts.map +1 -1
  22. package/dist/entries/app-rsc-entry.js +225 -186
  23. package/dist/entries/app-rsc-entry.js.map +1 -1
  24. package/dist/entries/pages-server-entry.d.ts.map +1 -1
  25. package/dist/entries/pages-server-entry.js +192 -91
  26. package/dist/entries/pages-server-entry.js.map +1 -1
  27. package/dist/index.d.ts +17 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +121 -40
  30. package/dist/index.js.map +1 -1
  31. package/dist/routing/app-router.d.ts +2 -0
  32. package/dist/routing/app-router.d.ts.map +1 -1
  33. package/dist/routing/app-router.js +131 -104
  34. package/dist/routing/app-router.js.map +1 -1
  35. package/dist/routing/pages-router.d.ts.map +1 -1
  36. package/dist/routing/pages-router.js +17 -57
  37. package/dist/routing/pages-router.js.map +1 -1
  38. package/dist/routing/route-trie.d.ts +57 -0
  39. package/dist/routing/route-trie.d.ts.map +1 -0
  40. package/dist/routing/route-trie.js +160 -0
  41. package/dist/routing/route-trie.js.map +1 -0
  42. package/dist/routing/route-validation.d.ts.map +1 -1
  43. package/dist/routing/route-validation.js +13 -1
  44. package/dist/routing/route-validation.js.map +1 -1
  45. package/dist/routing/utils.d.ts +19 -0
  46. package/dist/routing/utils.d.ts.map +1 -1
  47. package/dist/routing/utils.js +47 -0
  48. package/dist/routing/utils.js.map +1 -1
  49. package/dist/server/api-handler.d.ts.map +1 -1
  50. package/dist/server/api-handler.js +28 -13
  51. package/dist/server/api-handler.js.map +1 -1
  52. package/dist/server/app-router-entry.js +1 -1
  53. package/dist/server/app-router-entry.js.map +1 -1
  54. package/dist/server/dev-server.d.ts +2 -1
  55. package/dist/server/dev-server.d.ts.map +1 -1
  56. package/dist/server/dev-server.js +167 -115
  57. package/dist/server/dev-server.js.map +1 -1
  58. package/dist/server/image-optimization.d.ts.map +1 -1
  59. package/dist/server/image-optimization.js +24 -12
  60. package/dist/server/image-optimization.js.map +1 -1
  61. package/dist/server/instrumentation.d.ts.map +1 -1
  62. package/dist/server/instrumentation.js +17 -8
  63. package/dist/server/instrumentation.js.map +1 -1
  64. package/dist/server/isr-cache.d.ts.map +1 -1
  65. package/dist/server/isr-cache.js +8 -3
  66. package/dist/server/isr-cache.js.map +1 -1
  67. package/dist/server/metadata-routes.d.ts.map +1 -1
  68. package/dist/server/metadata-routes.js +56 -18
  69. package/dist/server/metadata-routes.js.map +1 -1
  70. package/dist/server/middleware-codegen.d.ts +10 -0
  71. package/dist/server/middleware-codegen.d.ts.map +1 -1
  72. package/dist/server/middleware-codegen.js +76 -4
  73. package/dist/server/middleware-codegen.js.map +1 -1
  74. package/dist/server/middleware.d.ts.map +1 -1
  75. package/dist/server/middleware.js +52 -7
  76. package/dist/server/middleware.js.map +1 -1
  77. package/dist/server/pages-i18n.d.ts +50 -0
  78. package/dist/server/pages-i18n.d.ts.map +1 -0
  79. package/dist/server/pages-i18n.js +152 -0
  80. package/dist/server/pages-i18n.js.map +1 -0
  81. package/dist/server/prod-server.d.ts +8 -2
  82. package/dist/server/prod-server.d.ts.map +1 -1
  83. package/dist/server/prod-server.js +60 -20
  84. package/dist/server/prod-server.js.map +1 -1
  85. package/dist/shims/cache-runtime.d.ts +3 -0
  86. package/dist/shims/cache-runtime.d.ts.map +1 -1
  87. package/dist/shims/cache-runtime.js +22 -5
  88. package/dist/shims/cache-runtime.js.map +1 -1
  89. package/dist/shims/cache.d.ts +3 -0
  90. package/dist/shims/cache.d.ts.map +1 -1
  91. package/dist/shims/cache.js +21 -12
  92. package/dist/shims/cache.js.map +1 -1
  93. package/dist/shims/constants.d.ts.map +1 -1
  94. package/dist/shims/constants.js +0 -1
  95. package/dist/shims/constants.js.map +1 -1
  96. package/dist/shims/fetch-cache.d.ts +14 -0
  97. package/dist/shims/fetch-cache.d.ts.map +1 -1
  98. package/dist/shims/fetch-cache.js +102 -37
  99. package/dist/shims/fetch-cache.js.map +1 -1
  100. package/dist/shims/head-state.d.ts +4 -0
  101. package/dist/shims/head-state.d.ts.map +1 -1
  102. package/dist/shims/head-state.js +14 -11
  103. package/dist/shims/head-state.js.map +1 -1
  104. package/dist/shims/head.d.ts +4 -2
  105. package/dist/shims/head.d.ts.map +1 -1
  106. package/dist/shims/head.js +162 -52
  107. package/dist/shims/head.js.map +1 -1
  108. package/dist/shims/headers.d.ts +8 -1
  109. package/dist/shims/headers.d.ts.map +1 -1
  110. package/dist/shims/headers.js +23 -34
  111. package/dist/shims/headers.js.map +1 -1
  112. package/dist/shims/i18n-context.d.ts +27 -0
  113. package/dist/shims/i18n-context.d.ts.map +1 -0
  114. package/dist/shims/i18n-context.js +57 -0
  115. package/dist/shims/i18n-context.js.map +1 -0
  116. package/dist/shims/i18n-state.d.ts +20 -0
  117. package/dist/shims/i18n-state.d.ts.map +1 -0
  118. package/dist/shims/i18n-state.js +53 -0
  119. package/dist/shims/i18n-state.js.map +1 -0
  120. package/dist/shims/image.d.ts +2 -0
  121. package/dist/shims/image.d.ts.map +1 -1
  122. package/dist/shims/image.js +14 -6
  123. package/dist/shims/image.js.map +1 -1
  124. package/dist/shims/internal/utils.d.ts +1 -0
  125. package/dist/shims/internal/utils.d.ts.map +1 -1
  126. package/dist/shims/internal/utils.js.map +1 -1
  127. package/dist/shims/link.d.ts.map +1 -1
  128. package/dist/shims/link.js +38 -54
  129. package/dist/shims/link.js.map +1 -1
  130. package/dist/shims/metadata.d.ts +78 -22
  131. package/dist/shims/metadata.d.ts.map +1 -1
  132. package/dist/shims/metadata.js +96 -28
  133. package/dist/shims/metadata.js.map +1 -1
  134. package/dist/shims/navigation-state.d.ts +14 -0
  135. package/dist/shims/navigation-state.d.ts.map +1 -1
  136. package/dist/shims/navigation-state.js +33 -15
  137. package/dist/shims/navigation-state.js.map +1 -1
  138. package/dist/shims/navigation.d.ts +2 -0
  139. package/dist/shims/navigation.d.ts.map +1 -1
  140. package/dist/shims/navigation.js +80 -51
  141. package/dist/shims/navigation.js.map +1 -1
  142. package/dist/shims/request-context.d.ts.map +1 -1
  143. package/dist/shims/request-context.js +9 -0
  144. package/dist/shims/request-context.js.map +1 -1
  145. package/dist/shims/request-state-types.d.ts +11 -0
  146. package/dist/shims/request-state-types.d.ts.map +1 -0
  147. package/dist/shims/request-state-types.js +2 -0
  148. package/dist/shims/request-state-types.js.map +1 -0
  149. package/dist/shims/router-state.d.ts +11 -0
  150. package/dist/shims/router-state.d.ts.map +1 -1
  151. package/dist/shims/router-state.js +10 -8
  152. package/dist/shims/router-state.js.map +1 -1
  153. package/dist/shims/router.d.ts +4 -0
  154. package/dist/shims/router.d.ts.map +1 -1
  155. package/dist/shims/router.js +130 -40
  156. package/dist/shims/router.js.map +1 -1
  157. package/dist/shims/server.d.ts +8 -1
  158. package/dist/shims/server.d.ts.map +1 -1
  159. package/dist/shims/server.js +52 -6
  160. package/dist/shims/server.js.map +1 -1
  161. package/dist/shims/unified-request-context.d.ts +66 -0
  162. package/dist/shims/unified-request-context.d.ts.map +1 -0
  163. package/dist/shims/unified-request-context.js +116 -0
  164. package/dist/shims/unified-request-context.js.map +1 -0
  165. package/dist/shims/url-utils.d.ts +20 -6
  166. package/dist/shims/url-utils.d.ts.map +1 -1
  167. package/dist/shims/url-utils.js +79 -0
  168. package/dist/shims/url-utils.js.map +1 -1
  169. package/dist/utils/domain-locale.d.ts +18 -0
  170. package/dist/utils/domain-locale.d.ts.map +1 -0
  171. package/dist/utils/domain-locale.js +64 -0
  172. package/dist/utils/domain-locale.js.map +1 -0
  173. package/package.json +2 -2
@@ -12,6 +12,8 @@ export interface InterceptingRoute {
12
12
  export interface ParallelSlot {
13
13
  /** Slot name (e.g. "team" from @team) */
14
14
  name: string;
15
+ /** Absolute path to the @slot directory that owns this slot. Internal routing metadata. */
16
+ ownerDir: string;
15
17
  /** Absolute path to the slot's page component */
16
18
  pagePath: string | null;
17
19
  /** Absolute path to the slot's default.tsx fallback */
@@ -1 +1 @@
1
- {"version":3,"file":"app-router.d.ts","sourceRoot":"","sources":["../../src/routing/app-router.ts"],"names":[],"mappings":"AAkBA,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAG3B,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,uDAAuD;IACvD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,wEAAwE;IACxE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oDAAoD;IACpD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kDAAkD;IAClD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,2CAA2C;IAC3C,kBAAkB,EAAE,iBAAiB,EAAE,CAAC;IACxC;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yDAAyD;IACzD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,qDAAqD;IACrD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6EAA6E;IAC7E,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,mFAAmF;IACnF,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,6BAA6B;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iDAAiD;IACjD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;;;OAMG;IACH,gBAAgB,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IACpC,mEAAmE;IACnE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;OAKG;IACH,aAAa,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IACjC,qCAAqC;IACrC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wCAAwC;IACxC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;;OAIG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;;OAMG;IACH,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,kFAAkF;IAClF,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAOD,wBAAgB,uBAAuB,IAAI,IAAI,CAI9C;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,EAClC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAyDrB;AAmvBD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,QAAQ,EAAE,GACjB;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAA;CAAE,GAAG,IAAI,CAoBvE"}
1
+ {"version":3,"file":"app-router.d.ts","sourceRoot":"","sources":["../../src/routing/app-router.ts"],"names":[],"mappings":"AAkBA,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAI3B,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,2FAA2F;IAC3F,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,uDAAuD;IACvD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,wEAAwE;IACxE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oDAAoD;IACpD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kDAAkD;IAClD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,2CAA2C;IAC3C,kBAAkB,EAAE,iBAAiB,EAAE,CAAC;IACxC;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yDAAyD;IACzD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,qDAAqD;IACrD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6EAA6E;IAC7E,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,mFAAmF;IACnF,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,6BAA6B;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iDAAiD;IACjD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;;;OAMG;IACH,gBAAgB,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IACpC,mEAAmE;IACnE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;OAKG;IACH,aAAa,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IACjC,qCAAqC;IACrC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wCAAwC;IACxC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;;OAIG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;;OAMG;IACH,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,kFAAkF;IAClF,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAOD,wBAAgB,uBAAuB,IAAI,IAAI,CAI9C;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,EAClC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAkDrB;AA40BD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,QAAQ,EAAE,GACjB;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAA;CAAE,GAAG,IAAI,CASvE"}
@@ -15,9 +15,10 @@
15
15
  */
16
16
  import path from "node:path";
17
17
  import fs from "node:fs";
18
- import { compareRoutes } from "./utils.js";
18
+ import { compareRoutes, decodeRouteSegment, normalizePathnameForRouteMatch } from "./utils.js";
19
19
  import { createValidFileMatcher, scanWithExtensions, } from "./file-matcher.js";
20
20
  import { validateRoutePatterns } from "./route-validation.js";
21
+ import { buildRouteTrie, trieMatch } from "./route-trie.js";
21
22
  // Cache for app routes
22
23
  let cachedRoutes = null;
23
24
  let cachedAppDir = null;
@@ -38,16 +39,18 @@ export async function appRouter(appDir, pageExtensions, matcher) {
38
39
  }
39
40
  // Find all page.tsx and route.ts files, excluding @slot directories
40
41
  // (slot pages are not standalone routes — they're rendered as props of their parent layout)
42
+ // and _private folders (Next.js convention for colocated non-route files).
41
43
  const routes = [];
44
+ const excludeDir = (name) => name.startsWith("@") || name.startsWith("_");
42
45
  // Process page files in a single pass
43
46
  // Use function form of exclude for Node < 22.14 compatibility (string arrays require >= 22.14)
44
- for await (const file of scanWithExtensions("**/page", appDir, matcher.extensions, (name) => name.startsWith("@"))) {
47
+ for await (const file of scanWithExtensions("**/page", appDir, matcher.extensions, excludeDir)) {
45
48
  const route = fileToAppRoute(file, appDir, "page", matcher);
46
49
  if (route)
47
50
  routes.push(route);
48
51
  }
49
52
  // Process route handler files (API routes) in a single pass
50
- for await (const file of scanWithExtensions("**/route", appDir, matcher.extensions, (name) => name.startsWith("@"))) {
53
+ for await (const file of scanWithExtensions("**/route", appDir, matcher.extensions, excludeDir)) {
51
54
  const route = fileToAppRoute(file, appDir, "route", matcher);
52
55
  if (route)
53
56
  routes.push(route);
@@ -80,7 +83,16 @@ export async function appRouter(appDir, pageExtensions, matcher) {
80
83
  */
81
84
  function discoverSlotSubRoutes(routes, _appDir, matcher) {
82
85
  const syntheticRoutes = [];
83
- const existingPatterns = new Set(routes.map((r) => r.pattern));
86
+ // O(1) lookup for existing routes by pattern — avoids O(n) routes.find() per sub-path per parent.
87
+ // Updated as new synthetic routes are pushed so that later parents can see earlier synthetic entries.
88
+ const routesByPattern = new Map(routes.map((r) => [r.pattern, r]));
89
+ const slotKey = (slotName, ownerDir) => `${slotName}\u0000${ownerDir}`;
90
+ const applySlotSubPages = (route, slotPages) => {
91
+ route.parallelSlots = route.parallelSlots.map((slot) => ({
92
+ ...slot,
93
+ pagePath: slotPages.get(slotKey(slot.name, slot.ownerDir)) ?? slot.pagePath,
94
+ }));
95
+ };
84
96
  for (const parentRoute of routes) {
85
97
  if (parentRoute.parallelSlots.length === 0)
86
98
  continue;
@@ -88,7 +100,8 @@ function discoverSlotSubRoutes(routes, _appDir, matcher) {
88
100
  continue;
89
101
  const parentPageDir = path.dirname(parentRoute.pagePath);
90
102
  // Collect sub-paths from all slots.
91
- // Map: relative sub-path (e.g., "demographics") -> Map<slotName, pagePath>
103
+ // Map: normalized visible sub-path -> slot pages, raw filesystem segments (for routeSegments),
104
+ // and the pre-computed convertedSubRoute (to avoid a redundant re-conversion in the merge loop).
92
105
  const subPathMap = new Map();
93
106
  for (const slot of parentRoute.parallelSlots) {
94
107
  const slotDir = path.join(parentPageDir, `@${slot.name}`);
@@ -96,37 +109,55 @@ function discoverSlotSubRoutes(routes, _appDir, matcher) {
96
109
  continue;
97
110
  const subPages = findSlotSubPages(slotDir, matcher);
98
111
  for (const { relativePath, pagePath } of subPages) {
99
- if (!subPathMap.has(relativePath)) {
100
- subPathMap.set(relativePath, new Map());
112
+ const subSegments = relativePath.split(path.sep);
113
+ const convertedSubRoute = convertSegmentsToRouteParts(subSegments);
114
+ if (!convertedSubRoute)
115
+ continue;
116
+ const { urlSegments } = convertedSubRoute;
117
+ const normalizedSubPath = urlSegments.join("/");
118
+ let subPathEntry = subPathMap.get(normalizedSubPath);
119
+ if (!subPathEntry) {
120
+ subPathEntry = {
121
+ rawSegments: subSegments,
122
+ converted: convertedSubRoute,
123
+ slotPages: new Map(),
124
+ };
125
+ subPathMap.set(normalizedSubPath, subPathEntry);
126
+ }
127
+ const slotId = slotKey(slot.name, slot.ownerDir);
128
+ const existingSlotPage = subPathEntry.slotPages.get(slotId);
129
+ if (existingSlotPage) {
130
+ const pattern = joinRoutePattern(parentRoute.pattern, normalizedSubPath);
131
+ throw new Error(`You cannot have two routes that resolve to the same path ("${pattern}").`);
101
132
  }
102
- subPathMap.get(relativePath).set(slot.name, pagePath);
133
+ subPathEntry.slotPages.set(slotId, pagePath);
103
134
  }
104
135
  }
105
136
  if (subPathMap.size === 0)
106
137
  continue;
107
138
  // Find the default.tsx for the children slot at the parent directory
108
139
  const childrenDefault = findFile(parentPageDir, "default", matcher);
109
- for (const [subPath, slotPages] of subPathMap) {
110
- // Convert sub-path segments to URL pattern parts
111
- const subSegments = subPath.split(path.sep);
112
- const convertedSubRoute = convertSegmentsToRouteParts(subSegments);
113
- if (!convertedSubRoute)
114
- continue;
140
+ if (!childrenDefault)
141
+ continue;
142
+ for (const { rawSegments, converted: convertedSubRoute, slotPages } of subPathMap.values()) {
115
143
  const { urlSegments: urlParts, params: subParams, isDynamic: subIsDynamic, } = convertedSubRoute;
116
144
  const subUrlPath = urlParts.join("/");
117
- const pattern = parentRoute.pattern === "/" ? "/" + subUrlPath : parentRoute.pattern + "/" + subUrlPath;
118
- // Skip if this pattern already exists as a regular route
119
- if (existingPatterns.has(pattern))
120
- continue;
121
- if (syntheticRoutes.some((r) => r.pattern === pattern))
145
+ const pattern = joinRoutePattern(parentRoute.pattern, subUrlPath);
146
+ const existingRoute = routesByPattern.get(pattern);
147
+ if (existingRoute) {
148
+ if (existingRoute.routePath && !existingRoute.pagePath) {
149
+ throw new Error(`You cannot have two routes that resolve to the same path ("${pattern}").`);
150
+ }
151
+ applySlotSubPages(existingRoute, slotPages);
122
152
  continue;
153
+ }
123
154
  // Build parallel slots for this sub-route: matching slots get the sub-page,
124
155
  // non-matching slots get null pagePath (rendering falls back to defaultPath)
125
156
  const subSlots = parentRoute.parallelSlots.map((slot) => ({
126
157
  ...slot,
127
- pagePath: slotPages.get(slot.name) || null,
158
+ pagePath: slotPages.get(slotKey(slot.name, slot.ownerDir)) || null,
128
159
  }));
129
- syntheticRoutes.push({
160
+ const newRoute = {
130
161
  pattern,
131
162
  pagePath: childrenDefault, // children slot uses parent's default.tsx as page
132
163
  routePath: null,
@@ -140,12 +171,14 @@ function discoverSlotSubRoutes(routes, _appDir, matcher) {
140
171
  notFoundPaths: parentRoute.notFoundPaths,
141
172
  forbiddenPath: parentRoute.forbiddenPath,
142
173
  unauthorizedPath: parentRoute.unauthorizedPath,
143
- routeSegments: [...parentRoute.routeSegments, ...subSegments],
174
+ routeSegments: [...parentRoute.routeSegments, ...rawSegments],
144
175
  layoutTreePositions: parentRoute.layoutTreePositions,
145
176
  isDynamic: parentRoute.isDynamic || subIsDynamic,
146
177
  params: [...parentRoute.params, ...subParams],
147
178
  patternParts: [...parentRoute.patternParts, ...urlParts],
148
- });
179
+ };
180
+ syntheticRoutes.push(newRoute);
181
+ routesByPattern.set(pattern, newRoute);
149
182
  }
150
183
  }
151
184
  return syntheticRoutes;
@@ -444,6 +477,7 @@ function discoverParallelSlots(dir, appDir, matcher) {
444
477
  continue;
445
478
  slots.push({
446
479
  name: slotName,
480
+ ownerDir: slotDir,
447
481
  pagePath,
448
482
  defaultPath,
449
483
  layoutPath: findFile(slotDir, "layout", matcher),
@@ -494,6 +528,9 @@ function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher
494
528
  for (const entry of entries) {
495
529
  if (!entry.isDirectory())
496
530
  continue;
531
+ // Skip private folders (prefixed with _)
532
+ if (entry.name.startsWith("_"))
533
+ continue;
497
534
  // Check if this directory name starts with an interception convention
498
535
  const interceptMatch = matchInterceptConvention(entry.name);
499
536
  if (interceptMatch) {
@@ -546,38 +583,69 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
546
583
  for (const entry of entries) {
547
584
  if (!entry.isDirectory())
548
585
  continue;
586
+ // Skip private folders (prefixed with _)
587
+ if (entry.name.startsWith("_"))
588
+ continue;
549
589
  collectInterceptingPages(path.join(currentDir, entry.name), interceptRoot, convention, interceptSegment, routeDir, appDir, results, matcher);
550
590
  }
551
591
  }
592
+ /**
593
+ * Check whether a path segment is invisible in the URL (route groups, parallel slots, ".").
594
+ *
595
+ * Used by computeInterceptTarget, convertSegmentsToRouteParts, and
596
+ * hasRemainingVisibleSegments — keep this the single source of truth.
597
+ */
598
+ function isInvisibleSegment(segment) {
599
+ if (segment === ".")
600
+ return true;
601
+ if (segment.startsWith("(") && segment.endsWith(")"))
602
+ return true;
603
+ if (segment.startsWith("@"))
604
+ return true;
605
+ return false;
606
+ }
552
607
  /**
553
608
  * Compute the target URL pattern for an intercepting route.
554
609
  *
610
+ * Interception conventions (..), (..)(..)" climb by *visible route segments*
611
+ * (not filesystem directories). Route groups like (marketing) and parallel
612
+ * slots like @modal are invisible and must be skipped when counting levels.
613
+ *
555
614
  * - (.) same level: resolve relative to routeDir
556
- * - (..) one level up: resolve relative to parent of routeDir
557
- * - (..)(..)" two levels up: resolve relative to grandparent of routeDir
615
+ * - (..) one level up: climb 1 visible segment
616
+ * - (..)(..) two levels up: climb 2 visible segments
558
617
  * - (...) root: resolve from appDir
559
618
  */
560
619
  function computeInterceptTarget(convention, interceptSegment, currentDir, interceptRoot, routeDir, appDir) {
561
- // Determine the base directory for target resolution
562
- let baseDir;
620
+ // Determine the base segments for target resolution.
621
+ // We work on route segments (not filesystem paths) so that route groups
622
+ // and parallel slots are properly skipped when climbing.
623
+ const routeSegments = path.relative(appDir, routeDir).split(path.sep).filter(Boolean);
624
+ let baseParts;
563
625
  switch (convention) {
564
626
  case ".":
565
- baseDir = routeDir;
627
+ baseParts = routeSegments;
566
628
  break;
567
629
  case "..":
568
- baseDir = path.dirname(routeDir);
569
- break;
570
- case "../..":
571
- baseDir = path.dirname(path.dirname(routeDir));
630
+ case "../..": {
631
+ const levelsToClimb = convention === ".." ? 1 : 2;
632
+ let climbed = 0;
633
+ let cutIndex = routeSegments.length;
634
+ while (cutIndex > 0 && climbed < levelsToClimb) {
635
+ cutIndex--;
636
+ if (!isInvisibleSegment(routeSegments[cutIndex])) {
637
+ climbed++;
638
+ }
639
+ }
640
+ baseParts = routeSegments.slice(0, cutIndex);
572
641
  break;
642
+ }
573
643
  case "...":
574
- baseDir = appDir;
644
+ baseParts = [];
575
645
  break;
576
646
  default:
577
647
  return null;
578
648
  }
579
- // Build the target URL segments from baseDir relative to appDir
580
- const baseParts = path.relative(appDir, baseDir).split(path.sep).filter(Boolean);
581
649
  // Add the intercept segment and any nested path segments
582
650
  const nestedParts = path.relative(interceptRoot, currentDir).split(path.sep).filter(Boolean);
583
651
  const allSegments = [...baseParts, interceptSegment, ...nestedParts];
@@ -600,19 +668,18 @@ function findFile(dir, name, matcher) {
600
668
  }
601
669
  return null;
602
670
  }
671
+ /**
672
+ * Convert filesystem path segments to URL route parts, skipping invisible segments
673
+ * (route groups, @slots, ".") and converting dynamic segment syntax to Express-style
674
+ * patterns (e.g. "[id]" → ":id", "[...slug]" → ":slug+").
675
+ */
603
676
  function convertSegmentsToRouteParts(segments) {
604
677
  const urlSegments = [];
605
678
  const params = [];
606
679
  let isDynamic = false;
607
680
  for (let i = 0; i < segments.length; i++) {
608
681
  const segment = segments[i];
609
- if (segment === ".")
610
- continue;
611
- // Route groups are transparent in the URL.
612
- if (segment.startsWith("(") && segment.endsWith(")"))
613
- continue;
614
- // Parallel slots are also transparent.
615
- if (segment.startsWith("@"))
682
+ if (isInvisibleSegment(segment))
616
683
  continue;
617
684
  // Catch-all segments are only valid in terminal URL position.
618
685
  const catchAllMatch = segment.match(/^\[\.\.\.([\w-]+)\]$/);
@@ -640,82 +707,42 @@ function convertSegmentsToRouteParts(segments) {
640
707
  urlSegments.push(`:${dynamicMatch[1]}`);
641
708
  continue;
642
709
  }
643
- try {
644
- urlSegments.push(decodeURIComponent(segment));
645
- }
646
- catch {
647
- urlSegments.push(segment);
648
- }
710
+ urlSegments.push(decodeRouteSegment(segment));
649
711
  }
650
712
  return { urlSegments, params, isDynamic };
651
713
  }
652
714
  function hasRemainingVisibleSegments(segments, startIndex) {
653
715
  for (let i = startIndex; i < segments.length; i++) {
654
- const segment = segments[i];
655
- if (segment.startsWith("(") && segment.endsWith(")"))
656
- continue;
657
- if (segment.startsWith("@"))
658
- continue;
659
- return true;
716
+ if (!isInvisibleSegment(segments[i]))
717
+ return true;
660
718
  }
661
719
  return false;
662
720
  }
721
+ // Trie cache — keyed by route array identity (same array = same trie)
722
+ const appTrieCache = new WeakMap();
723
+ function getOrBuildAppTrie(routes) {
724
+ let trie = appTrieCache.get(routes);
725
+ if (!trie) {
726
+ trie = buildRouteTrie(routes);
727
+ appTrieCache.set(routes, trie);
728
+ }
729
+ return trie;
730
+ }
731
+ function joinRoutePattern(basePattern, subPath) {
732
+ if (!subPath)
733
+ return basePattern;
734
+ return basePattern === "/" ? `/${subPath}` : `${basePattern}/${subPath}`;
735
+ }
663
736
  /**
664
737
  * Match a URL against App Router routes.
665
738
  */
666
739
  export function matchAppRoute(url, routes) {
667
740
  const pathname = url.split("?")[0];
668
741
  let normalizedUrl = pathname === "/" ? "/" : pathname.replace(/\/$/, "");
669
- try {
670
- normalizedUrl = decodeURIComponent(normalizedUrl);
671
- }
672
- catch {
673
- /* malformed percent-encoding — match as-is */
674
- }
675
- // Split URL once, reuse across all route match attempts
742
+ normalizedUrl = normalizePathnameForRouteMatch(normalizedUrl);
743
+ // Split URL once, look up via trie
676
744
  const urlParts = normalizedUrl.split("/").filter(Boolean);
677
- for (const route of routes) {
678
- const params = matchPattern(urlParts, route.patternParts);
679
- if (params !== null) {
680
- return { route, params };
681
- }
682
- }
683
- return null;
684
- }
685
- function matchPattern(urlParts, patternParts) {
686
- const params = Object.create(null);
687
- for (let i = 0; i < patternParts.length; i++) {
688
- const pp = patternParts[i];
689
- if (pp.endsWith("+")) {
690
- if (i !== patternParts.length - 1)
691
- return null;
692
- const paramName = pp.slice(1, -1);
693
- const remaining = urlParts.slice(i);
694
- if (remaining.length === 0)
695
- return null;
696
- params[paramName] = remaining;
697
- return params;
698
- }
699
- if (pp.endsWith("*")) {
700
- if (i !== patternParts.length - 1)
701
- return null;
702
- const paramName = pp.slice(1, -1);
703
- const remaining = urlParts.slice(i);
704
- params[paramName] = remaining;
705
- return params;
706
- }
707
- if (pp.startsWith(":")) {
708
- const paramName = pp.slice(1);
709
- if (i >= urlParts.length)
710
- return null;
711
- params[paramName] = urlParts[i];
712
- continue;
713
- }
714
- if (i >= urlParts.length || urlParts[i] !== pp)
715
- return null;
716
- }
717
- if (urlParts.length !== patternParts.length)
718
- return null;
719
- return params;
745
+ const trie = getOrBuildAppTrie(routes);
746
+ return trieMatch(trie, urlParts);
720
747
  }
721
748
  //# sourceMappingURL=app-router.js.map