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
@@ -1 +1 @@
1
- {"version":3,"file":"router-state.d.ts","sourceRoot":"","sources":["../../src/shims/router-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoCH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAK9E"}
1
+ {"version":3,"file":"router-state.d.ts","sourceRoot":"","sources":["../../src/shims/router-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;CAC/B;AAmBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAW9E"}
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { AsyncLocalStorage } from "node:async_hooks";
11
11
  import { _registerRouterStateAccessors } from "./router.js";
12
+ import { getRequestContext, isInsideUnifiedScope, runWithUnifiedStateMutation, } from "./unified-request-context.js";
12
13
  const _ALS_KEY = Symbol.for("vinext.router.als");
13
14
  const _FALLBACK_KEY = Symbol.for("vinext.router.fallback");
14
15
  const _g = globalThis;
@@ -18,6 +19,9 @@ const _fallbackState = (_g[_FALLBACK_KEY] ??= {
18
19
  ssrContext: null,
19
20
  });
20
21
  function _getState() {
22
+ if (isInsideUnifiedScope()) {
23
+ return getRequestContext();
24
+ }
21
25
  return _als.getStore() ?? _fallbackState;
22
26
  }
23
27
  /**
@@ -26,6 +30,11 @@ function _getState() {
26
30
  * on concurrent runtimes.
27
31
  */
28
32
  export function runWithRouterState(fn) {
33
+ if (isInsideUnifiedScope()) {
34
+ return runWithUnifiedStateMutation((uCtx) => {
35
+ uCtx.ssrContext = null;
36
+ }, fn);
37
+ }
29
38
  const state = {
30
39
  ssrContext: null,
31
40
  };
@@ -39,14 +48,7 @@ _registerRouterStateAccessors({
39
48
  return _getState().ssrContext;
40
49
  },
41
50
  setSSRContext(ctx) {
42
- const state = _als.getStore();
43
- if (state) {
44
- state.ssrContext = ctx;
45
- }
46
- else {
47
- // No ALS scope — fallback for environments without als.run() wrapping.
48
- _fallbackState.ssrContext = ctx;
49
- }
51
+ _getState().ssrContext = ctx;
50
52
  },
51
53
  });
52
54
  //# sourceMappingURL=router-state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"router-state.js","sourceRoot":"","sources":["../../src/shims/router-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAmB5D,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;AACjD,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AAC3D,MAAM,EAAE,GAAG,UAAqD,CAAC;AACjE,MAAM,IAAI,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC;IACxB,IAAI,iBAAiB,EAAe,CAAmC,CAAC;AAE1E,MAAM,cAAc,GAAG,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK;IAC5C,UAAU,EAAE,IAAI;CACK,CAAgB,CAAC;AAExC,SAAS,SAAS;IAChB,OAAO,IAAI,CAAC,QAAQ,EAAE,IAAI,cAAc,CAAC;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAI,EAAwB;IAC5D,MAAM,KAAK,GAAgB;QACzB,UAAU,EAAE,IAAI;KACjB,CAAC;IACF,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED,8EAA8E;AAC9E,+CAA+C;AAC/C,8EAA8E;AAE9E,6BAA6B,CAAC;IAC5B,aAAa;QACX,OAAO,SAAS,EAAE,CAAC,UAAU,CAAC;IAChC,CAAC;IAED,aAAa,CAAC,GAAsB;QAClC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,uEAAuE;YACvE,cAAc,CAAC,UAAU,GAAG,GAAG,CAAC;QAClC,CAAC;IACH,CAAC;CACF,CAAC,CAAC","sourcesContent":["/**\n * Server-only Pages Router state backed by AsyncLocalStorage.\n *\n * Provides request-scoped isolation for SSR context (pathname, query,\n * locale) so concurrent requests on Workers don't share state.\n *\n * This module is server-only — it imports node:async_hooks and must NOT\n * be bundled for the browser.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { _registerRouterStateAccessors } from \"./router.js\";\n\n// ---------------------------------------------------------------------------\n// ALS setup\n// ---------------------------------------------------------------------------\n\ninterface SSRContext {\n pathname: string;\n query: Record<string, string | string[]>;\n asPath: string;\n locale?: string;\n locales?: string[];\n defaultLocale?: string;\n}\n\ninterface RouterState {\n ssrContext: SSRContext | null;\n}\n\nconst _ALS_KEY = Symbol.for(\"vinext.router.als\");\nconst _FALLBACK_KEY = Symbol.for(\"vinext.router.fallback\");\nconst _g = globalThis as unknown as Record<PropertyKey, unknown>;\nconst _als = (_g[_ALS_KEY] ??=\n new AsyncLocalStorage<RouterState>()) as AsyncLocalStorage<RouterState>;\n\nconst _fallbackState = (_g[_FALLBACK_KEY] ??= {\n ssrContext: null,\n} satisfies RouterState) as RouterState;\n\nfunction _getState(): RouterState {\n return _als.getStore() ?? _fallbackState;\n}\n\n/**\n * Run a function within a router state ALS scope.\n * Ensures per-request isolation for Pages Router SSR context\n * on concurrent runtimes.\n */\nexport function runWithRouterState<T>(fn: () => T | Promise<T>): T | Promise<T> {\n const state: RouterState = {\n ssrContext: null,\n };\n return _als.run(state, fn);\n}\n\n// ---------------------------------------------------------------------------\n// Register ALS-backed accessors into router.ts\n// ---------------------------------------------------------------------------\n\n_registerRouterStateAccessors({\n getSSRContext(): SSRContext | null {\n return _getState().ssrContext;\n },\n\n setSSRContext(ctx: SSRContext | null): void {\n const state = _als.getStore();\n if (state) {\n state.ssrContext = ctx;\n } else {\n // No ALS scope — fallback for environments without als.run() wrapping.\n _fallbackState.ssrContext = ctx;\n }\n },\n});\n"]}
1
+ {"version":3,"file":"router-state.js","sourceRoot":"","sources":["../../src/shims/router-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,8BAA8B,CAAC;AAmBtC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;AACjD,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AAC3D,MAAM,EAAE,GAAG,UAAqD,CAAC;AACjE,MAAM,IAAI,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC;IACxB,IAAI,iBAAiB,EAAe,CAAmC,CAAC;AAE1E,MAAM,cAAc,GAAG,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK;IAC5C,UAAU,EAAE,IAAI;CACK,CAAgB,CAAC;AAExC,SAAS,SAAS;IAChB,IAAI,oBAAoB,EAAE,EAAE,CAAC;QAC3B,OAAO,iBAAiB,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC,QAAQ,EAAE,IAAI,cAAc,CAAC;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAI,EAAwB;IAC5D,IAAI,oBAAoB,EAAE,EAAE,CAAC;QAC3B,OAAO,2BAA2B,CAAC,CAAC,IAAI,EAAE,EAAE;YAC1C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzB,CAAC,EAAE,EAAE,CAAC,CAAC;IACT,CAAC;IAED,MAAM,KAAK,GAAgB;QACzB,UAAU,EAAE,IAAI;KACjB,CAAC;IACF,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED,8EAA8E;AAC9E,+CAA+C;AAC/C,8EAA8E;AAE9E,6BAA6B,CAAC;IAC5B,aAAa;QACX,OAAO,SAAS,EAAE,CAAC,UAAU,CAAC;IAChC,CAAC;IAED,aAAa,CAAC,GAAsB;QAClC,SAAS,EAAE,CAAC,UAAU,GAAG,GAAG,CAAC;IAC/B,CAAC;CACF,CAAC,CAAC","sourcesContent":["/**\n * Server-only Pages Router state backed by AsyncLocalStorage.\n *\n * Provides request-scoped isolation for SSR context (pathname, query,\n * locale) so concurrent requests on Workers don't share state.\n *\n * This module is server-only — it imports node:async_hooks and must NOT\n * be bundled for the browser.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { _registerRouterStateAccessors } from \"./router.js\";\nimport {\n getRequestContext,\n isInsideUnifiedScope,\n runWithUnifiedStateMutation,\n} from \"./unified-request-context.js\";\n\n// ---------------------------------------------------------------------------\n// ALS setup\n// ---------------------------------------------------------------------------\n\nexport interface SSRContext {\n pathname: string;\n query: Record<string, string | string[]>;\n asPath: string;\n locale?: string;\n locales?: string[];\n defaultLocale?: string;\n}\n\nexport interface RouterState {\n ssrContext: SSRContext | null;\n}\n\nconst _ALS_KEY = Symbol.for(\"vinext.router.als\");\nconst _FALLBACK_KEY = Symbol.for(\"vinext.router.fallback\");\nconst _g = globalThis as unknown as Record<PropertyKey, unknown>;\nconst _als = (_g[_ALS_KEY] ??=\n new AsyncLocalStorage<RouterState>()) as AsyncLocalStorage<RouterState>;\n\nconst _fallbackState = (_g[_FALLBACK_KEY] ??= {\n ssrContext: null,\n} satisfies RouterState) as RouterState;\n\nfunction _getState(): RouterState {\n if (isInsideUnifiedScope()) {\n return getRequestContext();\n }\n return _als.getStore() ?? _fallbackState;\n}\n\n/**\n * Run a function within a router state ALS scope.\n * Ensures per-request isolation for Pages Router SSR context\n * on concurrent runtimes.\n */\nexport function runWithRouterState<T>(fn: () => T | Promise<T>): T | Promise<T> {\n if (isInsideUnifiedScope()) {\n return runWithUnifiedStateMutation((uCtx) => {\n uCtx.ssrContext = null;\n }, fn);\n }\n\n const state: RouterState = {\n ssrContext: null,\n };\n return _als.run(state, fn);\n}\n\n// ---------------------------------------------------------------------------\n// Register ALS-backed accessors into router.ts\n// ---------------------------------------------------------------------------\n\n_registerRouterStateAccessors({\n getSSRContext(): SSRContext | null {\n return _getState().ssrContext;\n },\n\n setSSRContext(ctx: SSRContext | null): void {\n _getState().ssrContext = ctx;\n },\n});\n"]}
@@ -6,6 +6,7 @@
6
6
  * by fetching new page data and re-rendering the React root.
7
7
  */
8
8
  import { type ReactElement } from "react";
9
+ import type { VinextNextData } from "../client/vinext-next-data.js";
9
10
  import { type UrlQuery } from "../utils/query.js";
10
11
  type BeforePopStateCallback = (state: {
11
12
  url: string;
@@ -31,6 +32,8 @@ interface NextRouter {
31
32
  locales?: string[];
32
33
  /** Default locale */
33
34
  defaultLocale?: string;
35
+ /** Configured domain locales */
36
+ domainLocales?: VinextNextData["domainLocales"];
34
37
  /** Whether the router is ready */
35
38
  isReady: boolean;
36
39
  /** Whether this is a preview */
@@ -85,6 +88,7 @@ interface SSRContext {
85
88
  locale?: string;
86
89
  locales?: string[];
87
90
  defaultLocale?: string;
91
+ domainLocales?: VinextNextData["domainLocales"];
88
92
  }
89
93
  /**
90
94
  * Register ALS-backed state accessors. Called by router-state.ts on import.
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/shims/router.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/shims/router.ts"],"names":[],"mappings":"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"}
@@ -8,17 +8,12 @@
8
8
  import { useState, useEffect, useCallback, useMemo, createElement } from "react";
9
9
  import { RouterContext } from "./internal/router-context.js";
10
10
  import { isValidModulePath } from "../client/validate-module-path.js";
11
- import { toSameOriginPath } from "./url-utils.js";
11
+ import { toBrowserNavigationHref, toSameOriginAppPath } from "./url-utils.js";
12
12
  import { stripBasePath } from "../utils/base-path.js";
13
+ import { addLocalePrefix, getDomainLocaleUrl } from "../utils/domain-locale.js";
13
14
  import { addQueryParam, appendSearchParamsToUrl, urlQueryToSearchParams, } from "../utils/query.js";
14
15
  /** basePath from next.config.js, injected by the plugin at build time */
15
16
  const __basePath = process.env.__NEXT_ROUTER_BASEPATH ?? "";
16
- /** Prepend basePath to a path for browser URLs / fetches */
17
- function withBasePath(p) {
18
- if (!__basePath)
19
- return p;
20
- return __basePath + p;
21
- }
22
17
  function createRouterEvents() {
23
18
  const listeners = new Map();
24
19
  return {
@@ -57,6 +52,19 @@ function resolveUrl(url) {
57
52
  function resolveNavigationTarget(url, as, locale) {
58
53
  return applyNavigationLocale(as ?? resolveUrl(url), locale);
59
54
  }
55
+ function getDomainLocales() {
56
+ return window.__NEXT_DATA__?.domainLocales;
57
+ }
58
+ function getCurrentHostname() {
59
+ return window.location?.hostname;
60
+ }
61
+ function getDomainLocalePath(url, locale) {
62
+ return getDomainLocaleUrl(url, locale, {
63
+ basePath: __basePath,
64
+ currentHostname: getCurrentHostname(),
65
+ domainItems: getDomainLocales(),
66
+ });
67
+ }
60
68
  /**
61
69
  * Apply locale prefix to a URL for client-side navigation.
62
70
  * Same logic as Link's applyLocaleToHref but reads from window globals.
@@ -69,19 +77,30 @@ export function applyNavigationLocale(url, locale) {
69
77
  if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) {
70
78
  return url;
71
79
  }
72
- const defaultLocale = window.__VINEXT_DEFAULT_LOCALE__;
73
- // Default locale doesn't get a prefix
74
- if (locale === defaultLocale)
75
- return url;
76
- // Don't double-prefix
77
- if (url.startsWith(`/${locale}/`) || url === `/${locale}`)
78
- return url;
79
- return `/${locale}${url.startsWith("/") ? url : `/${url}`}`;
80
+ const domainLocalePath = getDomainLocalePath(url, locale);
81
+ if (domainLocalePath)
82
+ return domainLocalePath;
83
+ return addLocalePrefix(url, locale, window.__VINEXT_DEFAULT_LOCALE__ ?? "");
80
84
  }
81
85
  /** Check if a URL is external (any URL scheme per RFC 3986, or protocol-relative) */
82
86
  export function isExternalUrl(url) {
83
87
  return /^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith("//");
84
88
  }
89
+ /** Resolve a hash URL to a basePath-stripped app URL for event payloads */
90
+ function resolveHashUrl(url) {
91
+ if (typeof window === "undefined")
92
+ return url;
93
+ if (url.startsWith("#"))
94
+ return stripBasePath(window.location.pathname, __basePath) + window.location.search + url;
95
+ // Full-path hash URL — strip basePath for consistency with other events
96
+ try {
97
+ const parsed = new URL(url, window.location.href);
98
+ return stripBasePath(parsed.pathname, __basePath) + parsed.search + parsed.hash;
99
+ }
100
+ catch {
101
+ return url;
102
+ }
103
+ }
85
104
  /** Check if a href is only a hash change relative to the current URL */
86
105
  export function isHashOnlyChange(href) {
87
106
  if (href.startsWith("#"))
@@ -304,7 +323,7 @@ async function navigateClient(url) {
304
323
  }
305
324
  catch (err) {
306
325
  console.error("[vinext] Client navigation failed:", err);
307
- routerEvents.emit("routeChangeError", err, url);
326
+ routerEvents.emit("routeChangeError", err, url, { shallow: false });
308
327
  window.location.href = url;
309
328
  }
310
329
  finally {
@@ -319,10 +338,14 @@ async function navigateClient(url) {
319
338
  */
320
339
  function buildRouterValue(pathname, query, asPath, methods) {
321
340
  const _ssrState = _getSSRContext();
341
+ const nextData = typeof window !== "undefined"
342
+ ? window.__NEXT_DATA__
343
+ : undefined;
322
344
  const locale = typeof window === "undefined" ? _ssrState?.locale : window.__VINEXT_LOCALE__;
323
345
  const locales = typeof window === "undefined" ? _ssrState?.locales : window.__VINEXT_LOCALES__;
324
346
  const defaultLocale = typeof window === "undefined" ? _ssrState?.defaultLocale : window.__VINEXT_DEFAULT_LOCALE__;
325
- const route = typeof window !== "undefined" ? (window.__NEXT_DATA__?.page ?? pathname) : pathname;
347
+ const domainLocales = typeof window === "undefined" ? _ssrState?.domainLocales : nextData?.domainLocales;
348
+ const route = typeof window !== "undefined" ? (nextData?.page ?? pathname) : pathname;
326
349
  return {
327
350
  pathname,
328
351
  route,
@@ -332,9 +355,10 @@ function buildRouterValue(pathname, query, asPath, methods) {
332
355
  locale,
333
356
  locales,
334
357
  defaultLocale,
358
+ domainLocales,
335
359
  isReady: true,
336
360
  isPreview: false,
337
- isFallback: typeof window !== "undefined" && window.__NEXT_DATA__?.isFallback === true,
361
+ isFallback: typeof window !== "undefined" && nextData?.isFallback === true,
338
362
  ...methods,
339
363
  events: routerEvents,
340
364
  };
@@ -357,31 +381,41 @@ export function useRouter() {
357
381
  let resolved = resolveNavigationTarget(url, as, options?.locale);
358
382
  // External URLs — delegate to browser (unless same-origin)
359
383
  if (isExternalUrl(resolved)) {
360
- const localPath = toSameOriginPath(resolved);
384
+ const localPath = toSameOriginAppPath(resolved, __basePath);
361
385
  if (localPath == null) {
362
386
  window.location.assign(resolved);
363
387
  return true;
364
388
  }
365
389
  resolved = localPath;
366
390
  }
391
+ const full = toBrowserNavigationHref(resolved, window.location.href, __basePath);
367
392
  // Hash-only change — no page fetch needed
368
393
  if (isHashOnlyChange(resolved)) {
394
+ const eventUrl = resolveHashUrl(resolved);
395
+ routerEvents.emit("hashChangeStart", eventUrl, {
396
+ shallow: options?.shallow ?? false,
397
+ });
369
398
  const hash = resolved.includes("#") ? resolved.slice(resolved.indexOf("#")) : "";
370
- window.history.pushState({}, "", resolved.startsWith("#") ? resolved : withBasePath(resolved));
399
+ window.history.pushState({}, "", resolved.startsWith("#") ? resolved : full);
400
+ _lastPathnameAndSearch = window.location.pathname + window.location.search;
371
401
  scrollToHash(hash);
372
402
  setState(getPathnameAndQuery());
403
+ routerEvents.emit("hashChangeComplete", eventUrl, {
404
+ shallow: options?.shallow ?? false,
405
+ });
373
406
  window.dispatchEvent(new CustomEvent("vinext:navigate"));
374
407
  return true;
375
408
  }
376
409
  saveScrollPosition();
377
- const full = withBasePath(resolved);
378
- routerEvents.emit("routeChangeStart", resolved);
410
+ routerEvents.emit("routeChangeStart", resolved, { shallow: options?.shallow ?? false });
411
+ routerEvents.emit("beforeHistoryChange", resolved, { shallow: options?.shallow ?? false });
379
412
  window.history.pushState({}, "", full);
413
+ _lastPathnameAndSearch = window.location.pathname + window.location.search;
380
414
  if (!options?.shallow) {
381
415
  await navigateClient(full);
382
416
  }
383
417
  setState(getPathnameAndQuery());
384
- routerEvents.emit("routeChangeComplete", resolved);
418
+ routerEvents.emit("routeChangeComplete", resolved, { shallow: options?.shallow ?? false });
385
419
  // Scroll: handle hash target, else scroll to top unless scroll:false
386
420
  const hash = resolved.includes("#") ? resolved.slice(resolved.indexOf("#")) : "";
387
421
  if (hash) {
@@ -397,30 +431,40 @@ export function useRouter() {
397
431
  let resolved = resolveNavigationTarget(url, as, options?.locale);
398
432
  // External URLs — delegate to browser (unless same-origin)
399
433
  if (isExternalUrl(resolved)) {
400
- const localPath = toSameOriginPath(resolved);
434
+ const localPath = toSameOriginAppPath(resolved, __basePath);
401
435
  if (localPath == null) {
402
436
  window.location.replace(resolved);
403
437
  return true;
404
438
  }
405
439
  resolved = localPath;
406
440
  }
441
+ const full = toBrowserNavigationHref(resolved, window.location.href, __basePath);
407
442
  // Hash-only change — no page fetch needed
408
443
  if (isHashOnlyChange(resolved)) {
444
+ const eventUrl = resolveHashUrl(resolved);
445
+ routerEvents.emit("hashChangeStart", eventUrl, {
446
+ shallow: options?.shallow ?? false,
447
+ });
409
448
  const hash = resolved.includes("#") ? resolved.slice(resolved.indexOf("#")) : "";
410
- window.history.replaceState({}, "", resolved.startsWith("#") ? resolved : withBasePath(resolved));
449
+ window.history.replaceState({}, "", resolved.startsWith("#") ? resolved : full);
450
+ _lastPathnameAndSearch = window.location.pathname + window.location.search;
411
451
  scrollToHash(hash);
412
452
  setState(getPathnameAndQuery());
453
+ routerEvents.emit("hashChangeComplete", eventUrl, {
454
+ shallow: options?.shallow ?? false,
455
+ });
413
456
  window.dispatchEvent(new CustomEvent("vinext:navigate"));
414
457
  return true;
415
458
  }
416
- const full = withBasePath(resolved);
417
- routerEvents.emit("routeChangeStart", resolved);
459
+ routerEvents.emit("routeChangeStart", resolved, { shallow: options?.shallow ?? false });
460
+ routerEvents.emit("beforeHistoryChange", resolved, { shallow: options?.shallow ?? false });
418
461
  window.history.replaceState({}, "", full);
462
+ _lastPathnameAndSearch = window.location.pathname + window.location.search;
419
463
  if (!options?.shallow) {
420
464
  await navigateClient(full);
421
465
  }
422
466
  setState(getPathnameAndQuery());
423
- routerEvents.emit("routeChangeComplete", resolved);
467
+ routerEvents.emit("routeChangeComplete", resolved, { shallow: options?.shallow ?? false });
424
468
  // Scroll: handle hash target, else scroll to top unless scroll:false
425
469
  const hash = resolved.includes("#") ? resolved.slice(resolved.indexOf("#")) : "";
426
470
  if (hash) {
@@ -463,6 +507,10 @@ export function useRouter() {
463
507
  // beforePopState callback: called before handling browser back/forward.
464
508
  // If it returns false, the navigation is cancelled.
465
509
  let _beforePopStateCb;
510
+ // Track pathname+search for detecting hash-only back/forward in the popstate
511
+ // handler. Updated after every pushState/replaceState so that popstate can
512
+ // compare the previous value with the (already-changed) window.location.
513
+ let _lastPathnameAndSearch = typeof window !== "undefined" ? window.location.pathname + window.location.search : "";
466
514
  // Module-level popstate listener: handles browser back/forward by re-rendering
467
515
  // the React root with the page at the new URL. This runs regardless of whether
468
516
  // any component calls useRouter().
@@ -470,6 +518,8 @@ if (typeof window !== "undefined") {
470
518
  window.addEventListener("popstate", (e) => {
471
519
  const browserUrl = window.location.pathname + window.location.search;
472
520
  const appUrl = stripBasePath(window.location.pathname, __basePath) + window.location.search;
521
+ // Detect hash-only back/forward: pathname+search unchanged, only hash differs.
522
+ const isHashOnly = browserUrl === _lastPathnameAndSearch;
473
523
  // Check beforePopState callback
474
524
  if (_beforePopStateCb !== undefined) {
475
525
  const shouldContinue = _beforePopStateCb({
@@ -480,9 +530,29 @@ if (typeof window !== "undefined") {
480
530
  if (!shouldContinue)
481
531
  return;
482
532
  }
483
- routerEvents.emit("routeChangeStart", appUrl);
533
+ // Update tracker only after beforePopState confirms navigation proceeds.
534
+ // If beforePopState cancels, the tracker must retain the previous value
535
+ // so the next popstate compares against the correct baseline.
536
+ _lastPathnameAndSearch = browserUrl;
537
+ if (isHashOnly) {
538
+ // Hash-only back/forward — no page fetch needed
539
+ const hashUrl = appUrl + window.location.hash;
540
+ routerEvents.emit("hashChangeStart", hashUrl, { shallow: false });
541
+ scrollToHash(window.location.hash);
542
+ routerEvents.emit("hashChangeComplete", hashUrl, { shallow: false });
543
+ window.dispatchEvent(new CustomEvent("vinext:navigate"));
544
+ return;
545
+ }
546
+ const fullAppUrl = appUrl + window.location.hash;
547
+ routerEvents.emit("routeChangeStart", fullAppUrl, { shallow: false });
548
+ // Note: The browser has already updated window.location by the time popstate
549
+ // fires, so this is not truly "before" the URL change. In Next.js the popstate
550
+ // handler calls replaceState to store history metadata — beforeHistoryChange
551
+ // precedes that call, not the URL change itself. We emit it here for API
552
+ // compatibility.
553
+ routerEvents.emit("beforeHistoryChange", fullAppUrl, { shallow: false });
484
554
  void navigateClient(browserUrl).then(() => {
485
- routerEvents.emit("routeChangeComplete", appUrl);
555
+ routerEvents.emit("routeChangeComplete", fullAppUrl, { shallow: false });
486
556
  restoreScrollPosition(e.state);
487
557
  window.dispatchEvent(new CustomEvent("vinext:navigate"));
488
558
  });
@@ -515,29 +585,39 @@ const Router = {
515
585
  let resolved = resolveNavigationTarget(url, as, options?.locale);
516
586
  // External URLs (unless same-origin)
517
587
  if (isExternalUrl(resolved)) {
518
- const localPath = toSameOriginPath(resolved);
588
+ const localPath = toSameOriginAppPath(resolved, __basePath);
519
589
  if (localPath == null) {
520
590
  window.location.assign(resolved);
521
591
  return true;
522
592
  }
523
593
  resolved = localPath;
524
594
  }
595
+ const full = toBrowserNavigationHref(resolved, window.location.href, __basePath);
525
596
  // Hash-only change
526
597
  if (isHashOnlyChange(resolved)) {
598
+ const eventUrl = resolveHashUrl(resolved);
599
+ routerEvents.emit("hashChangeStart", eventUrl, {
600
+ shallow: options?.shallow ?? false,
601
+ });
527
602
  const hash = resolved.includes("#") ? resolved.slice(resolved.indexOf("#")) : "";
528
- window.history.pushState({}, "", resolved.startsWith("#") ? resolved : withBasePath(resolved));
603
+ window.history.pushState({}, "", resolved.startsWith("#") ? resolved : full);
604
+ _lastPathnameAndSearch = window.location.pathname + window.location.search;
529
605
  scrollToHash(hash);
606
+ routerEvents.emit("hashChangeComplete", eventUrl, {
607
+ shallow: options?.shallow ?? false,
608
+ });
530
609
  window.dispatchEvent(new CustomEvent("vinext:navigate"));
531
610
  return true;
532
611
  }
533
612
  saveScrollPosition();
534
- const full = withBasePath(resolved);
535
- routerEvents.emit("routeChangeStart", resolved);
613
+ routerEvents.emit("routeChangeStart", resolved, { shallow: options?.shallow ?? false });
614
+ routerEvents.emit("beforeHistoryChange", resolved, { shallow: options?.shallow ?? false });
536
615
  window.history.pushState({}, "", full);
616
+ _lastPathnameAndSearch = window.location.pathname + window.location.search;
537
617
  if (!options?.shallow) {
538
618
  await navigateClient(full);
539
619
  }
540
- routerEvents.emit("routeChangeComplete", resolved);
620
+ routerEvents.emit("routeChangeComplete", resolved, { shallow: options?.shallow ?? false });
541
621
  const hash = resolved.includes("#") ? resolved.slice(resolved.indexOf("#")) : "";
542
622
  if (hash) {
543
623
  scrollToHash(hash);
@@ -552,28 +632,38 @@ const Router = {
552
632
  let resolved = resolveNavigationTarget(url, as, options?.locale);
553
633
  // External URLs (unless same-origin)
554
634
  if (isExternalUrl(resolved)) {
555
- const localPath = toSameOriginPath(resolved);
635
+ const localPath = toSameOriginAppPath(resolved, __basePath);
556
636
  if (localPath == null) {
557
637
  window.location.replace(resolved);
558
638
  return true;
559
639
  }
560
640
  resolved = localPath;
561
641
  }
642
+ const full = toBrowserNavigationHref(resolved, window.location.href, __basePath);
562
643
  // Hash-only change
563
644
  if (isHashOnlyChange(resolved)) {
645
+ const eventUrl = resolveHashUrl(resolved);
646
+ routerEvents.emit("hashChangeStart", eventUrl, {
647
+ shallow: options?.shallow ?? false,
648
+ });
564
649
  const hash = resolved.includes("#") ? resolved.slice(resolved.indexOf("#")) : "";
565
- window.history.replaceState({}, "", resolved.startsWith("#") ? resolved : withBasePath(resolved));
650
+ window.history.replaceState({}, "", resolved.startsWith("#") ? resolved : full);
651
+ _lastPathnameAndSearch = window.location.pathname + window.location.search;
566
652
  scrollToHash(hash);
653
+ routerEvents.emit("hashChangeComplete", eventUrl, {
654
+ shallow: options?.shallow ?? false,
655
+ });
567
656
  window.dispatchEvent(new CustomEvent("vinext:navigate"));
568
657
  return true;
569
658
  }
570
- const full = withBasePath(resolved);
571
- routerEvents.emit("routeChangeStart", resolved);
659
+ routerEvents.emit("routeChangeStart", resolved, { shallow: options?.shallow ?? false });
660
+ routerEvents.emit("beforeHistoryChange", resolved, { shallow: options?.shallow ?? false });
572
661
  window.history.replaceState({}, "", full);
662
+ _lastPathnameAndSearch = window.location.pathname + window.location.search;
573
663
  if (!options?.shallow) {
574
664
  await navigateClient(full);
575
665
  }
576
- routerEvents.emit("routeChangeComplete", resolved);
666
+ routerEvents.emit("routeChangeComplete", resolved, { shallow: options?.shallow ?? false });
577
667
  const hash = resolved.includes("#") ? resolved.slice(resolved.indexOf("#")) : "";
578
668
  if (hash) {
579
669
  scrollToHash(hash);