@thomas.f/prnext 0.1.17-alpha → 0.1.19-alpha

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 (202) hide show
  1. package/README.md +27 -868
  2. package/build/actions.mjs +27 -15
  3. package/build/analyze.mjs +180 -24
  4. package/build/app-config.mjs +13 -0
  5. package/build/app-static.mjs +34 -12
  6. package/build/app-styles.mjs +79 -0
  7. package/build/app.mjs +122 -46
  8. package/build/bundle-runtime.mjs +22 -9
  9. package/build/cache-components.mjs +1 -1
  10. package/build/cache-handlers.mjs +1 -1
  11. package/build/compact-css.mjs +53 -0
  12. package/build/compilation-cache.mjs +269 -0
  13. package/build/compilation-store.mjs +142 -0
  14. package/build/compiler.mjs +34 -10
  15. package/build/config.mjs +34 -3
  16. package/build/create.mjs +239 -0
  17. package/build/css-modules.mjs +56 -12
  18. package/build/deployment-assets.mjs +89 -0
  19. package/build/deployment-config.mjs +18 -0
  20. package/build/dev-css-assets.mjs +173 -0
  21. package/build/dev-css-update.mjs +18 -14
  22. package/build/dev-dependencies.mjs +1 -1
  23. package/build/dev-discovery.mjs +3 -3
  24. package/build/dev-lazy-build.mjs +77 -0
  25. package/build/dev.mjs +15 -8
  26. package/build/dynamic.mjs +14 -7
  27. package/build/edge.mjs +3 -3
  28. package/build/fonts.mjs +1 -1
  29. package/build/framework-imports.mjs +15 -5
  30. package/build/html-only-config.mjs +53 -0
  31. package/build/html-only-graph.mjs +67 -0
  32. package/build/incremental.mjs +112 -18
  33. package/build/index.mjs +90 -28
  34. package/build/instrumentation.mjs +30 -0
  35. package/build/lazy-dynamic-imports.mjs +137 -0
  36. package/build/metadata-routes.mjs +1 -1
  37. package/build/migrate.mjs +4 -4
  38. package/build/migration-audit.mjs +19 -31
  39. package/build/migration-imports.mjs +245 -0
  40. package/build/migration-scenarios-runner.mjs +73 -0
  41. package/build/migration-scenarios.mjs +72 -0
  42. package/build/native-discovery.mjs +65 -5
  43. package/build/native-packages.mjs +2 -0
  44. package/build/native-type-config.mjs +37 -0
  45. package/build/npm-packages.mjs +1 -0
  46. package/build/persistent-cache.mjs +18 -3
  47. package/build/precompress.mjs +1 -1
  48. package/build/prerender-cache.mjs +70 -0
  49. package/build/prerender-config.mjs +29 -0
  50. package/build/prerender-pages.mjs +19 -9
  51. package/build/prerender-pool.mjs +165 -0
  52. package/build/prerender-process-worker.mjs +58 -0
  53. package/build/prerender-worker.mjs +5 -5
  54. package/build/production-builder.mjs +59 -10
  55. package/build/react-compiler-report.mjs +225 -0
  56. package/build/react-compiler.mjs +139 -0
  57. package/build/runtime-contract.mjs +2 -2
  58. package/build/standalone.mjs +21 -3
  59. package/build/static-export.mjs +4 -4
  60. package/build/styles.mjs +6 -4
  61. package/build/transform.mjs +1 -1
  62. package/build/typed-routes.mjs +4 -0
  63. package/build/typescript-dev.mjs +35 -8
  64. package/build/typescript-native-worker.mjs +55 -0
  65. package/build/typescript-native.mjs +18 -0
  66. package/build/typescript-toolchain.mjs +19 -0
  67. package/build/typescript.mjs +6 -4
  68. package/build/webpack-graph.mjs +19 -6
  69. package/build/worker-memory.mjs +9 -0
  70. package/cli.mjs +70 -5
  71. package/compat/activity-cache.cjs +26 -0
  72. package/compat/after.cjs +6 -4
  73. package/compat/app-dynamic.cjs +12 -1
  74. package/compat/app-layout-context.cjs +52 -2
  75. package/compat/base64.cjs +10 -0
  76. package/compat/cache-handlers.cjs +15 -3
  77. package/compat/cache-wire.cjs +28 -0
  78. package/compat/cache.cjs +2 -1
  79. package/compat/cache.d.cts +4 -0
  80. package/compat/cookies.cjs +21 -5
  81. package/compat/data-cache.cjs +12 -8
  82. package/compat/flight-delta.cjs +152 -0
  83. package/compat/headers.cjs +4 -2
  84. package/compat/incremental-cache.cjs +20 -7
  85. package/compat/index.d.ts +69 -4
  86. package/compat/link.cjs +13 -10
  87. package/compat/navigation-authorization.cjs +9 -0
  88. package/compat/navigation-stages.cjs +19 -0
  89. package/compat/offline-store.cjs +11 -0
  90. package/compat/offline.cjs +5 -0
  91. package/compat/offline.d.cts +2 -0
  92. package/compat/redis-cache.cjs +201 -0
  93. package/compat/redis-cache.d.cts +37 -0
  94. package/compat/server-primitives.cjs +31 -11
  95. package/compat/server.d.cts +15 -2
  96. package/compat/static-generation.cjs +7 -0
  97. package/compat/use-cache.cjs +3 -0
  98. package/docs/ai/compatibility.md +248 -2
  99. package/docs/ai/data.md +165 -5
  100. package/docs/ai/deployment.md +158 -0
  101. package/docs/ai/quickstart.md +29 -4
  102. package/docs/ai/routing.md +93 -0
  103. package/docs/ai/troubleshooting.md +116 -2
  104. package/package.json +40 -9
  105. package/runtime/action-client.mjs +10 -4
  106. package/runtime/action-server.mjs +2 -0
  107. package/runtime/app-client.mjs +316 -92
  108. package/runtime/app-content.mjs +4 -3
  109. package/runtime/app-partial-client.mjs +3 -0
  110. package/runtime/app-partial.mjs +1 -1
  111. package/runtime/app-prefetch-constants.mjs +4 -0
  112. package/runtime/app-prefetch.mjs +2 -3
  113. package/runtime/app-render-key.mjs +5 -0
  114. package/runtime/app-render.mjs +88 -28
  115. package/runtime/app-routing.mjs +22 -9
  116. package/runtime/app-static.mjs +18 -8
  117. package/runtime/app-stream-html.mjs +10 -53
  118. package/runtime/app-style-selection.mjs +34 -0
  119. package/runtime/app-styles.mjs +154 -0
  120. package/runtime/atomic-file.mjs +27 -0
  121. package/runtime/cache-components-rsc.mjs +7 -2
  122. package/runtime/cache-request.mjs +2 -1
  123. package/runtime/deployment-client.mjs +19 -0
  124. package/runtime/dev-client.mjs +13 -3
  125. package/runtime/dev-compiler-broker.mjs +42 -0
  126. package/runtime/dev-file-changes.mjs +123 -8
  127. package/runtime/dev-indicator.mjs +8 -3
  128. package/runtime/dev-lazy-imports.mjs +121 -0
  129. package/runtime/dev-lazy-staging.mjs +14 -0
  130. package/runtime/dev-lazy-tool.mjs +16 -0
  131. package/runtime/dev-style-retention.mjs +15 -0
  132. package/runtime/development.mjs +83 -25
  133. package/runtime/document.mjs +1 -1
  134. package/runtime/edge.mjs +6 -5
  135. package/runtime/fetch-cache.mjs +10 -8
  136. package/runtime/flight-delta.mjs +74 -0
  137. package/runtime/flight-tap.mjs +28 -0
  138. package/runtime/fs.mjs +15 -8
  139. package/runtime/html-only.mjs +27 -0
  140. package/runtime/http.mjs +2 -0
  141. package/runtime/incremental-cache.mjs +25 -4
  142. package/runtime/instrumentation.mjs +49 -0
  143. package/runtime/lazy-import-client.mjs +38 -0
  144. package/runtime/navigation-recovery.mjs +36 -0
  145. package/runtime/navigation.mjs +7 -0
  146. package/runtime/node-html-stream.mjs +47 -0
  147. package/runtime/pages-client.mjs +14 -7
  148. package/runtime/pages-navigation.mjs +48 -63
  149. package/runtime/prefetch-scheduler.mjs +110 -0
  150. package/runtime/profile.mjs +14 -0
  151. package/runtime/profiles.json +2 -2
  152. package/runtime/render.mjs +3 -0
  153. package/runtime/route-pattern.mjs +44 -0
  154. package/runtime/rsc-worker.mjs +34 -12
  155. package/runtime/trace.mjs +78 -0
  156. package/runtime/watch-project.mjs +19 -15
  157. package/runtime/worker.mjs +70 -26
  158. package/vendor/nft/LICENSE +7 -0
  159. package/vendor/nft/README.md +16 -0
  160. package/vendor/nft/out/analyze.d.ts +8 -0
  161. package/vendor/nft/out/analyze.js +1216 -0
  162. package/vendor/nft/out/analyze.js.map +1 -0
  163. package/vendor/nft/out/cli.d.ts +2 -0
  164. package/vendor/nft/out/cli.js +94 -0
  165. package/vendor/nft/out/cli.js.map +1 -0
  166. package/vendor/nft/out/fs.d.ts +22 -0
  167. package/vendor/nft/out/fs.js +106 -0
  168. package/vendor/nft/out/fs.js.map +1 -0
  169. package/vendor/nft/out/index.d.ts +4 -0
  170. package/vendor/nft/out/index.js +25 -0
  171. package/vendor/nft/out/index.js.map +1 -0
  172. package/vendor/nft/out/node-file-trace.d.ts +41 -0
  173. package/vendor/nft/out/node-file-trace.js +380 -0
  174. package/vendor/nft/out/node-file-trace.js.map +1 -0
  175. package/vendor/nft/out/resolve-dependency.d.ts +6 -0
  176. package/vendor/nft/out/resolve-dependency.js +427 -0
  177. package/vendor/nft/out/resolve-dependency.js.map +1 -0
  178. package/vendor/nft/out/types.d.ts +66 -0
  179. package/vendor/nft/out/types.js +2 -0
  180. package/vendor/nft/out/types.js.map +1 -0
  181. package/vendor/nft/out/utils/ast-helpers.js +49 -0
  182. package/vendor/nft/out/utils/ast-helpers.js.map +1 -0
  183. package/vendor/nft/out/utils/binary-locators.js +88 -0
  184. package/vendor/nft/out/utils/binary-locators.js.map +1 -0
  185. package/vendor/nft/out/utils/get-package-base.js +30 -0
  186. package/vendor/nft/out/utils/get-package-base.js.map +1 -0
  187. package/vendor/nft/out/utils/interop-require.js +24 -0
  188. package/vendor/nft/out/utils/interop-require.js.map +1 -0
  189. package/vendor/nft/out/utils/node-version.js +10 -0
  190. package/vendor/nft/out/utils/node-version.js.map +1 -0
  191. package/vendor/nft/out/utils/sharedlib-emit.js +33 -0
  192. package/vendor/nft/out/utils/sharedlib-emit.js.map +1 -0
  193. package/vendor/nft/out/utils/special-cases.js +347 -0
  194. package/vendor/nft/out/utils/special-cases.js.map +1 -0
  195. package/vendor/nft/out/utils/static-eval.js +579 -0
  196. package/vendor/nft/out/utils/static-eval.js.map +1 -0
  197. package/vendor/nft/out/utils/types.js +2 -0
  198. package/vendor/nft/out/utils/types.js.map +1 -0
  199. package/vendor/nft/out/utils/wrappers.js +659 -0
  200. package/vendor/nft/out/utils/wrappers.js.map +1 -0
  201. package/vendor/nft/package.json +4 -0
  202. package/vendor/nft/upstream.json +51 -0
package/compat/index.d.ts CHANGED
@@ -13,6 +13,13 @@ export interface InstantSample {
13
13
  }
14
14
  export type InstantConfig = boolean | { level?: 'warning' | 'experimental-error'; unstable_samples?: InstantSample[]; unstable_disableValidation?: true; unstable_disableDevValidation?: true; unstable_disableBuildValidation?: true };
15
15
 
16
+ export interface ReactCompilerOptions {
17
+ compilationMode?: 'infer' | 'annotation';
18
+ panicThreshold?: 'none' | 'critical_errors' | 'all_errors';
19
+ /** PRNext requires matching React 19 peers. */
20
+ target?: '19';
21
+ }
22
+
16
23
  export type TurbopackCondition = 'browser' | 'foreign' | 'development' | 'production' | 'node' | 'edge-light'
17
24
  | {all: TurbopackCondition[]} | {any: TurbopackCondition[]} | {not: TurbopackCondition}
18
25
  | {path: string | RegExp; content?: RegExp} | {path?: string | RegExp; content: RegExp};
@@ -117,9 +124,17 @@ export interface NextConfig {
117
124
  /** PRNext only. CLI > environment > build configuration > profile.
118
125
  * 0 disables the corresponding retirement; keepWarm retains one lane per used host.
119
126
  */
127
+ /** Soft RSS retirement after three one-second samples; active work drains. */
128
+ workerMemory?: false | { maxRssMb: number };
120
129
  workerRetirement?: { idleSeconds?: number; laneIdleSeconds?: number; keepWarm?: boolean };
121
130
  /** Import selected Node routes on startup; top-level module code executes. RSS is a soft import budget. */
122
131
  workerPreload?: false | { routes: string[]; maxRssMb?: number; timeoutMs?: number };
132
+ /** Keep bounded previous public hashed assets. false disables carry-over. Defaults: one hour, two generations, 64 MiB. */
133
+ deploymentRetention?: false | {maxAge?: number; maxGenerations?: number; maxBytes?: number};
134
+ /** PRNext production prerender processes; maxMemoryMb bounds their combined observed memory. */
135
+ prerender?: {workers?: number; maxMemoryMb?: number};
136
+ /** Explicit App route patterns that must produce complete static HTML without browser JavaScript. */
137
+ htmlOnly?: string[];
123
138
  /** Bounded active development routes; default retains five requested routes. */
124
139
  onDemandEntries?: { maxInactiveAge?: number; pagesBufferLength?: number };
125
140
  /** PRNext development menu; bottom-left by default. false hides it without disabling errors or Fast Refresh. */
@@ -130,8 +145,8 @@ export interface NextConfig {
130
145
  images?: import('./image.cjs').ImageConfig;
131
146
  pageExtensions?: string[];
132
147
  reactStrictMode?: boolean;
133
- /** Enabled React Compiler transforms are not implemented. */
134
- reactCompiler?: false;
148
+ /** Opt in to the bundled official React Compiler for application components. Disabled by default. */
149
+ reactCompiler?: boolean | ReactCompilerOptions;
135
150
  /** Generate route completions and validate Link/router destinations. */
136
151
  typedRoutes?: boolean;
137
152
  /** Native CSS for default styles; custom PostCSS pipelines remain authoritative. */
@@ -139,6 +154,9 @@ export interface NextConfig {
139
154
  /** Production checks use the project's installed TypeScript without emitting files. */
140
155
  typescript?: { ignoreBuildErrors?: boolean; tsconfigPath?: string; devChecks?: boolean };
141
156
  cacheComponents?: boolean;
157
+ partialPrefetching?: boolean;
158
+ /** App Router inactive layout branches. Defaults to 4 entries for 120 seconds. */
159
+ navigationCache?: false | {maxEntries?: number; ttlMs?: number; authCookies?: readonly string[]};
142
160
  cacheLife?: Record<string, import('./cache.cjs').CacheLife>;
143
161
  transpilePackages?: string[];
144
162
  serverExternalPackages?: string[];
@@ -149,7 +167,7 @@ export interface NextConfig {
149
167
  implementation?: 'sass' | 'sass-embedded';
150
168
  [option: string]: unknown;
151
169
  };
152
- experimental?: { nextScriptWorkers?: boolean; optimizePackageImports?: string[]; proxyClientMaxBodySize?: number | string; serverActions?: true | { allowedOrigins?: string[]; bodySizeLimit?: number | string } };
170
+ experimental?: { nextScriptWorkers?: boolean; /** App Router development only; compile literal client imports on first use. */ lazyDynamicImports?: boolean; optimizePackageImports?: string[]; proxyClientMaxBodySize?: number | string; serverActions?: true | { allowedOrigins?: string[]; bodySizeLimit?: number | string } };
153
171
  trailingSlash?: boolean;
154
172
  skipTrailingSlashRedirect?: boolean;
155
173
  skipMiddlewareUrlNormalize?: boolean;
@@ -196,7 +214,45 @@ export interface StaticBuildOptions {
196
214
  distDir?: string;
197
215
  dev?: false;
198
216
  }
199
- export interface BuildResult { buildId: string; outputDirectory: string; }
217
+ export interface ReactCompilerReport {
218
+ version: 1;
219
+ compilerVersion: string | null;
220
+ enabled: boolean;
221
+ buildId: string | null;
222
+ mode: 'development' | 'production';
223
+ /** Incremental graphs can reuse inputs without observing their transforms. */
224
+ scope: 'observed-transforms';
225
+ options: boolean | ReactCompilerOptions | null;
226
+ truncated: boolean;
227
+ summary: {files: number; functions: number; optimized: number; skipped: number; errors: number; diagnostics: number; mixed: number; transforms: number; cacheReuses: number};
228
+ files: Array<{
229
+ file: string; transforms: number; cacheReuses: number; targets: string[]; notes: string[];
230
+ functions: Array<{
231
+ name: string | null;
232
+ start: {line: number; column: number} | null;
233
+ end: {line: number; column: number} | null;
234
+ status: 'optimized' | 'skipped' | 'error' | 'diagnostic' | 'mixed';
235
+ targets: string[];
236
+ events: Array<{
237
+ kind: 'CompileSuccess' | 'CompileSkip' | 'CompileError' | 'CompileDiagnostic' | 'PipelineError';
238
+ status: 'optimized' | 'skipped' | 'error' | 'diagnostic' | 'mixed';
239
+ reason: string | null; category: string | null; targets: string[]; policy?: boolean;
240
+ memoSlots?: number; memoBlocks?: number; memoValues?: number;
241
+ }>;
242
+ }>;
243
+ }>;
244
+ graphs: Array<{target: string; metadataAvailable: boolean; projectJavaScriptInputs: number; observedFiles: number; unobservedInputs: number}>;
245
+ }
246
+ export interface CompilationCacheReport {
247
+ version: 1;
248
+ scope: 'eligible-production-compilation-graphs';
249
+ hits: number;
250
+ misses: number;
251
+ bypasses: number;
252
+ reusedBytes: number;
253
+ kinds: Array<{kind: string; hits: number; misses: number; bypasses: number; writes: number; reusedBytes: number; reasons: Record<string, number>}>;
254
+ }
255
+ export interface BuildResult { buildId: string; outputDirectory: string; reactCompilerReport?: ReactCompilerReport; compilationCache?: CompilationCacheReport; }
200
256
  export interface ProductionBuilder {
201
257
  build(options?: {envMode?: 'production' | 'test'}): Promise<BuildResult>;
202
258
  stats(): {contexts: number; created: number; reused: number};
@@ -212,3 +268,12 @@ export function buildStatic(projectRoot: string, options?: StaticBuildOptions):
212
268
  export function readBuildOutput(buildDirectory: string): Promise<BuildOutput>;
213
269
  export function validateBuildOutput(value: unknown): BuildOutput;
214
270
  export type { DocumentContext, DocumentInitialProps, DocumentProps } from './document.cjs';
271
+
272
+ /** Server instrumentation hooks, compatible with Next.js instrumentation files. */
273
+ export namespace Instrumentation {
274
+ export type onRequestError = (error: unknown, request: Readonly<{path: string; method: string; headers: Record<string,string|string[]>}>, context: {
275
+ routerKind: 'App Router' | 'Pages Router'; routePath: string; routeType: 'render' | 'route' | 'action' | 'proxy';
276
+ renderSource: 'react-server-components' | 'react-server-components-payload' | 'server-rendering';
277
+ revalidateReason: 'on-demand' | 'stale' | undefined; renderType: 'dynamic' | 'dynamic-resume';
278
+ }) => void | Promise<void>;
279
+ }
package/compat/link.cjs CHANGED
@@ -7,7 +7,7 @@ const { addBasePath, hasBasePath, normalizeTrailingSlash } = require('./paths.cj
7
7
  const { localizedHref } = require('./locale.cjs');
8
8
 
9
9
  const Link = React.forwardRef(function Link({ href, as, children, replace, scroll, shallow, prefetch,
10
- locale, legacyBehavior, passHref, onClick, onNavigate, onMouseEnter, onTouchStart, ...props }, ref) {
10
+ locale, legacyBehavior, passHref, onClick, onNavigate, onMouseEnter, onTouchStart, onFocus, ...props }, ref) {
11
11
  const app = React.useContext(AppRouterContext);
12
12
  const pages = React.useContext(RouterContext);
13
13
  const anchorRef = React.useRef(null);
@@ -19,14 +19,15 @@ const Link = React.forwardRef(function Link({ href, as, children, replace, scrol
19
19
  : target.startsWith('//') ? target : addBasePath(target, basePath);
20
20
  const publicTarget = normalizeTrailingSlash(mounted, policy);
21
21
  const source = formatUrl(href);
22
- const requestPrefetch = React.useCallback(() => {
23
- if (app?.router.partialPrefetch && prefetch !== false && process.env.NODE_ENV === 'production') app.router.prefetch(target);
24
- if (!app && pages && process.env.NODE_ENV === 'production') void pages.prefetch(source, as ? target : undefined, {locale}).catch(() => {});
25
- }, [app, pages, source, target, as, prefetch]);
22
+ const requestPrefetch = React.useCallback((priority = 'intent') => {
23
+ if (prefetch === false || process.env.NODE_ENV !== 'production') return;
24
+ if (app && (app.router.partialPrefetch || prefetch === true)) app.router.prefetch(target, { priority, full: prefetch === true });
25
+ if (!app && pages) void pages.prefetch(source, as ? target : undefined, { locale, priority }).catch(() => {});
26
+ }, [app, pages, source, target, as, locale, prefetch]);
26
27
  React.useEffect(() => {
27
- if ((app ? !app.router.partialPrefetch : !pages) || prefetch === false || process.env.NODE_ENV !== 'production' || typeof IntersectionObserver === 'undefined' || !anchorRef.current) return;
28
+ if ((app ? !app.router.partialPrefetch && prefetch !== true : !pages) || prefetch === false || process.env.NODE_ENV !== 'production' || typeof IntersectionObserver === 'undefined' || !anchorRef.current) return;
28
29
  const observer = new IntersectionObserver(entries => {
29
- if (entries.some(entry => entry.isIntersecting)) { observer.disconnect(); requestPrefetch(); }
30
+ if (entries.some(entry => entry.isIntersecting)) { observer.disconnect(); requestPrefetch('viewport'); }
30
31
  }, { rootMargin: '200px' });
31
32
  observer.observe(anchorRef.current);
32
33
  return () => observer.disconnect();
@@ -53,16 +54,18 @@ const Link = React.forwardRef(function Link({ href, as, children, replace, scrol
53
54
  void pages[replace ? 'replace' : 'push'](href, as, { scroll, shallow, locale }).catch(error => console.error(error));
54
55
  } else if (replace) { event.preventDefault(); window.location.replace(publicTarget); }
55
56
  }
56
- function hover(event) { onMouseEnter?.(event); requestPrefetch(); }
57
- function touch(event) { onTouchStart?.(event); requestPrefetch(); }
57
+ function hover(event) { onMouseEnter?.(event); if (!event.defaultPrevented) requestPrefetch(); }
58
+ function touch(event) { onTouchStart?.(event); if (!event.defaultPrevented) requestPrefetch(); }
59
+ function focus(event) { onFocus?.(event); if (!event.defaultPrevented) requestPrefetch(); }
58
60
  if (legacyBehavior) {
59
61
  const child = React.Children.only(children);
60
62
  return React.cloneElement(child, { ...props, href: publicTarget, ref: setRef,
61
63
  onMouseEnter(event) { child.props.onMouseEnter?.(event); hover(event); },
62
64
  onTouchStart(event) { child.props.onTouchStart?.(event); touch(event); },
65
+ onFocus(event) { child.props.onFocus?.(event); focus(event); },
63
66
  onClick(event) { child.props.onClick?.(event); if (!event.defaultPrevented) click(event); } });
64
67
  }
65
- return React.createElement('a', { ...props, href: publicTarget, ref: setRef, onClick: click, onMouseEnter: hover, onTouchStart: touch }, children);
68
+ return React.createElement('a', { ...props, href: publicTarget, ref: setRef, onClick: click, onMouseEnter: hover, onTouchStart: touch, onFocus: focus }, children);
66
69
  });
67
70
  module.exports = Link;
68
71
  module.exports.default = Link;
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+ const { createHmac } = require('node:crypto');
3
+ function navigationAuthorization(context, secret, cookieNames) {
4
+ if (!secret || context?.staticState) return undefined;
5
+ const names = cookieNames === undefined ? null : new Set(cookieNames);
6
+ const cookies = context.cookies.getAll().filter(cookie => !names || names.has(cookie.name)).map(({ name, value }) => [name, value]).sort((a, b) => a[0].localeCompare(b[0]));
7
+ return createHmac('sha256', secret).update(JSON.stringify([cookies, context.headers.get('authorization') || ''])).digest('hex');
8
+ }
9
+ module.exports = { navigationAuthorization };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+ const { optionalRequestContext, inCacheScope } = require('./data-cache.cjs');
3
+
4
+ function postponeStage(name) {
5
+ throw Object.assign(new Error(`Deferred until ${name}.`), { code: 'PRNEXT_PPR_DYNAMIC', digest: 'PRNEXT_PPR_DYNAMIC' });
6
+ }
7
+ function stage(name) {
8
+ const context = optionalRequestContext();
9
+ if (!context?.cacheComponents) throw new Error(`${name}() requires cacheComponents: true.`);
10
+ if (inCacheScope()) throw new Error(`${name}() cannot run inside a cached function. Call it before invoking the cached function.`);
11
+ // Static prerenders deliberately continue through both gates. Cacheable work
12
+ // remains prerenderable and is not made request-dependent by either API.
13
+ if (context.staticState) return;
14
+ if (context.navigationStage === 'shell' && (name === 'navigation' || context.partialPrefetching)) postponeStage(name);
15
+ if (context.navigationStage === 'prefetch' && name === 'navigation') postponeStage(name);
16
+ }
17
+ async function navigation() { stage('navigation'); }
18
+ async function prefetch() { stage('prefetch'); }
19
+ module.exports = { navigation, prefetch, postponeStage };
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+ const waiting=new Set(),listeners=new Set();
3
+ const changed=()=>{for(const listener of listeners)listener();};
4
+ const snapshot=()=>waiting.size>0||globalThis.navigator?.onLine===false;
5
+ function subscribe(listener){
6
+ if(!listeners.size){globalThis.window?.addEventListener('online',changed);globalThis.window?.addEventListener('offline',changed);}
7
+ listeners.add(listener);
8
+ return()=>{listeners.delete(listener);if(!listeners.size){globalThis.window?.removeEventListener('online',changed);globalThis.window?.removeEventListener('offline',changed);}};
9
+ }
10
+ function beginWait(){const id={};waiting.add(id);changed();let ended=false;return()=>{if(ended)return;ended=true;waiting.delete(id);changed();};}
11
+ module.exports={snapshot,subscribe,beginWait};
@@ -0,0 +1,5 @@
1
+ 'use client';
2
+ 'use strict';
3
+ const React=require('react'),store=require('./offline-store.cjs');
4
+ function useOffline(){return React.useSyncExternalStore(store.subscribe,store.snapshot,()=>false);}
5
+ module.exports={useOffline};
@@ -0,0 +1,2 @@
1
+ /** True when the browser is offline or a navigation is waiting for connectivity. */
2
+ export declare function useOffline(): boolean;
@@ -0,0 +1,201 @@
1
+ 'use strict';
2
+ const {createHash,randomBytes}=require('node:crypto');
3
+ const MAX_VALUE=2*1024*1024,MAX_META=65536;
4
+ const hash=value=>createHash('sha256').update(value).digest('hex');
5
+ const ACQUIRE=`
6
+ if not redis.call('HGET',KEYS[2],'_incarnation') then redis.call('HSET',KEYS[2],'_incarnation',ARGV[3],'_epoch',ARGV[3]) end
7
+ redis.call('PEXPIRE',KEYS[2],ARGV[4])
8
+ local epoch=redis.call('HGET',KEYS[2],'_epoch');local incarnation=redis.call('HGET',KEYS[2],'_incarnation')
9
+ if redis.call('SET',KEYS[1],ARGV[1],'NX','PX',ARGV[2]) then return {1,epoch,incarnation} end
10
+ return {0}`;
11
+ const RELEASE=`if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]) end return 0`;
12
+ const READ=`
13
+ local fields=redis.call('HMGET',KEYS[1],'meta','value')
14
+ if not fields[1] or not fields[2] or string.len(fields[1])>65536 or string.len(fields[2])>tonumber(ARGV[2]) then return nil end
15
+ local meta=cjson.decode(fields[1]); local extra=cjson.decode(ARGV[1]); local stale=0
16
+ if redis.call('HGET',KEYS[2],'_incarnation')~=meta.incarnation then return nil end
17
+ local t=redis.call('TIME'); local now=tonumber(t[1])*1000+math.floor(tonumber(t[2])/1000)
18
+ local seen={}
19
+ for _,id in ipairs(meta.tagIds) do seen[id]=true end
20
+ for _,id in ipairs(extra) do seen[id]=true end
21
+ for id,_ in pairs(seen) do
22
+ local value=redis.call('HGET',KEYS[2],id)
23
+ if value then
24
+ local tag=cjson.decode(value)
25
+ if tag.generation~=(meta.versions[id] or '0') then
26
+ if tag.deadline>=0 and tag.deadline<=now then return nil end
27
+ stale=1
28
+ end
29
+ end
30
+ end
31
+ return {fields[1],fields[2],math.max(0,now-meta.storedAt),stale}`;
32
+ const WRITE=`
33
+ if redis.call('GET',KEYS[3])~=ARGV[1] or redis.call('HGET',KEYS[2],'_epoch')~=ARGV[2] then return 0 end
34
+ local meta=cjson.decode(ARGV[3]); meta.versions={};meta.incarnation=ARGV[5];meta.writer=ARGV[1]
35
+ local t=redis.call('TIME'); local now=tonumber(t[1])*1000+math.floor(tonumber(t[2])/1000)
36
+ meta.storedAt=now-tonumber(ARGV[6])
37
+ for _,id in ipairs(meta.tagIds) do
38
+ local value=redis.call('HGET',KEYS[2],id)
39
+ meta.versions[id]=value and cjson.decode(value).generation or '0'
40
+ end
41
+ local encoded=cjson.encode(meta)
42
+ if string.len(encoded)>65536 then return 0 end
43
+ redis.call('HSET',KEYS[1],'meta',encoded,'value',ARGV[4])
44
+ redis.call('PEXPIRE',KEYS[1],ARGV[7]);redis.call('PEXPIRE',KEYS[2],ARGV[8]);return 1`;
45
+ const INVALIDATE=`
46
+ local generation=ARGV[3];local ids=cjson.decode(ARGV[4]);local added=0
47
+ for _,id in ipairs(ids) do if redis.call('HEXISTS',KEYS[1],id)==0 then added=added+1 end end
48
+ if redis.call('HLEN',KEYS[1])+added>4098 then redis.call('DEL',KEYS[1]) end
49
+ if not redis.call('HGET',KEYS[1],'_incarnation') then redis.call('HSET',KEYS[1],'_incarnation',generation) end
50
+ redis.call('HSET',KEYS[1],'_epoch',generation)
51
+ local t=redis.call('TIME');local now=tonumber(t[1])*1000+math.floor(tonumber(t[2])/1000)
52
+ local expiry=tonumber(ARGV[1]);local deadline=expiry<0 and -1 or now+expiry
53
+ for _,id in ipairs(ids) do redis.call('HSET',KEYS[1],id,cjson.encode({generation=generation,deadline=deadline})) end
54
+ redis.call('PEXPIRE',KEYS[1],ARGV[2])
55
+ return 1`;
56
+ const REMOVE=`local meta=redis.call('HGET',KEYS[1],'meta');if meta and cjson.decode(meta).writer==ARGV[1] then return redis.call('DEL',KEYS[1]) end return 0`;
57
+
58
+ function tags(values=[]){
59
+ if(!Array.isArray(values)||values.length>256||values.some(value=>typeof value!=='string'||!value||Buffer.byteLength(value)>8192))throw new TypeError('Redis cache tags must be bounded nonempty strings.');
60
+ return [...new Set(values)];
61
+ }
62
+ function positive(value,fallback,min,max,name){value??=fallback;if(!Number.isSafeInteger(value)||value<min||value>max)throw new TypeError(`Redis cache ${name} must be between ${min} and ${max}.`);return value;}
63
+ function pause(ms,signal){return new Promise((resolve,reject)=>{const done=error=>{clearTimeout(timer);signal?.removeEventListener('abort',abort);error?reject(error):resolve();};const abort=()=>done(signal.reason);const timer=setTimeout(done,ms);signal?.addEventListener('abort',abort,{once:true});if(signal?.aborted)abort();});}
64
+ function boundedRead(reader,ms){return new Promise((resolve,reject)=>{const timer=setTimeout(()=>reject(new Error('Redis cache value read timed out.')),ms);reader.read().then(value=>{clearTimeout(timer);resolve(value);},error=>{clearTimeout(timer);reject(error);});});}
65
+
66
+ /** First-party Cache Components handler; Redis is loaded only when configured. */
67
+ function createRedisCache(options={},maxValueBytes=MAX_VALUE){
68
+ if(!options||typeof options!=='object'||Array.isArray(options))throw new TypeError('Redis cache options must be an object.');
69
+ const known=new Set(['namespace','urlEnv','client','leaseMs','waitMs','commandTimeoutMs','maxAgeSeconds']);
70
+ for(const key of Object.keys(options))if(!known.has(key))throw new TypeError(`Unknown Redis cache option: ${key}`);
71
+ if(options.client!==undefined&&typeof options.client?.withTypeMapping!=='function')throw new TypeError('Redis cache client must be a node-redis client.');
72
+ if(typeof options.namespace!=='string'||!options.namespace||Buffer.byteLength(options.namespace)>256)throw new TypeError('Redis cache requires an application-specific namespace.');
73
+ const urlEnv=options.urlEnv??'REDIS_URL';if(typeof urlEnv!=='string'||!/^[_A-Za-z][_A-Za-z0-9]*$/.test(urlEnv))throw new TypeError('Redis cache urlEnv must name an environment variable.');
74
+ const leaseMs=positive(options.leaseMs,30000,1000,120000,'leaseMs'),waitMs=positive(options.waitMs,2500,100,3500,'waitMs');
75
+ const commandTimeoutMs=positive(options.commandTimeoutMs,1000,100,2000,'commandTimeoutMs'),maxAgeSeconds=positive(options.maxAgeSeconds,604800,1,2592000,'maxAgeSeconds');
76
+ if(waitMs+2*commandTimeoutMs>4500)throw new TypeError('Redis cache waitMs plus twice commandTimeoutMs must not exceed 4500 ms.');
77
+ const prefix='prnext:v1:{'+hash(options.namespace)+'}:',epochKey=prefix+'tags',retention=String(maxAgeSeconds*1000+leaseMs+60000);
78
+ let owned,connecting,closed=false;
79
+ async function client(){
80
+ if(closed)throw new Error('Redis cache is closed.');
81
+ if(options.client){
82
+ if(options.client.isReady===false)throw new Error('Redis cache client is not connected.');
83
+ return options.client;
84
+ }
85
+ if(owned?.isReady)return owned;
86
+ if(!connecting){
87
+ connecting=(async()=>{
88
+ const url=process.env[urlEnv];
89
+ if(!url||!/^rediss?:\/\//.test(url))throw new Error(`Set ${urlEnv} to a Redis connection URL.`);
90
+ // Use node-redis's base client; Redis Stack command packages are not
91
+ // needed by this provider's bounded EVAL/HASH protocol.
92
+ const {createClient}=require('@redis/client');
93
+ owned?.destroy();owned=createClient({url,disableOfflineQueue:true,commandsQueueMaxLength:128,socket:{connectTimeout:commandTimeoutMs,reconnectStrategy:false}});
94
+ owned.on('error',()=>{});await owned.connect();return owned;
95
+ })().finally(()=>{connecting=undefined;});
96
+ }
97
+ return connecting;
98
+ }
99
+ async function command(args,signal){
100
+ const connection=await client();
101
+ // '$' (36) is RESP's binary-safe bulk-string type, as exposed by node-redis.
102
+ const binary=connection.withTypeMapping({36:Buffer});
103
+ const deadline=AbortSignal.timeout(commandTimeoutMs);
104
+ return binary.sendCommand(args,{abortSignal:signal?AbortSignal.any([signal,deadline]):deadline});
105
+ }
106
+ const keys=key=>{if(typeof key!=='string'||!key||Buffer.byteLength(key)>8192)throw new TypeError('Invalid Redis cache key.');const id=hash(key);return {entry:prefix+'entry:'+id,lock:prefix+'lease:'+id};};
107
+ async function acquire(key,{signal}={}){
108
+ const {lock}=keys(key),token=randomBytes(24).toString('hex'),until=performance.now()+waitMs;
109
+ for(;;){
110
+ signal?.throwIfAborted();
111
+ const started=performance.now();
112
+ const result=await command(['EVAL',ACQUIRE,'2',lock,epochKey,token,String(leaseMs),randomBytes(24).toString('hex'),retention],signal);
113
+ if(Number(result[0])===1){let released=false;return {token,started,epoch:String(result[1]),incarnation:String(result[2]),async release(){if(released)return;released=true;await command(['EVAL',RELEASE,'1',lock,token]);}};}
114
+ if(performance.now()>=until)throw Object.assign(new Error('Redis cache fill wait exceeded its budget.'),{code:'PRNEXT_REDIS_BUSY'});
115
+ await pause(Math.min(50,Math.max(1,until-performance.now())),signal);
116
+ }
117
+ }
118
+ async function get(key,softTags=[]){
119
+ const result=await command(['EVAL',READ,'2',keys(key).entry,epochKey,JSON.stringify(tags(softTags).map(hash)),String(maxValueBytes)]);
120
+ if(!result)return undefined;
121
+ const meta=JSON.parse(result[0].toString('utf8')),bytes=result[1];
122
+ if(meta.version!==1||!Buffer.isBuffer(bytes)||bytes.length>maxValueBytes)throw new Error('Invalid Redis cache entry.');
123
+ const age=Number(result[2]);if(!Number.isFinite(age)||age<0)throw new Error('Invalid Redis cache age.');
124
+ let offset=0;
125
+ return {tags:meta.tags,stale:meta.stale,revalidate:Number(result[3])?0:meta.revalidate,expire:meta.expire,timestamp:Math.max(0,Date.now()-age),
126
+ value:new ReadableStream({pull(controller){if(offset===bytes.length){controller.close();return;}const end=Math.min(bytes.length,offset+65536);controller.enqueue(new Uint8Array(bytes.subarray(offset,end)));offset=end;}},{highWaterMark:0})};
127
+ }
128
+ async function set(key,pending,{lease,softTags=[]}={}){
129
+ if(!lease||!Number.isFinite(lease.started)){
130
+ // Observe and release a caller's value even when its lease is missing.
131
+ // An unfenced producer must never publish after an invalidation.
132
+ void Promise.resolve(pending).then(entry=>entry.value.cancel()).catch(()=>{});
133
+ throw new Error('Redis cache writes require the lease returned by acquire().');
134
+ }
135
+ const entry=await pending,entryTags=tags(entry.tags);
136
+ for(const field of ['timestamp','stale','revalidate','expire'])if(!Number.isFinite(entry[field])||entry[field]<0)throw new TypeError('Invalid Redis cache '+field);
137
+ const reader=entry.value.getReader(),chunks=[];let size=0,complete=false;
138
+ const until=performance.now()+commandTimeoutMs;
139
+ let reads=0;
140
+ try{for(;;){if(performance.now()>=until||++reads>10000)throw new Error('Redis cache value read timed out.');const part=await boundedRead(reader,Math.max(1,until-performance.now()));if(part.done){complete=true;break;}if(!(part.value instanceof Uint8Array)||(size+=part.value.byteLength)>maxValueBytes)throw new Error(`Redis cache value exceeds ${maxValueBytes/1024/1024} MiB.`);chunks.push(Buffer.from(part.value));}}
141
+ finally{if(!complete)void reader.cancel().catch(()=>{});reader.releaseLock();}
142
+ const meta={version:1,tags:entryTags,tagIds:tags([...entryTags,...softTags]).map(hash),stale:entry.stale,revalidate:entry.revalidate,expire:entry.expire};
143
+ const metadata=JSON.stringify(meta);if(Buffer.byteLength(metadata)>MAX_META)throw new Error('Redis cache metadata exceeds 64 KiB.');
144
+ const ttl=Math.ceil(Math.min(maxAgeSeconds,entry.expire)*1000);if(ttl<=0)return;
145
+ const {entry:entryKey,lock}=keys(key);
146
+ return Number(await command(['EVAL',WRITE,'3',entryKey,epochKey,lock,lease.token,lease.epoch,metadata,Buffer.concat(chunks,size),lease.incarnation,String(Math.max(0,performance.now()-lease.started)),String(ttl),retention]))===1;
147
+ }
148
+ async function updateTags(values,durations){
149
+ const list=tags(values);if(!list.length)return;
150
+ const expire=durations?.expire;if(expire!==undefined&&(!Number.isFinite(expire)||expire<0))throw new TypeError('Invalid Redis tag expiration.');
151
+ await command(['EVAL',INVALIDATE,'1',epochKey,String(expire===undefined?-1:Math.ceil(expire*1000)),retention,randomBytes(24).toString('hex'),JSON.stringify(list.map(hash))]);
152
+ }
153
+ return {get,set,acquire,updateTags,
154
+ async release(key,lease){if(lease?.token)await command(['EVAL',RELEASE,'1',keys(key).lock,lease.token]);},
155
+ async remove(key,lease){if(lease?.token)await command(['EVAL',REMOVE,'1',keys(key).entry,lease.token]);},
156
+ async refreshTags(){},async getExpiration(){return Infinity;},async close(){closed=true;await connecting?.catch(()=>{});if(owned?.isOpen)owned.destroy();}};
157
+ }
158
+
159
+ /** Legacy incremental handler for HTML/ISR, fetch and unstable_cache. */
160
+ function createRedisIncrementalCache(options){
161
+ const {serialize,deserialize}=require('node:v8');
162
+ const backend=createRedisCache({leaseMs:60000,...options},16*1024*1024);
163
+ const keyFor=key=>'incremental:v8-'+process.versions.v8.split('.')[0]+':'+key;
164
+ function leaseFor(lease){
165
+ if(!lease||!['token','epoch','incarnation'].every(field=>typeof lease[field]==='string'&&/^[a-f0-9]{48}$/.test(lease[field]))||!Number.isFinite(lease.startedAt))throw new Error('Incremental Redis writes require a valid generation lease.');
166
+ return {...lease,started:performance.now()-Math.max(0,Date.now()-lease.startedAt)};
167
+ }
168
+ return class RedisIncrementalCache {
169
+ get prnextLeaseVersion(){return 1;}
170
+ async acquire(key,{signal}={}){
171
+ const lease=await backend.acquire(keyFor(key),{signal});
172
+ return {token:lease.token,epoch:lease.epoch,incarnation:lease.incarnation,startedAt:Date.now()};
173
+ }
174
+ async release(key,lease){await backend.release(keyFor(key),lease);}
175
+ async get(key,context={}){
176
+ const entry=await backend.get(keyFor(key),tags([...new Set([...(context.tags||[]),...(context.softTags||[])])]));
177
+ if(!entry)return null;
178
+ const bytes=Buffer.from(await new Response(entry.value).arrayBuffer());
179
+ const payload=deserialize(bytes);
180
+ if(payload?.version!==1||!Number.isFinite(payload.lastModified)||!payload.value||typeof payload.value.kind!=='string')throw new Error('Invalid incremental Redis payload.');
181
+ return {value:payload.value,lastModified:payload.lastModified,isStale:entry.revalidate===0};
182
+ }
183
+ async set(key,value,context={}){
184
+ const lease=leaseFor(context.prnextLease);
185
+ if(value===null){await backend.remove(keyFor(key),lease);return true;}
186
+ const raw=context.cacheControl?.revalidate??value.revalidate??value._prnext?.revalidate??false;
187
+ if(raw!==false&&(!Number.isFinite(raw)||raw<0))throw new TypeError('Invalid incremental Redis revalidate value.');
188
+ if(raw===0)return true;
189
+ const revalidate=raw===false?31536000:raw;
190
+ const entryTags=tags([...new Set([...(context.tags||[]),...(context.softTags||[])])]);
191
+ const bytes=serialize({version:1,lastModified:Date.now(),value});
192
+ if(bytes.length>16*1024*1024)throw new Error('Incremental Redis entry exceeds 16 MiB.');
193
+ return backend.set(keyFor(key),Promise.resolve({timestamp:Date.now(),stale:0,revalidate,expire:Math.max(revalidate,options?.maxAgeSeconds??604800),tags:entryTags,
194
+ value:new ReadableStream({start(controller){controller.enqueue(bytes);controller.close();}})}),{lease});
195
+ }
196
+ async revalidateTag(values,durations){await backend.updateTags(Array.isArray(values)?values:[values],durations);}
197
+ resetRequestCache(){}
198
+ async close(){await backend.close();}
199
+ };
200
+ }
201
+ module.exports={createRedisCache,createRedisIncrementalCache};
@@ -0,0 +1,37 @@
1
+ /// <reference types="node" />
2
+ export interface RedisCacheOptions {
3
+ /** Unique application namespace, shared by its replicas. */
4
+ namespace: string;
5
+ /** Runtime environment variable; the URL is never embedded in a build. Default REDIS_URL. */
6
+ urlEnv?: string;
7
+ /** An already-connected node-redis client. Its lifecycle remains yours. */
8
+ client?: {readonly isReady?: boolean; withTypeMapping(mapping: {36: BufferConstructor}): {sendCommand(args: (string | Buffer)[], options?: {abortSignal?: AbortSignal}): Promise<unknown>}};
9
+ leaseMs?: number;
10
+ waitMs?: number;
11
+ commandTimeoutMs?: number;
12
+ maxAgeSeconds?: number;
13
+ }
14
+ interface CacheEntry {value: ReadableStream<Uint8Array>; tags: string[]; timestamp: number; stale: number; revalidate: number; expire: number}
15
+ interface Lease {token: string; started: number; epoch: string; incarnation: string; release(): Promise<void>}
16
+ export declare function createRedisCache(options: RedisCacheOptions): {
17
+ get(key: string, softTags?: string[]): Promise<CacheEntry | undefined>;
18
+ set(key: string, entry: Promise<CacheEntry>, options?: {lease?: Lease; softTags?: string[]}): Promise<void>;
19
+ acquire(key: string, options?: {signal?: AbortSignal}): Promise<Lease>;
20
+ refreshTags(): Promise<void>;
21
+ getExpiration(): Promise<number>;
22
+ updateTags(tags: string[], durations?: {expire: number}): Promise<void>;
23
+ close(): Promise<void>;
24
+ };
25
+
26
+ export interface IncrementalRedisLease {token: string; epoch: string; incarnation: string; startedAt: number}
27
+ /** Configure its returned constructor as cacheHandler; Cache Components use createRedisCache separately. */
28
+ export declare function createRedisIncrementalCache(options: RedisCacheOptions): new (context?: unknown) => {
29
+ readonly prnextLeaseVersion: 1;
30
+ acquire(key: string, options?: {signal?: AbortSignal}): Promise<IncrementalRedisLease>;
31
+ release(key: string, lease: IncrementalRedisLease): Promise<void>;
32
+ get(key: string, context?: {tags?: string[]; softTags?: string[]}): Promise<{value: unknown; lastModified: number; isStale?: boolean} | null>;
33
+ set(key: string, value: unknown, context?: {prnextLease?: IncrementalRedisLease; tags?: string[]; softTags?: string[]; cacheControl?: {revalidate: number | false}}): Promise<boolean>;
34
+ revalidateTag(tags: string | string[], durations?: {expire?: number}): Promise<void>;
35
+ resetRequestCache(): void;
36
+ close(): Promise<void>;
37
+ };
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
- const { CookieStore, ResponseCookieStore } = require('./cookies.cjs');
2
+ const { RequestCookieStore, ResponseCookieStore } = require('./cookies.cjs');
3
3
  const { hasBasePath, addBasePath, removeBasePath } = require('./paths.cjs');
4
4
  const { localePath, withLocale } = require('./locale.cjs');
5
5
 
@@ -14,21 +14,37 @@ function middlewareHeaders(init, headers) {
14
14
  headers.set('x-middleware-override-headers', names.join(','));
15
15
  }
16
16
 
17
+ function nextDataPath(pathname) {
18
+ const match = /^\/_next\/data\/([^/]+)\/(.*)\.json$/.exec(pathname);
19
+ if (!match) return { pathname };
20
+ const parts = match[2].split('/');
21
+ return { buildId: match[1], pathname: parts[0] === 'index' ? '/' : '/' + parts.join('/') };
22
+ }
23
+
17
24
  class NextURL extends URL {
18
25
  constructor(input, baseOrOptions, options) {
19
26
  const base = typeof baseOrOptions === 'string' || baseOrOptions instanceof URL ? baseOrOptions : undefined;
20
27
  const config = options || (base ? {} : baseOrOptions) || {};
21
28
  super(input, base || config.base);
22
29
  this._nextConfig = config.nextConfig || {};
30
+ this._forceLocale = config.forceLocale === true;
31
+ this._parseData = !(this._nextConfig.skipProxyUrlNormalize ?? this._nextConfig.skipMiddlewareUrlNormalize);
23
32
  this._trailingSlash = super.pathname === '/' ? !!this._nextConfig.trailingSlash : super.pathname.endsWith('/');
24
33
  this._basePath = hasBasePath(super.pathname, this._nextConfig.basePath) ? this._nextConfig.basePath || '' : '';
25
- this._locale = this._nextConfig.i18n ? localePath(removeBasePath(super.pathname, this._basePath), {...this._nextConfig.i18n,defaultLocale:this.defaultLocale}).locale : '';
34
+ const logical = this._parseData ? nextDataPath(removeBasePath(super.pathname, this._basePath)) : { pathname: removeBasePath(super.pathname, this._basePath) };
35
+ this._buildId = logical.buildId;
36
+ this._locale = this._nextConfig.i18n ? localePath(logical.pathname, {...this._nextConfig.i18n,defaultLocale:this.defaultLocale}).locale : '';
37
+ }
38
+ get pathname() {
39
+ const raw = removeBasePath(super.pathname, this._basePath);
40
+ return localePath(this._parseData ? nextDataPath(raw).pathname : raw, this._nextConfig.i18n).pathname;
26
41
  }
27
- get pathname() { return localePath(removeBasePath(super.pathname, this._basePath), this._nextConfig.i18n).pathname; }
28
42
  set pathname(value) { super.pathname = addBasePath(withLocale(String(value).startsWith('/') ? value : `/${value}`, this._locale, this.defaultLocale), this._basePath); }
29
43
  get defaultLocale() { return this.domainLocale?.defaultLocale || this._nextConfig.i18n?.defaultLocale; }
30
44
  get domainLocale() { return this._nextConfig.i18n?.domains?.find(value=>value.domain.toLowerCase()===this.host.toLowerCase()); }
31
45
  get locale() { return this._locale; }
46
+ get buildId() { return this._buildId; }
47
+ set buildId(value) { this._buildId = value; }
32
48
  set locale(value) {
33
49
  if (!this._nextConfig.i18n?.locales.includes(value)) throw new TypeError(`Unknown locale: ${value}`);
34
50
  const pathname=this.pathname;this._locale=value;this.pathname=pathname;
@@ -43,8 +59,13 @@ class NextURL extends URL {
43
59
  set trailingSlash(value) { this._trailingSlash = value; }
44
60
  get href() {
45
61
  const url = new URL(super.href);
46
- if (this._nextConfig.i18n) url.pathname=addBasePath(withLocale(this.pathname,this._locale,this.defaultLocale),this._basePath);
47
- if (this._trailingSlash) { if (!url.pathname.endsWith('/')) url.pathname += '/'; }
62
+ let pathname = withLocale(this.pathname, this._locale, this._buildId || this._forceLocale ? undefined : this.defaultLocale);
63
+ if (this._buildId) {
64
+ pathname = pathname.replace(/\/$/, '');
65
+ pathname = '/_next/data/' + this._buildId + pathname + (this.pathname === '/' ? '/index.json' : '.json');
66
+ }
67
+ url.pathname = addBasePath(pathname, this._basePath);
68
+ if (this._trailingSlash && !this._buildId) { if (!url.pathname.endsWith('/')) url.pathname += '/'; }
48
69
  else if (url.pathname !== '/') url.pathname = url.pathname.replace(/\/$/, '');
49
70
  return url.href;
50
71
  }
@@ -54,19 +75,18 @@ class NextURL extends URL {
54
75
  super.href = value;
55
76
  this._trailingSlash = super.pathname === '/' ? !!this._nextConfig.trailingSlash : super.pathname.endsWith('/');
56
77
  this._basePath = hasBasePath(super.pathname, this._nextConfig.basePath) ? this._nextConfig.basePath || '' : '';
57
- this._locale = this._nextConfig.i18n ? localePath(removeBasePath(super.pathname, this._basePath), {...this._nextConfig.i18n,defaultLocale:this.defaultLocale}).locale : '';
78
+ const logical = this._parseData ? nextDataPath(removeBasePath(super.pathname, this._basePath)) : { pathname: removeBasePath(super.pathname, this._basePath) };
79
+ this._buildId = logical.buildId;
80
+ this._locale = this._nextConfig.i18n ? localePath(logical.pathname, {...this._nextConfig.i18n,defaultLocale:this.defaultLocale}).locale : '';
58
81
  }
59
- clone() { return new NextURL(this.href, { nextConfig: this._nextConfig }); }
82
+ clone() { return new NextURL(this.href, { nextConfig: this._nextConfig, forceLocale: this._forceLocale }); }
60
83
  }
61
84
 
62
85
  class NextRequest extends Request {
63
86
  constructor(input, init) {
64
87
  super(input, init);
65
88
  this.nextUrl = new NextURL(this.url, { nextConfig: init?.nextConfig || input?.nextUrl?._nextConfig });
66
- this.cookies = new CookieStore(this.headers.get('cookie') || '', {
67
- mutable: true,
68
- onChange: () => this.headers.set('cookie', this.cookies.toString()),
69
- });
89
+ this.cookies = new RequestCookieStore(this.headers);
70
90
  }
71
91
  }
72
92
 
@@ -1,7 +1,20 @@
1
1
  import type { CookieStore } from './headers.cjs';
2
2
  export interface NextURLConfig { i18n?: import('./index.js').NextConfig['i18n']; basePath?: string; trailingSlash?: boolean; skipMiddlewareUrlNormalize?: boolean; skipProxyUrlNormalize?: boolean }
3
- export class NextURL extends URL { constructor(input: string | URL, options?: { base?: string | URL; nextConfig?: NextURLConfig }); constructor(input: string | URL, base: string | URL, options?: { nextConfig?: NextURLConfig }); clone(): NextURL; locale: string; readonly defaultLocale?: string; readonly domainLocale?: NonNullable<NonNullable<import('./index.js').NextConfig['i18n']>['domains']>[number]; basePath: string; trailingSlash: boolean }
4
- export class NextRequest extends Request { constructor(input: RequestInfo | URL, init?: RequestInit & { nextConfig?: NextURLConfig }); nextUrl: NextURL; cookies: CookieStore }
3
+ export class NextURL extends URL { constructor(input: string | URL, options?: { base?: string | URL; nextConfig?: NextURLConfig; forceLocale?: boolean }); constructor(input: string | URL, base: string | URL, options?: { nextConfig?: NextURLConfig; forceLocale?: boolean }); clone(): NextURL; buildId: string | undefined; locale: string; readonly defaultLocale?: string; readonly domainLocale?: NonNullable<NonNullable<import('./index.js').NextConfig['i18n']>['domains']>[number]; basePath: string; trailingSlash: boolean }
4
+ export interface RequestCookie { name: string; value: string }
5
+ export interface RequestCookieStore extends Iterable<[string, RequestCookie]> {
6
+ readonly size: number;
7
+ get(name: string | {name: string}): RequestCookie | undefined;
8
+ getAll(name?: string | {name: string}): RequestCookie[];
9
+ has(name: string): boolean;
10
+ set(name: string, value: string): this;
11
+ set(cookie: RequestCookie): this;
12
+ delete(name: string): boolean;
13
+ delete(names: string[]): boolean[];
14
+ clear(): this;
15
+ toString(): string;
16
+ }
17
+ export class NextRequest extends Request { constructor(input: RequestInfo | URL, init?: RequestInit & { nextConfig?: NextURLConfig }); nextUrl: NextURL; cookies: RequestCookieStore }
5
18
  export interface MiddlewareResponseInit extends ResponseInit { request?: { headers?: Headers } }
6
19
  export class NextResponse<Body = unknown> extends Response {
7
20
  cookies: CookieStore;
@@ -27,6 +27,13 @@ function dynamicUsage(reason, request = context()) {
27
27
  function staticBailout(reason, request = context()) {
28
28
  if (diagnostics.enabled) diagnostics.record('dynamic', { route: diagnostics.route(request), reason });
29
29
  const state = request?.staticState;
30
+ if (!state && request?.navigationStage && ['an uncached fetch', 'connection()', 'unstable_noStore()'].includes(reason)
31
+ && !require('./data-cache.cjs').inCacheScope()) require('./navigation-stages.cjs').postponeStage(reason);
32
+ const requirement = request?.cacheConfig?.ensureStatic;
33
+ if (!state && (request?.navigationStage === 'shell' && ['shell', 'prefetch', 'navigation'].includes(requirement)
34
+ || request?.navigationStage === 'prefetch' && ['prefetch', 'navigation'].includes(requirement))) {
35
+ require('./navigation-stages.cjs').postponeStage(reason);
36
+ }
30
37
  if (!state) return true;
31
38
  if (state.partial && state.mode === 'auto') {
32
39
  state.dynamicReasons.add(reason);
@@ -35,6 +35,9 @@ function recordCacheDependency({ tags = [], revalidate } = {}) {
35
35
  }
36
36
  function metadata(scope, context) { return { ...scope.life, revalidate: Math.min(scope.life.revalidate, scope.life.expire), tags: [...scope.tags], paths: getCachePaths(context), ...(scope.externalCache ? { externalCache: true } : {}) }; }
37
37
  function propagate(info, parent, context) {
38
+ if (context?.staticState && context.cacheConfig?.ensureStatic === 'navigation' && (info.expire < 300 || info.stale < 30 || info.revalidate === 0)) {
39
+ dynamicUsage('a cache lifetime with expire below 300 seconds, stale below 30 seconds, or revalidate=0', context);
40
+ }
38
41
  trackStaticDependency(info, context);
39
42
  if (!parent || parent === true) return;
40
43
  if (info.externalCache) parent.externalCache = true;