vinext 0.1.1 → 0.1.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 (147) hide show
  1. package/README.md +2 -5
  2. package/dist/build/client-build-config.d.ts +7 -1
  3. package/dist/build/client-build-config.js +9 -1
  4. package/dist/check.js +4 -3
  5. package/dist/client/navigation-runtime.d.ts +3 -2
  6. package/dist/client/window-next.d.ts +6 -4
  7. package/dist/config/config-matchers.d.ts +11 -4
  8. package/dist/config/config-matchers.js +15 -2
  9. package/dist/config/next-config.d.ts +13 -0
  10. package/dist/config/next-config.js +2 -0
  11. package/dist/deploy.js +9 -2
  12. package/dist/entries/app-rsc-entry.js +7 -1
  13. package/dist/entries/pages-client-entry.js +1 -1
  14. package/dist/entries/pages-server-entry.js +7 -6
  15. package/dist/index.d.ts +0 -2
  16. package/dist/index.js +86 -78
  17. package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
  18. package/dist/plugins/dynamic-preload-metadata.js +415 -0
  19. package/dist/plugins/og-assets.js +2 -2
  20. package/dist/plugins/optimize-imports.d.ts +8 -4
  21. package/dist/plugins/optimize-imports.js +16 -12
  22. package/dist/plugins/sass.d.ts +53 -24
  23. package/dist/plugins/sass.js +249 -1
  24. package/dist/plugins/wasm-module-import.d.ts +15 -0
  25. package/dist/plugins/wasm-module-import.js +50 -0
  26. package/dist/routing/app-route-graph.d.ts +23 -1
  27. package/dist/routing/app-route-graph.js +47 -8
  28. package/dist/routing/file-matcher.js +1 -1
  29. package/dist/server/app-browser-entry.js +108 -213
  30. package/dist/server/app-browser-error.d.ts +4 -1
  31. package/dist/server/app-browser-error.js +7 -1
  32. package/dist/server/app-browser-history-controller.d.ts +104 -0
  33. package/dist/server/app-browser-history-controller.js +210 -0
  34. package/dist/server/app-browser-navigation-controller.d.ts +3 -2
  35. package/dist/server/app-browser-navigation-controller.js +10 -7
  36. package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
  37. package/dist/server/app-browser-rsc-redirect.js +30 -8
  38. package/dist/server/app-browser-state.js +4 -7
  39. package/dist/server/app-browser-visible-commit.js +1 -1
  40. package/dist/server/app-fallback-renderer.d.ts +2 -1
  41. package/dist/server/app-fallback-renderer.js +3 -1
  42. package/dist/server/app-middleware.js +1 -0
  43. package/dist/server/app-optimistic-routing.js +22 -1
  44. package/dist/server/app-page-boundary-render.d.ts +2 -1
  45. package/dist/server/app-page-boundary-render.js +4 -2
  46. package/dist/server/app-page-cache.js +9 -7
  47. package/dist/server/app-page-dispatch.d.ts +8 -0
  48. package/dist/server/app-page-dispatch.js +18 -5
  49. package/dist/server/app-page-element-builder.d.ts +22 -2
  50. package/dist/server/app-page-element-builder.js +37 -8
  51. package/dist/server/app-page-execution.d.ts +1 -1
  52. package/dist/server/app-page-execution.js +32 -17
  53. package/dist/server/app-page-render.d.ts +1 -1
  54. package/dist/server/app-page-render.js +7 -14
  55. package/dist/server/app-page-request.d.ts +1 -0
  56. package/dist/server/app-page-request.js +3 -2
  57. package/dist/server/app-page-response.js +1 -1
  58. package/dist/server/app-page-route-wiring.d.ts +3 -1
  59. package/dist/server/app-page-route-wiring.js +8 -7
  60. package/dist/server/app-page-stream.d.ts +1 -6
  61. package/dist/server/app-page-stream.js +1 -4
  62. package/dist/server/app-route-handler-response.js +11 -10
  63. package/dist/server/app-route-handler-runtime.js +12 -1
  64. package/dist/server/app-rsc-handler.js +1 -1
  65. package/dist/server/app-rsc-response-finalizer.js +1 -1
  66. package/dist/server/app-server-action-execution.d.ts +11 -0
  67. package/dist/server/app-server-action-execution.js +5 -2
  68. package/dist/server/app-ssr-entry.js +2 -2
  69. package/dist/server/app-ssr-stream.js +9 -1
  70. package/dist/server/dev-lockfile.js +2 -1
  71. package/dist/server/dev-server.js +43 -12
  72. package/dist/server/headers.d.ts +8 -1
  73. package/dist/server/headers.js +8 -1
  74. package/dist/server/instrumentation-runtime.d.ts +6 -0
  75. package/dist/server/instrumentation-runtime.js +8 -0
  76. package/dist/server/isr-decision.d.ts +79 -0
  77. package/dist/server/isr-decision.js +70 -0
  78. package/dist/server/metadata-route-response.js +5 -3
  79. package/dist/server/middleware-runtime.d.ts +13 -0
  80. package/dist/server/middleware-runtime.js +11 -7
  81. package/dist/server/middleware.js +1 -0
  82. package/dist/server/navigation-planner.d.ts +62 -1
  83. package/dist/server/navigation-planner.js +188 -0
  84. package/dist/server/navigation-trace.d.ts +11 -1
  85. package/dist/server/navigation-trace.js +11 -1
  86. package/dist/server/normalize-path.d.ts +0 -8
  87. package/dist/server/normalize-path.js +3 -1
  88. package/dist/server/otel-tracer-extension.d.ts +45 -0
  89. package/dist/server/otel-tracer-extension.js +89 -0
  90. package/dist/server/pages-api-route.d.ts +14 -3
  91. package/dist/server/pages-api-route.js +6 -1
  92. package/dist/server/pages-asset-tags.d.ts +15 -4
  93. package/dist/server/pages-asset-tags.js +18 -12
  94. package/dist/server/pages-data-route.js +5 -1
  95. package/dist/server/pages-node-compat.d.ts +3 -11
  96. package/dist/server/pages-node-compat.js +174 -121
  97. package/dist/server/pages-page-data.d.ts +28 -0
  98. package/dist/server/pages-page-data.js +61 -17
  99. package/dist/server/pages-page-handler.d.ts +1 -0
  100. package/dist/server/pages-page-handler.js +22 -6
  101. package/dist/server/pages-page-response.d.ts +45 -1
  102. package/dist/server/pages-page-response.js +66 -5
  103. package/dist/server/pages-readiness.d.ts +1 -1
  104. package/dist/server/pages-request-pipeline.d.ts +15 -1
  105. package/dist/server/pages-request-pipeline.js +23 -2
  106. package/dist/server/prod-server.d.ts +39 -1
  107. package/dist/server/prod-server.js +98 -34
  108. package/dist/shims/cache-runtime.js +9 -2
  109. package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
  110. package/dist/shims/dynamic-preload-chunks.js +77 -0
  111. package/dist/shims/dynamic.d.ts +4 -0
  112. package/dist/shims/dynamic.js +4 -2
  113. package/dist/shims/error-boundary.d.ts +4 -4
  114. package/dist/shims/error.js +37 -11
  115. package/dist/shims/fetch-cache.d.ts +9 -1
  116. package/dist/shims/fetch-cache.js +11 -1
  117. package/dist/shims/head.js +6 -1
  118. package/dist/shims/headers.d.ts +16 -2
  119. package/dist/shims/headers.js +37 -1
  120. package/dist/shims/image-config.js +7 -1
  121. package/dist/shims/internal/app-route-detection.d.ts +6 -3
  122. package/dist/shims/internal/app-route-detection.js +10 -6
  123. package/dist/shims/internal/app-router-context.d.ts +5 -0
  124. package/dist/shims/metadata.d.ts +6 -2
  125. package/dist/shims/metadata.js +32 -14
  126. package/dist/shims/navigation.d.ts +7 -16
  127. package/dist/shims/navigation.js +33 -16
  128. package/dist/shims/router.js +28 -1
  129. package/dist/shims/script-nonce-context.d.ts +1 -1
  130. package/dist/shims/script-nonce-context.js +11 -3
  131. package/dist/shims/server.d.ts +17 -1
  132. package/dist/shims/server.js +31 -6
  133. package/dist/shims/slot.js +1 -1
  134. package/dist/shims/unified-request-context.js +1 -0
  135. package/dist/typegen.js +1 -0
  136. package/dist/utils/client-build-manifest.js +15 -5
  137. package/dist/utils/client-runtime-metadata.d.ts +45 -0
  138. package/dist/utils/client-runtime-metadata.js +63 -0
  139. package/dist/utils/hash.d.ts +17 -1
  140. package/dist/utils/hash.js +36 -1
  141. package/dist/utils/lazy-chunks.d.ts +27 -1
  142. package/dist/utils/lazy-chunks.js +65 -1
  143. package/dist/utils/manifest-paths.d.ts +20 -2
  144. package/dist/utils/manifest-paths.js +38 -3
  145. package/dist/utils/path.d.ts +2 -1
  146. package/dist/utils/path.js +5 -1
  147. package/package.json +2 -2
@@ -0,0 +1,77 @@
1
+ "use client";
2
+ import { useScriptNonce } from "./script-nonce-context.js";
3
+ import React from "react";
4
+ import * as ReactDOM from "react-dom";
5
+ //#region src/shims/dynamic-preload-chunks.tsx
6
+ /**
7
+ * Preload links for rendered next/dynamic() boundaries.
8
+ *
9
+ * This MUST be a "use client" component. next/dynamic() can be called from
10
+ * either a Server Component or a Client Component. If this rendered in the
11
+ * environment of the call site, a Server-Component call site would render it in
12
+ * the RSC environment, where the script-nonce React context is unavailable
13
+ * (createContext is not callable in react-server), so emitted preload links
14
+ * would drop the request CSP nonce — a CSP violation under
15
+ * `script-src 'nonce-…' 'strict-dynamic'`.
16
+ *
17
+ * Marking it "use client" forces it into the SSR pass (where vinext installs
18
+ * the ScriptNonceProvider via withScriptNonce()), so the nonce is available
19
+ * regardless of whether the dynamic() call site is a Server or Client
20
+ * Component. This mirrors Next.js's <PreloadChunks> ('use client') and vinext's
21
+ * own next/script shim.
22
+ *
23
+ * Deliberate divergence from Next.js: for CSS we render
24
+ * `<link rel="stylesheet">` WITHOUT `as="style"`. Next.js emits `as="style"`,
25
+ * but per the HTML spec `as` is only meaningful on `rel="preload"`/`modulepreload`
26
+ * — on `rel="stylesheet"` it is ignored by browsers and is semantically wrong.
27
+ * React keys stylesheet resources on href + precedence, not `as`, so omitting it
28
+ * is safe. This is an intentional, documented difference, not a parity bug.
29
+ */
30
+ function dynamicPreloadHref(file) {
31
+ if (file.startsWith("/") || file.startsWith("http://") || file.startsWith("https://") || file.startsWith("//")) return file;
32
+ return `/${file}`;
33
+ }
34
+ function resolveDynamicPreloadFiles(moduleIds) {
35
+ if (!moduleIds || moduleIds.length === 0) return [];
36
+ const preloadMap = globalThis.__VINEXT_DYNAMIC_PRELOADS__;
37
+ if (!preloadMap) return [];
38
+ const files = [];
39
+ const seen = /* @__PURE__ */ new Set();
40
+ for (const moduleId of moduleIds) for (const file of preloadMap[moduleId] ?? []) {
41
+ if (seen.has(file)) continue;
42
+ seen.add(file);
43
+ files.push(file);
44
+ }
45
+ return files;
46
+ }
47
+ function DynamicPreloadChunks(props) {
48
+ const nonce = useScriptNonce();
49
+ if (typeof window !== "undefined") return null;
50
+ const files = resolveDynamicPreloadFiles(props.moduleIds);
51
+ if (files.length === 0) return null;
52
+ const stylesheets = [];
53
+ for (const file of files) {
54
+ const href = dynamicPreloadHref(file);
55
+ if (href.endsWith(".css")) {
56
+ stylesheets.push(React.createElement("link", {
57
+ key: href,
58
+ rel: "stylesheet",
59
+ href,
60
+ nonce,
61
+ precedence: "dynamic"
62
+ }));
63
+ continue;
64
+ }
65
+ if (href.endsWith(".js") && typeof ReactDOM.preload === "function") {
66
+ const preloadOptions = {
67
+ as: "script",
68
+ fetchPriority: "low",
69
+ nonce
70
+ };
71
+ ReactDOM.preload(href, preloadOptions);
72
+ }
73
+ }
74
+ return stylesheets.length > 0 ? React.createElement(React.Fragment, null, ...stylesheets) : null;
75
+ }
76
+ //#endregion
77
+ export { DynamicPreloadChunks };
@@ -16,6 +16,10 @@ type LoaderFn<P> = () => LoaderComponent<P>;
16
16
  type DynamicOptions<P> = {
17
17
  loading?: ComponentType<DynamicLoadingProps>;
18
18
  loader?: Loader<P>;
19
+ loadableGenerated?: {
20
+ modules?: readonly string[];
21
+ };
22
+ modules?: readonly string[];
19
23
  ssr?: boolean;
20
24
  };
21
25
  type Loader<P> = LoaderFn<P> | LoaderComponent<P>;
@@ -1,3 +1,4 @@
1
+ import { DynamicPreloadChunks } from "./dynamic-preload-chunks.js";
1
2
  import React from "react";
2
3
  //#region src/shims/dynamic.ts
3
4
  /**
@@ -121,8 +122,9 @@ function flushPreloads() {
121
122
  return Promise.all(pending);
122
123
  }
123
124
  function dynamic(dynamicInput, options) {
124
- const { loader: dynamicLoader, loading: LoadingComponent, ssr = true } = normalizeDynamicOptions(dynamicInput, options);
125
+ const { loader: dynamicLoader, loadableGenerated, loading: LoadingComponent, modules, ssr = true } = normalizeDynamicOptions(dynamicInput, options);
125
126
  const loader = dynamicLoader ? normalizeLoader(dynamicLoader) : () => Promise.resolve(() => null);
127
+ const preloadModuleIds = loadableGenerated?.modules ?? modules;
126
128
  if (!ssr) {
127
129
  if (isServer) {
128
130
  const SSRFalse = (_props) => LoadingComponent ? React.createElement(LoadingComponent, createDynamicLoadingProps({ pastDelay: false })) : null;
@@ -174,7 +176,7 @@ function dynamic(dynamicInput, options) {
174
176
  resetKey: 0
175
177
  }, lazyElement);
176
178
  }
177
- return React.createElement(React.Suspense, { fallback }, content);
179
+ return React.createElement(React.Fragment, null, React.createElement(DynamicPreloadChunks, { moduleIds: preloadModuleIds }), React.createElement(React.Suspense, { fallback }, content));
178
180
  };
179
181
  ServerDynamic.displayName = "DynamicServer";
180
182
  return ServerDynamic;
@@ -31,7 +31,7 @@ declare class RedirectErrorBoundary extends React.Component<{
31
31
  children?: React.ReactNode;
32
32
  });
33
33
  static getDerivedStateFromError(error: unknown): RedirectBoundaryState;
34
- render(): string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | null | undefined;
34
+ render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
35
35
  }
36
36
  declare function RedirectBoundary({
37
37
  children
@@ -51,7 +51,7 @@ declare class ErrorBoundaryInner extends React.Component<ErrorBoundaryInnerProps
51
51
  componentDidMount(): void;
52
52
  componentWillUnmount(): void;
53
53
  reset: () => void;
54
- render(): string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | null | undefined;
54
+ render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
55
55
  }
56
56
  declare function ErrorBoundary({
57
57
  fallback,
@@ -99,7 +99,7 @@ declare class ForbiddenBoundaryInner extends React.Component<ForbiddenBoundaryIn
99
99
  constructor(props: ForbiddenBoundaryInnerProps);
100
100
  static getDerivedStateFromProps(props: ForbiddenBoundaryInnerProps, state: ForbiddenBoundaryState): ForbiddenBoundaryState | null;
101
101
  static getDerivedStateFromError(error: unknown): Partial<ForbiddenBoundaryState>;
102
- render(): string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | null | undefined;
102
+ render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
103
103
  }
104
104
  declare function ForbiddenBoundary({
105
105
  fallback,
@@ -123,7 +123,7 @@ declare class UnauthorizedBoundaryInner extends React.Component<UnauthorizedBoun
123
123
  constructor(props: UnauthorizedBoundaryInnerProps);
124
124
  static getDerivedStateFromProps(props: UnauthorizedBoundaryInnerProps, state: UnauthorizedBoundaryState): UnauthorizedBoundaryState | null;
125
125
  static getDerivedStateFromError(error: unknown): Partial<UnauthorizedBoundaryState>;
126
- render(): string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | null | undefined;
126
+ render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
127
127
  }
128
128
  declare function UnauthorizedBoundary({
129
129
  fallback,
@@ -1,5 +1,6 @@
1
+ import { AppRouterContext } from "./internal/app-router-context.js";
1
2
  import { RouterContext } from "./internal/router-context.js";
2
- import { appRouterInstance, isNextRouterError } from "./navigation.js";
3
+ import { isNextRouterError, usePathname } from "./navigation.js";
3
4
  import React from "react";
4
5
  //#region src/shims/error.tsx
5
6
  /**
@@ -39,33 +40,53 @@ function ErrorComponent({ statusCode, title }) {
39
40
  margin: 0
40
41
  } }, displayTitle + "."))));
41
42
  }
43
+ const _CatchErrorAppRouterContext = AppRouterContext ?? React.createContext(null);
42
44
  var _CatchError = class extends React.Component {
43
- static contextType = RouterContext;
45
+ static contextType = _CatchErrorAppRouterContext;
44
46
  static displayName = "unstable_catchError(Next.CatchError)";
45
- state = { error: null };
47
+ constructor(props) {
48
+ super(props);
49
+ this.state = {
50
+ error: null,
51
+ previousPathname: props.pathname
52
+ };
53
+ }
46
54
  static getDerivedStateFromError(thrownValue) {
47
55
  if (isNextRouterError(thrownValue)) throw thrownValue;
48
56
  return { error: { thrownValue } };
49
57
  }
58
+ static getDerivedStateFromProps(props, state) {
59
+ if (props.pathname !== state.previousPathname && state.error) return {
60
+ error: null,
61
+ previousPathname: props.pathname
62
+ };
63
+ return {
64
+ error: state.error,
65
+ previousPathname: props.pathname
66
+ };
67
+ }
50
68
  reset = () => {
51
69
  this.setState({ error: null });
52
70
  };
53
71
  unstable_retry = () => {
54
- if (this.context !== null) throw new Error("`unstable_retry()` can only be used in the App Router. Use `reset()` in the Pages Router.");
55
- if (typeof window === "undefined") throw new Error("`unstable_retry()` can only be used on the client. Call it from a user interaction handler inside the error fallback.");
72
+ if (this.props.isPagesRouter) throw new Error("`unstable_retry()` can only be used in the App Router. Use `reset()` in the Pages Router.");
56
73
  React.startTransition(() => {
57
- appRouterInstance.refresh();
74
+ this.context?.refresh();
58
75
  this.reset();
59
76
  });
60
77
  };
61
78
  render() {
62
79
  if (this.state.error) {
80
+ const Fallback = this.props.fallback;
63
81
  const errorInfo = {
64
82
  error: this.state.error.thrownValue,
65
83
  reset: this.reset,
66
84
  unstable_retry: this.unstable_retry
67
85
  };
68
- return this.props.fallback(this.props.forwardedProps, errorInfo);
86
+ return React.createElement(Fallback, {
87
+ props: this.props.props,
88
+ errorInfo
89
+ });
69
90
  }
70
91
  return this.props.children;
71
92
  }
@@ -79,13 +100,18 @@ var _CatchError = class extends React.Component {
79
100
  * https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/catch-error.tsx
80
101
  */
81
102
  function unstable_catchError(fallback) {
82
- const TypedCatchError = _CatchError;
103
+ const Fallback = ({ props, errorInfo }) => fallback(props, errorInfo);
104
+ Fallback.displayName = fallback.name || "CatchErrorFallback";
83
105
  function CatchErrorBoundary(allProps) {
84
106
  const { children, ...rest } = allProps;
107
+ const pathname = usePathname();
108
+ const isPagesRouter = React.useContext(RouterContext) !== null;
85
109
  const forwardedProps = rest;
86
- return React.createElement(TypedCatchError, {
87
- fallback,
88
- forwardedProps
110
+ return React.createElement(_CatchError, {
111
+ fallback: Fallback,
112
+ isPagesRouter,
113
+ pathname,
114
+ props: forwardedProps
89
115
  }, children);
90
116
  }
91
117
  CatchErrorBoundary.displayName = `unstable_catchError(${fallback.name || "CatchErrorFallback"})`;
@@ -75,6 +75,14 @@ declare function addCollectedRequestTags(tags: readonly string[]): void;
75
75
  * affected route, without permanently coupling a shared fetch entry to one path.
76
76
  */
77
77
  declare function setCurrentFetchSoftTags(tags: string[]): void;
78
+ /**
79
+ * Read the path-derived soft tags for the current render.
80
+ *
81
+ * Used by the "use cache" runtime to pass soft tags to the cache handler
82
+ * so that `revalidatePath()` invalidates "use cache" entries during the
83
+ * affected route's next request, even when the entry carries no hard tags.
84
+ */
85
+ declare function getCurrentFetchSoftTags(): string[];
78
86
  declare function setCurrentFetchCacheMode(mode: FetchCacheMode | null): void;
79
87
  /**
80
88
  * Install the patched fetch and reset per-request tag state.
@@ -123,4 +131,4 @@ declare function ensureFetchPatch(): void;
123
131
  */
124
132
  declare function getOriginalFetch(): typeof globalThis.fetch;
125
133
  //#endregion
126
- export { FetchCacheMode, FetchCacheState, _resetPendingRefetches, addCollectedRequestTags, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, withFetchCache };
134
+ export { FetchCacheMode, FetchCacheState, _resetPendingRefetches, addCollectedRequestTags, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getCurrentFetchSoftTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, withFetchCache };
@@ -369,6 +369,16 @@ function addCollectedRequestTags(tags) {
369
369
  function setCurrentFetchSoftTags(tags) {
370
370
  _getState().currentFetchSoftTags = [...tags];
371
371
  }
372
+ /**
373
+ * Read the path-derived soft tags for the current render.
374
+ *
375
+ * Used by the "use cache" runtime to pass soft tags to the cache handler
376
+ * so that `revalidatePath()` invalidates "use cache" entries during the
377
+ * affected route's next request, even when the entry carries no hard tags.
378
+ */
379
+ function getCurrentFetchSoftTags() {
380
+ return _getState().currentFetchSoftTags;
381
+ }
372
382
  function setCurrentFetchCacheMode(mode) {
373
383
  _getState().currentFetchCacheMode = mode;
374
384
  }
@@ -728,4 +738,4 @@ function getOriginalFetch() {
728
738
  return originalFetch;
729
739
  }
730
740
  //#endregion
731
- export { _resetPendingRefetches, addCollectedRequestTags, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, withFetchCache };
741
+ export { _resetPendingRefetches, addCollectedRequestTags, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getCurrentFetchSoftTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, withFetchCache };
@@ -104,6 +104,10 @@ const SELF_CLOSING_HEAD_TAGS = new Set([
104
104
  ]);
105
105
  /** Tags whose content is raw text — closing-tag sequences must be escaped during SSR. */
106
106
  const RAW_CONTENT_TAGS = new Set(["script", "style"]);
107
+ const INLINE_CLOSE_TAG_RES = {
108
+ script: /<\/(script)/gi,
109
+ style: /<\/(style)/gi
110
+ };
107
111
  function warnDisallowedHeadTag(tag) {
108
112
  if (process.env.NODE_ENV !== "production") console.warn(`[vinext] <Head> ignoring disallowed tag <${tag}>. Only ${ALLOWED_HEAD_TAGS_LIST} are allowed.`);
109
113
  }
@@ -247,7 +251,8 @@ function escapeAttr(s) {
247
251
  * context but prevents the HTML parser from seeing a closing tag.
248
252
  */
249
253
  function escapeInlineContent(content, tag) {
250
- const pattern = new RegExp(`<\\/(${tag})`, "gi");
254
+ const pattern = INLINE_CLOSE_TAG_RES[tag];
255
+ if (!pattern) return content;
251
256
  return content.replace(pattern, "<\\/$1");
252
257
  }
253
258
  function getDangerouslySetInnerHTML(value) {
@@ -18,12 +18,24 @@ type HeadersAccessPhase = "render" | "action" | "route-handler";
18
18
  type VinextHeadersShimState = {
19
19
  headersContext: HeadersContext | null;
20
20
  dynamicUsageDetected: boolean;
21
- renderRequestApiUsage: Set<RenderRequestApiKind>; /** Error recorded by throwIfInsideCacheScope for dev diagnostics, persists even if caught by user code. */
21
+ renderRequestApiUsage: Set<RenderRequestApiKind>;
22
+ connectionProbe: ConnectionProbeState | null; /** Error recorded by throwIfInsideCacheScope for dev diagnostics, persists even if caught by user code. */
22
23
  invalidDynamicUsageError: unknown;
23
24
  pendingSetCookies: string[];
24
25
  draftModeCookieHeader: string | null;
25
26
  phase: HeadersAccessPhase;
26
27
  };
28
+ type ConnectionProbeState = {
29
+ interrupted: boolean;
30
+ interrupt: () => void;
31
+ pending: Promise<never>;
32
+ };
33
+ type ConnectionProbeResult<T> = {
34
+ completed: true;
35
+ result: T;
36
+ } | {
37
+ completed: false;
38
+ };
27
39
  /**
28
40
  * Dynamic usage flag — set when a component calls connection(), cookies(),
29
41
  * headers(), or noStore() during rendering. When true, ISR caching is
@@ -35,6 +47,8 @@ type VinextHeadersShimState = {
35
47
  */
36
48
  declare function markDynamicUsage(): void;
37
49
  declare function markRenderRequestApiUsage(kind: RenderRequestApiKind): void;
50
+ declare function runWithConnectionProbe<T>(fn: () => T | Promise<T>): Promise<ConnectionProbeResult<T>>;
51
+ declare function suspendConnectionProbe(): Promise<never> | null;
38
52
  declare function peekRenderRequestApiUsage(): RenderRequestApiKind[];
39
53
  declare function consumeRenderRequestApiUsage(): RenderRequestApiKind[];
40
54
  /**
@@ -236,4 +250,4 @@ declare class RequestCookies {
236
250
  toString(): string;
237
251
  }
238
252
  //#endregion
239
- export { HeadersAccessPhase, HeadersContext, type RequestCookies, VinextHeadersShimState, applyMiddlewareRequestHeaders, consumeDynamicUsage, consumeInvalidDynamicUsageError, consumeRenderRequestApiUsage, cookies, draftMode, getAndClearPendingCookies, getDraftModeCookieHeader, getHeadersAccessPhase, getHeadersContext, headers, headersContextFromRequest, isDraftModeRequest, markDynamicUsage, markRenderRequestApiUsage, peekDynamicUsage, peekRenderRequestApiUsage, runWithHeadersContext, setHeadersAccessPhase, setHeadersContext, throwIfInsideCacheScope };
253
+ export { HeadersAccessPhase, HeadersContext, type RequestCookies, VinextHeadersShimState, applyMiddlewareRequestHeaders, consumeDynamicUsage, consumeInvalidDynamicUsageError, consumeRenderRequestApiUsage, cookies, draftMode, getAndClearPendingCookies, getDraftModeCookieHeader, getHeadersAccessPhase, getHeadersContext, headers, headersContextFromRequest, isDraftModeRequest, markDynamicUsage, markRenderRequestApiUsage, peekDynamicUsage, peekRenderRequestApiUsage, runWithConnectionProbe, runWithHeadersContext, setHeadersAccessPhase, setHeadersContext, suspendConnectionProbe, throwIfInsideCacheScope };
@@ -21,6 +21,7 @@ const _fallbackState = _g[_FALLBACK_KEY] ??= {
21
21
  headersContext: null,
22
22
  dynamicUsageDetected: false,
23
23
  renderRequestApiUsage: /* @__PURE__ */ new Set(),
24
+ connectionProbe: null,
24
25
  invalidDynamicUsageError: null,
25
26
  pendingSetCookies: [],
26
27
  draftModeCookieHeader: null,
@@ -115,6 +116,39 @@ function markDynamicUsage() {
115
116
  function markRenderRequestApiUsage(kind) {
116
117
  _getState().renderRequestApiUsage.add(kind);
117
118
  }
119
+ async function runWithConnectionProbe(fn) {
120
+ const state = _getState();
121
+ const previousProbe = state.connectionProbe;
122
+ let interruptProbe = () => {};
123
+ const interrupted = new Promise((resolve) => {
124
+ interruptProbe = () => resolve({ completed: false });
125
+ });
126
+ const probe = {
127
+ interrupted: false,
128
+ interrupt() {
129
+ if (probe.interrupted) return;
130
+ probe.interrupted = true;
131
+ interruptProbe();
132
+ },
133
+ pending: new Promise(() => {})
134
+ };
135
+ state.connectionProbe = probe;
136
+ try {
137
+ const completed = Promise.resolve().then(fn).then((result) => ({
138
+ completed: true,
139
+ result
140
+ }));
141
+ return await Promise.race([completed, interrupted]);
142
+ } finally {
143
+ state.connectionProbe = previousProbe;
144
+ }
145
+ }
146
+ function suspendConnectionProbe() {
147
+ const probe = _getState().connectionProbe;
148
+ if (!probe) return null;
149
+ probe.interrupt();
150
+ return probe.pending;
151
+ }
118
152
  function peekRenderRequestApiUsage() {
119
153
  return [..._getState().renderRequestApiUsage].sort();
120
154
  }
@@ -260,6 +294,7 @@ function runWithHeadersContext(ctx, fn) {
260
294
  uCtx.headersContext = ctx;
261
295
  uCtx.dynamicUsageDetected = false;
262
296
  uCtx.renderRequestApiUsage = /* @__PURE__ */ new Set();
297
+ uCtx.connectionProbe = null;
263
298
  uCtx.pendingSetCookies = [];
264
299
  uCtx.draftModeCookieHeader = null;
265
300
  uCtx.phase = "render";
@@ -268,6 +303,7 @@ function runWithHeadersContext(ctx, fn) {
268
303
  headersContext: ctx,
269
304
  dynamicUsageDetected: false,
270
305
  renderRequestApiUsage: /* @__PURE__ */ new Set(),
306
+ connectionProbe: null,
271
307
  invalidDynamicUsageError: null,
272
308
  pendingSetCookies: [],
273
309
  draftModeCookieHeader: null,
@@ -690,4 +726,4 @@ var RequestCookies = class {
690
726
  }
691
727
  };
692
728
  //#endregion
693
- export { applyMiddlewareRequestHeaders, consumeDynamicUsage, consumeInvalidDynamicUsageError, consumeRenderRequestApiUsage, cookies, draftMode, getAndClearPendingCookies, getDraftModeCookieHeader, getHeadersAccessPhase, getHeadersContext, headers, headersContextFromRequest, isDraftModeRequest, markDynamicUsage, markRenderRequestApiUsage, peekDynamicUsage, peekRenderRequestApiUsage, runWithHeadersContext, setHeadersAccessPhase, setHeadersContext, throwIfInsideCacheScope };
729
+ export { applyMiddlewareRequestHeaders, consumeDynamicUsage, consumeInvalidDynamicUsageError, consumeRenderRequestApiUsage, cookies, draftMode, getAndClearPendingCookies, getDraftModeCookieHeader, getHeadersAccessPhase, getHeadersContext, headers, headersContextFromRequest, isDraftModeRequest, markDynamicUsage, markRenderRequestApiUsage, peekDynamicUsage, peekRenderRequestApiUsage, runWithConnectionProbe, runWithHeadersContext, setHeadersAccessPhase, setHeadersContext, suspendConnectionProbe, throwIfInsideCacheScope };
@@ -1,5 +1,6 @@
1
1
  import ipaddr from "ipaddr.js";
2
2
  //#region src/shims/image-config.ts
3
+ const globRegexCache = /* @__PURE__ */ new Map();
3
4
  /**
4
5
  * Convert a glob pattern (with `*` and `**`) to a RegExp.
5
6
  *
@@ -14,6 +15,9 @@ import ipaddr from "ipaddr.js";
14
15
  * Literal characters are escaped for regex safety.
15
16
  */
16
17
  function globToRegex(pattern, separator) {
18
+ const key = `${separator}\0${pattern}`;
19
+ const cached = globRegexCache.get(key);
20
+ if (cached !== void 0) return cached;
17
21
  let regexStr = "^";
18
22
  const doubleStar = separator === "." ? ".+" : ".*";
19
23
  const singleStar = separator === "." ? "[^.]+" : "[^/]+";
@@ -27,7 +31,9 @@ function globToRegex(pattern, separator) {
27
31
  }
28
32
  }
29
33
  regexStr += "$";
30
- return new RegExp(regexStr);
34
+ const re = new RegExp(regexStr);
35
+ globRegexCache.set(key, re);
36
+ return re;
31
37
  }
32
38
  /**
33
39
  * Check whether a URL matches a single remote pattern.
@@ -27,9 +27,12 @@ declare function getPagesRouterComponentsMap(): PagesRouterComponentsMap;
27
27
  * Pages Router map when the href matches an App Router route. No-op when the
28
28
  * manifest is absent, the URL is external, or no app route matches.
29
29
  *
30
- * `pathname` is the basePath-stripped path — matching Next.js's
31
- * `router.components[urlPathname]` key (see the source link in this file's
32
- * leading comment).
30
+ * `pathname` is the basePath-stripped, trailing-slash-stripped path —
31
+ * matching Next.js's `removeTrailingSlash(removeBasePath(pathname))` key used
32
+ * at read time (router.ts:1442). Stripping here ensures the write and read
33
+ * keys agree regardless of whether the caller normalised trailing slashes
34
+ * first (e.g. `link.tsx` normalises to match `trailingSlash` config before
35
+ * calling, while `router.prefetch()` passes the raw user-supplied URL).
33
36
  */
34
37
  declare function markAppRouteDetectedOnPrefetch(href: string, basePath: string): void;
35
38
  //#endregion
@@ -1,4 +1,4 @@
1
- import { stripBasePath } from "../../utils/base-path.js";
1
+ import { removeTrailingSlash, stripBasePath } from "../../utils/base-path.js";
2
2
  import { createRouteTrieCache, matchRouteWithTrie } from "../../routing/route-matching.js";
3
3
  //#region src/shims/internal/app-route-detection.ts
4
4
  const appRouteTrieCache = createRouteTrieCache();
@@ -52,15 +52,19 @@ function matchesAppRoute(href, basePath) {
52
52
  * Pages Router map when the href matches an App Router route. No-op when the
53
53
  * manifest is absent, the URL is external, or no app route matches.
54
54
  *
55
- * `pathname` is the basePath-stripped path — matching Next.js's
56
- * `router.components[urlPathname]` key (see the source link in this file's
57
- * leading comment).
55
+ * `pathname` is the basePath-stripped, trailing-slash-stripped path —
56
+ * matching Next.js's `removeTrailingSlash(removeBasePath(pathname))` key used
57
+ * at read time (router.ts:1442). Stripping here ensures the write and read
58
+ * keys agree regardless of whether the caller normalised trailing slashes
59
+ * first (e.g. `link.tsx` normalises to match `trailingSlash` config before
60
+ * calling, while `router.prefetch()` passes the raw user-supplied URL).
58
61
  */
59
62
  function markAppRouteDetectedOnPrefetch(href, basePath) {
60
63
  if (typeof window === "undefined") return;
61
64
  if (!matchesAppRoute(href, basePath)) return;
62
- const pathname = resolveSameOriginPathname(href, basePath);
63
- if (pathname === null) return;
65
+ const rawPathname = resolveSameOriginPathname(href, basePath);
66
+ if (rawPathname === null) return;
67
+ const pathname = removeTrailingSlash(rawPathname);
64
68
  getPagesRouterComponentsMap()[pathname] = { __appRouter: true };
65
69
  }
66
70
  //#endregion
@@ -16,6 +16,11 @@ type AppRouterInstance = {
16
16
  push(href: string, options?: NavigateOptions): void;
17
17
  replace(href: string, options?: NavigateOptions): void;
18
18
  prefetch(href: string, options?: PrefetchOptions): void;
19
+ /**
20
+ * Perform an experimental gesture transition navigation.
21
+ * Only available when experimental.gestureTransition is enabled.
22
+ */
23
+ experimental_gesturePush?(href: string, options?: NavigateOptions): void;
19
24
  };
20
25
  declare const AppRouterContext: React$1.Context<AppRouterInstance | null> | null;
21
26
  declare const GlobalLayoutRouterContext: React$1.Context<unknown> | null;
@@ -271,11 +271,15 @@ declare function resolveModuleMetadata(mod: Record<string, unknown>, params?: Re
271
271
  type MetadataHeadProps = {
272
272
  metadata: Metadata;
273
273
  pathname?: string;
274
+ trailingSlash?: boolean;
274
275
  };
275
- declare function renderMetadataToHtml(metadata: Metadata, pathname?: string): string;
276
+ declare function renderMetadataToHtml(metadata: Metadata, pathname?: string, options?: {
277
+ trailingSlash?: boolean;
278
+ }): string;
276
279
  declare function MetadataHead({
277
280
  metadata,
278
- pathname
281
+ pathname,
282
+ trailingSlash
279
283
  }: MetadataHeadProps): React.JSX.Element;
280
284
  //#endregion
281
285
  export { DEFAULT_VIEWPORT, Metadata, MetadataHead, MetadataMergeEntry, Viewport, ViewportHead, mergeMetadata, mergeMetadataEntries, mergeViewport, postProcessMetadata, renderMetadataToHtml, resolveModuleMetadata, resolveModuleViewport };
@@ -287,18 +287,35 @@ function formatResolvedMetadataUrl(url) {
287
287
  if (url.pathname === "/" && url.search === "" && url.hash === "") return url.origin;
288
288
  return url.href;
289
289
  }
290
- function resolveMetadataUrl(url, metadataBase) {
290
+ const TRAILING_SLASH_FILE_REGEX = /^(?:\/((?!\.well-known(?:\/.*)?)((?:[^/]+\/)*)([^/]+\.\w+)))(\/?|$)/i;
291
+ function resolveMetadataUrl(url, metadataBase, trailingSlash) {
291
292
  const value = stringifyUrl(url);
292
- if (isAbsoluteOrProtocolRelativeUrl(value) || !metadataBase) return value;
293
+ if (!metadataBase) return value;
293
294
  try {
294
- return formatResolvedMetadataUrl(new URL(joinMetadataPath(metadataBase.pathname, value), metadataBase));
295
+ const isAbsolute = isAbsoluteOrProtocolRelativeUrl(value);
296
+ const composed = isAbsolute ? new URL(value, metadataBase) : new URL(joinMetadataPath(metadataBase.pathname, value), metadataBase);
297
+ if (isAbsolute && composed.origin !== metadataBase.origin) return value;
298
+ if (trailingSlash === true && composed.search === "") {
299
+ if (composed.pathname !== "/" && !composed.pathname.endsWith("/") && !TRAILING_SLASH_FILE_REGEX.test(composed.pathname)) composed.pathname += "/";
300
+ }
301
+ const result = formatResolvedMetadataUrl(composed);
302
+ if (trailingSlash === true && result === metadataBase.origin) return `${metadataBase.origin}/`;
303
+ return result;
295
304
  } catch {
296
305
  return value;
297
306
  }
298
307
  }
299
- function resolveCanonicalUrl(url, metadataBase, pathname) {
300
- if (url instanceof URL) return resolveMetadataUrl(url, metadataBase);
301
- return resolveMetadataUrl(resolveRelativeMetadataUrl(url, pathname), metadataBase);
308
+ function resolveCanonicalUrl(url, metadataBase, pathname, trailingSlash) {
309
+ if (url instanceof URL) return resolveMetadataUrl(url, metadataBase, trailingSlash);
310
+ return resolveMetadataUrl(resolveRelativeMetadataUrl(url, pathname), metadataBase, trailingSlash);
311
+ }
312
+ function resolveAlternateUrl(url, metadataBase, pathname, trailingSlash) {
313
+ if (url instanceof URL) {
314
+ const resolvedUrl = new URL(pathname, url);
315
+ url.searchParams.forEach((value, key) => resolvedUrl.searchParams.set(key, value));
316
+ return resolveMetadataUrl(resolvedUrl, metadataBase, trailingSlash);
317
+ }
318
+ return resolveCanonicalUrl(url, metadataBase, pathname, trailingSlash);
302
319
  }
303
320
  function isSocialImageDescriptor(value) {
304
321
  return typeof value === "object" && !(value instanceof URL);
@@ -359,13 +376,14 @@ function renderMetadataElementToHtml(node) {
359
376
  default: return "";
360
377
  }
361
378
  }
362
- function renderMetadataToHtml(metadata, pathname = "/") {
379
+ function renderMetadataToHtml(metadata, pathname = "/", options) {
363
380
  return renderMetadataElementToHtml(MetadataHead({
364
381
  metadata,
365
- pathname
382
+ pathname,
383
+ trailingSlash: options?.trailingSlash
366
384
  }));
367
385
  }
368
- function MetadataHead({ metadata, pathname = "/" }) {
386
+ function MetadataHead({ metadata, pathname = "/", trailingSlash }) {
369
387
  const elements = [];
370
388
  let key = 0;
371
389
  const base = metadata.metadataBase;
@@ -474,7 +492,7 @@ function MetadataHead({ metadata, pathname = "/" }) {
474
492
  }, key++));
475
493
  if (og.url) elements.push(/* @__PURE__ */ jsx("meta", {
476
494
  property: "og:url",
477
- content: resolveUrl(og.url)
495
+ content: resolveCanonicalUrl(og.url, base, pathname, trailingSlash)
478
496
  }, key++));
479
497
  if (og.siteName) elements.push(/* @__PURE__ */ jsx("meta", {
480
498
  property: "og:site_name",
@@ -691,22 +709,22 @@ function MetadataHead({ metadata, pathname = "/" }) {
691
709
  const alt = metadata.alternates;
692
710
  if (alt.canonical) elements.push(/* @__PURE__ */ jsx("link", {
693
711
  rel: "canonical",
694
- href: resolveCanonicalUrl(alt.canonical, base, pathname)
712
+ href: resolveCanonicalUrl(alt.canonical, base, pathname, trailingSlash)
695
713
  }, key++));
696
714
  if (alt.languages) for (const [lang, href] of Object.entries(alt.languages)) elements.push(/* @__PURE__ */ jsx("link", {
697
715
  rel: "alternate",
698
716
  hrefLang: lang,
699
- href: resolveUrl(href)
717
+ href: resolveAlternateUrl(href, base, pathname, trailingSlash)
700
718
  }, key++));
701
719
  if (alt.media) for (const [media, href] of Object.entries(alt.media)) elements.push(/* @__PURE__ */ jsx("link", {
702
720
  rel: "alternate",
703
721
  media,
704
- href: resolveUrl(href)
722
+ href: resolveAlternateUrl(href, base, pathname, trailingSlash)
705
723
  }, key++));
706
724
  if (alt.types) for (const [type, href] of Object.entries(alt.types)) elements.push(/* @__PURE__ */ jsx("link", {
707
725
  rel: "alternate",
708
726
  type,
709
- href: resolveUrl(href)
727
+ href: resolveAlternateUrl(href, base, pathname, trailingSlash)
710
728
  }, key++));
711
729
  }
712
730
  if (metadata.verification) {