@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
@@ -18,12 +18,181 @@ transpilation, image/font tooling and caching deserve targeted build and browser
18
18
  tests. Support for one configuration shape does not imply support for all
19
19
  upstream plugin behavior.
20
20
 
21
+ ## Instrumentation and tracing
22
+
23
+ Use a single `instrumentation.ts`, `.js`, `.mjs` or `.cjs` at the project root
24
+ or in `src`. Export an optional async `register()` for runtime initialization
25
+ and an optional async `onRequestError(error, request, context)` for reporting.
26
+ The `Instrumentation.onRequestError` type is exported from PRNext's root API.
27
+ `process.env.NEXT_RUNTIME` distinguishes `nodejs` and `edge`; conditional imports
28
+ keep Node SDKs out of an Edge bundle. Registration runs once per server realm,
29
+ including the separate RSC realm, before application requests. A production
30
+ prerender does not run the runtime startup hook. Request-time ISR and PPR
31
+ regeneration initialize their server realm and report failures through the same
32
+ hooks. Repeated propagation of an RSC error to the HTML worker does not report
33
+ the same error again.
34
+
35
+ ```ts
36
+ import type { Instrumentation } from '@thomas.f/prnext'
37
+
38
+ export async function register() {
39
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
40
+ await import('./observability.node')
41
+ }
42
+ }
43
+
44
+ export const onRequestError: Instrumentation.onRequestError = async (
45
+ error, request, context
46
+ ) => {
47
+ await reportError(error, request, context)
48
+ }
49
+ ```
50
+
51
+ `reportError` above is an application function, not a PRNext API. Initialization
52
+ has a 30-second budget. Each error hook has a five-second budget; reporting
53
+ failure does not replace the application failure. Redirect/not-found control
54
+ signals are excluded. Hooks receive request information: the application owns
55
+ redaction before sending that information to a third party.
56
+
57
+ For built-in correlated traces, explicitly set
58
+ `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` to an OTLP HTTP collector's complete traces
59
+ URL and `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/json`. Alternatively,
60
+ `OTEL_EXPORTER_OTLP_ENDPOINT` supplies a base URL with `/v1/traces` appended.
61
+ `OTEL_SERVICE_NAME`, `OTEL_EXPORTER_OTLP_TRACES_HEADERS` (or general
62
+ `OTEL_EXPORTER_OTLP_HEADERS`) and `OTEL_SDK_DISABLED=true` are supported. Keep
63
+ exporter credentials in the server environment. This built-in path supports
64
+ HTTP JSON, not gRPC or HTTP protobuf; use your own SDK for other protocols.
65
+
66
+ Native HTTP spans cover static and dynamic responses through body completion
67
+ or cancellation. Node, RSC and fetch spans share W3C trace context. Export queues
68
+ are bounded and telemetry is best effort; an unavailable collector does not
69
+ block a site response. Built-in span attributes exclude bodies, cookie values,
70
+ query strings and authorization headers. An application-installed
71
+ `@opentelemetry/api` can use the same parent context when its SDK installs a
72
+ context manager in `register`. No SDK is installed automatically, and this does
73
+ not certify every vendor integration.
74
+
75
+ ## Production bytes and navigation
76
+
77
+ Production CSS Modules use deterministic compact class and animation names.
78
+ Global classes and declared composition exports retain their meaning; development
79
+ keeps readable names. Internal JavaScript exports are shortened by the selected
80
+ compiler, while public exports and dynamic imports keep their interfaces. Asset
81
+ hashes and source maps remain compiler-owned.
82
+
83
+ App navigation can negotiate binary differential Flight responses. Every request
84
+ still runs fresh rendering and access checks. The server retains at most 64
85
+ completed bases, 32 MiB in total, with a two-MiB per-base limit and 30-second
86
+ expiry. Bases are keyed by deployment and credentials; an unknown, expired or
87
+ incompatible base produces ordinary Flight. The browser retains one completed
88
+ base, streams reconstructed bytes to React and can retry one failed differential
89
+ navigation as a full GET. Server Actions are never replayed through this path.
90
+ Per worker, at most eight captures and two encoders run together. Additional
91
+ requests use ordinary Flight, keeping transient transport work bounded too.
92
+
21
93
  Configuration uses one `next.config.*` or `prnext.config.*` file and rejects
22
- unknown keys rather than ignoring them. `reactCompiler: false` is accepted;
23
- enabling React Compiler is explicitly unsupported. `typedRoutes: true` is
94
+ unknown keys rather than ignoring them. React Compiler is available through an
95
+ explicit `reactCompiler` option described below. `typedRoutes: true` is
24
96
  implemented through PRNext's generated route registry. Do not copy a full
25
97
  Next.js configuration template into a new application.
26
98
 
99
+ The repository's reference tests build and run matching applications against
100
+ Next.js 16.4.3, including mounted App navigation, form Actions and cookies,
101
+ metadata, Pages locale routing, redirects, APIs and selected webpack loader
102
+ contracts. These executable examples cover specific behavior; they do not
103
+ certify every Next.js application or plugin. `NextRequest.nextUrl` understands
104
+ data-route build IDs and locales, and request-cookie mutations synchronize the
105
+ request header. Request cookies have different mutation results from response
106
+ cookies; do not use them interchangeably.
107
+
108
+ A locked real-application fixture additionally exercises Better Auth 1.7.14
109
+ (email/password, session cookies and logout), Drizzle ORM 0.45.4 over SQLite
110
+ through its proxy driver, next-intl 4.14.9 (server and client translations),
111
+ and the MDX 3.1.1 webpack loader against Next.js 16.4.3. It covers authenticated
112
+ Server Actions, user-owned rows, cookie/locale isolation, client navigation and
113
+ Server Component `after()` callbacks. This is evidence for those workflows,
114
+ not a guarantee for every plugin, provider or deployment configuration.
115
+
116
+ PRNext-specific options such as `prerender` and `htmlOnly` are described in
117
+ [Build and deployment](deployment.md). Do not pass them to an upstream Next.js
118
+ build when comparing the two implementations.
119
+
120
+ ## TypeScript 7 and earlier compilers
121
+
122
+ PRNext uses the application's installed TypeScript. TypeScript 7 runs through its
123
+ official native `tsc` command for production checks, `prnext typecheck` and live
124
+ development diagnostics. Install TypeScript normally, including its platform
125
+ optional dependencies. PRNext does not download a compiler during a build.
126
+ TypeScript 5 and 6 retain their compiler API integration.
127
+
128
+ Checks use `--noEmit` and keep incremental compiler state in an owned temporary
129
+ directory. Development reports updated errors and clears fixed diagnostics;
130
+ new source files and configuration changes restart the native watch generation.
131
+ `typedRoutes` also supports native TypeScript and preserves JSONC comments when
132
+ adding its generated declaration to the config. Compiler support does not imply
133
+ that every application is ready for TypeScript 7's own language/config changes.
134
+
135
+ ## React Compiler
136
+
137
+ Enable the bundled official React Compiler in the project's existing config:
138
+
139
+ ```js
140
+ export default {
141
+ reactCompiler: true,
142
+ };
143
+ ```
144
+
145
+ No separate compiler installation is required. The default is disabled;
146
+ `reactCompiler: false` preserves the uncompiled component path. Enabled builds
147
+ optimize project-owned browser components in both Pages and App applications,
148
+ in development and production. App Client Components also use the transform in
149
+ their isolated server-rendering graph. The Pages server graph stays uncompiled,
150
+ including APIs, custom Document code and data-loader utilities, so ordinary
151
+ server functions never acquire client-only memoization hooks. React Server
152
+ Components, Server Action modules, PRNext internals, generated entrypoints and
153
+ npm packages are not compiled. Keep the matching React 19 peer versions required
154
+ by the installed framework.
155
+
156
+ Ordinary `.mts`/`.cts` hook helpers can be optimized; server/client directives
157
+ in those extensions remain unsupported. Decorated `.mts`/`.cts` utility modules
158
+ keep their native compilation path while importing React components can still
159
+ be optimized.
160
+
161
+ For gradual adoption, use `reactCompiler: { compilationMode: 'annotation' }`, then
162
+ put `"use memo";` at the beginning of each component or hook to optimize. In
163
+ either mode, `"use no memo";` excludes an individual function. Supported object
164
+ options are `compilationMode: 'infer' | 'annotation'`,
165
+ `panicThreshold: 'none' | 'critical_errors' | 'all_errors'` and `target: '19'`.
166
+ Defaults are `'infer'`, `'none'` and `'19'`; other fields are rejected.
167
+
168
+ After a successful build, inspect the compiler's actual results:
169
+
170
+ ```sh
171
+ prnext compiler
172
+ prnext compiler --json
173
+ ```
174
+
175
+ The report names observed functions, their project-relative files, optimization
176
+ or skip status, and the compiler's reasons. These are transform decisions;
177
+ server-code removal and tree shaking can later omit a function from the browser
178
+ bundle. Cached transforms retain their
179
+ diagnostics and are identified as reused. Browser and server graphs do not
180
+ inflate the function count. An incremental build can reuse graph inputs without
181
+ observing every transform; the report states that scope rather than claiming
182
+ complete application coverage. Disabled compilation is reported explicitly.
183
+ The report describes the last successful build, stays outside public assets and
184
+ does not include source code. A failed build preserves the previous report.
185
+
186
+ The compiler can skip code that cannot be safely optimized. Follow the Rules of
187
+ React and check actual behavior rather than assuming every component was
188
+ compiled. It reduces repeated component work; it does not guarantee faster
189
+ server responses, smaller JavaScript or a specific speed multiplier. Validate
190
+ hydration, interactions and Fast Refresh in the application, then compare a
191
+ production build with the option enabled and disabled. Compilation adds build
192
+ work; PRNext caches pure transforms with their compiler version and settings.
193
+
194
+ See the [official React Compiler documentation](https://react.dev/learn/react-compiler).
195
+
27
196
  Only explicit private Next.js import adapters are supported. Do not add arbitrary
28
197
  `next/dist/...` imports or install another Next runtime to satisfy one: isolate
29
198
  or replace the incompatible dependency, retain the original framework where
@@ -67,6 +236,29 @@ by silently adding `--force` or `--legacy-peer-deps`.
67
236
  can run project code, and installed custom loaders still require a real build.
68
237
  The audit is not a sandbox for untrusted projects.
69
238
 
239
+ The version 2 JSON report follows static imports from project sources into the
240
+ installed packages they reference. It checks explicit PRNext adapters and their
241
+ statically declared named exports, and includes the importing file, line and
242
+ import chain when a dependency uses an unavailable API. It resolves selected
243
+ Node, browser and React Server Component package conditions, including workspace
244
+ symlinks, without importing those package modules. Unreferenced packages are not
245
+ enumerated. Runtime branches, custom loader output and every possible bundler
246
+ condition remain outside this check.
247
+
248
+ A module-level `use server` directive is a server boundary even when a Client
249
+ Component reaches it through a re-export. Its dependencies are still audited in
250
+ the server context. A client dependency on a Node builtin needs an actual browser
251
+ implementation; package `browser` mappings and installed shims are resolved
252
+ before diagnosing it. Custom configuration aliases still require a build with
253
+ that application's configuration.
254
+
255
+ `evidence.state: "checked"` means the selected static graph was inspected, not
256
+ that migration is certified. `"unknown"` identifies unresolved/computed imports,
257
+ loader inputs or incomplete coverage. The graph is bounded to 2,000 modules,
258
+ 12,000 imports, 32 MiB of source reads and 32 import hops. Hitting a limit leaves
259
+ the remaining coverage unknown. `ok` means no blocking diagnostic was found;
260
+ inspect warnings and coverage even when it is true.
261
+
70
262
  ## Compare running applications
71
263
 
72
264
  Start the original application and the candidate on separate available ports.
@@ -89,6 +281,60 @@ in a real browser, test authorized and unauthorized states, and run the existing
89
281
  application tests. Keep the original setup available until the candidate is
90
282
  actually verified. Timings from this comparison are not controlled benchmarks.
91
283
 
284
+ ## Run application browser scenarios
285
+
286
+ An explicit browser option can run the application's own assertions against
287
+ each server. Install Playwright and its Chromium browser in the application
288
+ using its normal development setup; PRNext never installs them for this check.
289
+ Provide a trusted `.mjs`, `.cjs` or `.js` module inside the application directory:
290
+
291
+ ```js
292
+ // migration.scenarios.mjs
293
+ export default [
294
+ {
295
+ async run({page, baseURL}) {
296
+ await page.goto(`${baseURL}/sign-in`);
297
+ await page.getByLabel('Email').fill(process.env.MIGRATION_TEST_EMAIL);
298
+ await page.getByLabel('Password').fill(process.env.MIGRATION_TEST_PASSWORD);
299
+ await page.getByRole('button', {name: 'Sign in', exact: true}).click();
300
+ await page.getByRole('heading', {name: 'Dashboard', exact: true}).waitFor();
301
+ await page.getByRole('button', {name: 'Save note', exact: true}).click();
302
+ await page.getByText('Note saved', {exact: true}).waitFor();
303
+ await page.getByRole('button', {name: 'Sign out', exact: true}).click();
304
+ await page.getByRole('heading', {name: 'Sign in', exact: true}).waitFor();
305
+ },
306
+ },
307
+ ];
308
+ ```
309
+
310
+ Replace the URLs and accessible names with the application's real controls.
311
+ Supply dedicated test account values through the command's environment rather than
312
+ command arguments or source files. Use isolated test databases: these explicit
313
+ callbacks execute application code and can submit forms or mutate data.
314
+
315
+ ```sh
316
+ prnext migrate --check --against http://localhost:3000 --candidate http://localhost:3001 --routes paths.json --browser-scenarios migration.scenarios.mjs --timeout 15000 --json
317
+ ```
318
+
319
+ Only this option loads the scenario module and starts a browser. It requires
320
+ the application's installed `playwright` or `@playwright/test`. Each callback
321
+ receives a new isolated browser context for each target. Assert observable
322
+ behavior inside the callback; its return value is ignored. A callback that
323
+ fulfills passes for that target, and a thrown assertion fails. Both targets
324
+ must pass independently; equal durations or response samples are not used to
325
+ infer browser equivalence.
326
+
327
+ The report records scenario numbers, targets and pass/fail results. It excludes
328
+ application console output, raw error messages, request data, cookies and
329
+ screenshots. Read or debug the original application test separately when a
330
+ generic failure needs investigation. Keep credentials out of assertion labels
331
+ and application-managed artifacts too. An unavailable browser or incomplete run
332
+ is reported as unknown. There are at most 20 scenarios, a 100–60,000 ms budget
333
+ per callback, a bounded startup period and a five-minute maximum for the worker.
334
+ A timeout closes its context and ends the run before another target starts.
335
+ The worker also closes its owned browser when it finishes. This is explicit
336
+ execution of trusted test code, not a sandbox for an untrusted module.
337
+
92
338
  ## Boundaries that often matter
93
339
 
94
340
  React server/client directives in `.mts`/`.cts` are rejected; use `.ts`/`.tsx`.
package/docs/ai/data.md CHANGED
@@ -114,13 +114,173 @@ enable them to silence a request-context error or assume their complete upstream
114
114
  behavior. Keep a working `unstable_cache` or fetch solution unless the project
115
115
  needs a different model.
116
116
 
117
+ ## Navigation stages
118
+
119
+ Enable `cacheComponents: true` and `partialPrefetching: true` to separate runtime
120
+ App Shell work from per-link prefetch work and actual navigation work:
121
+
122
+ ```jsx
123
+ import { Suspense } from 'react';
124
+ import { navigation, prefetch } from 'next/cache';
125
+
126
+ async function Recommendations() {
127
+ await prefetch();
128
+ return <div>{await getCachedRecommendations()}</div>;
129
+ }
130
+
131
+ async function ExpensiveReport() {
132
+ await navigation();
133
+ return <div>{await getCachedReport()}</div>;
134
+ }
135
+
136
+ export default function Page() {
137
+ return <>
138
+ <Suspense fallback={<p>Loading recommendations</p>}><Recommendations /></Suspense>
139
+ <Suspense fallback={<p>Loading report</p>}><ExpensiveReport /></Suspense>
140
+ </>;
141
+ }
142
+ ```
143
+
144
+ Define and authorize the application-specific data functions above. Call gates
145
+ before invoking a cached function, never inside its cached scope. Both gates
146
+ require Cache Components. Static prerenders continue through them, so static
147
+ output can already contain the result. Without partial prefetching, `prefetch()`
148
+ has no effect and an explicit `Link prefetch={true}` requests the full route.
149
+
150
+ A default Link requests the shell. `prefetch={true}` permits per-link work;
151
+ content below `navigation()` waits for the navigation. Runtime previews are
152
+ private, size-bounded and tied to the request's authorization identity. They do
153
+ not authorize a subsequent navigation.
154
+ Uncached runtime fetches and `connection()` remain deferred during speculative
155
+ rendering. Put reusable I/O in a cached function to make it eligible for an
156
+ earlier stage; a gate does not itself cache data.
157
+
158
+ Pages and layouts may export `ensureStatic = 'shell'`, `'prefetch'` or
159
+ `'navigation'`. Earlier stages are included in stricter levels. A descendant
160
+ cannot weaken a parent; `false` conflicts with a static requirement elsewhere
161
+ in the route. `'auto'` adds no requirement. Client Component exports are rejected.
162
+ `'shell'` and `'prefetch'` request static output for those stages without adding
163
+ a separate full-route build check. `'navigation'` validates complete static
164
+ server output during build and development, including request APIs beneath
165
+ Suspense, metadata and complete dynamic parameter sets. It requires nonempty,
166
+ complete `generateStaticParams()` results for dynamic routes. All levels require
167
+ Cache Components; shell/prefetch levels need partial prefetching to select stages.
168
+
169
+ ## Shared Redis Cache Components
170
+
171
+ The optional first-party Redis handler shares `'use cache'` results across Node
172
+ processes. Install `@redis/client@^6.3.0` only in applications using it; a normal PRNext
173
+ installation and generated project do not require Redis.
174
+
175
+ ### `redis-handler.mjs`
176
+
177
+ ```js
178
+ import { createRedisCache } from '@thomas.f/prnext/cache/redis';
179
+
180
+ export default createRedisCache({
181
+ namespace: 'my-app-production-v1',
182
+ urlEnv: 'REDIS_URL',
183
+ });
184
+ ```
185
+
186
+ ### `prnext.config.mjs`
187
+
188
+ ```js
189
+ export default {
190
+ cacheComponents: true,
191
+ cacheHandlers: { remote: './redis-handler.mjs' },
192
+ };
193
+ ```
194
+
195
+ ### `app/catalog/read.ts`
196
+
197
+ ```ts
198
+ import { cacheLife, cacheTag } from 'next/cache';
199
+
200
+ export async function readCatalog() {
201
+ 'use cache: remote';
202
+ cacheLife('hours');
203
+ cacheTag('catalog');
204
+ return [{ id: 'demo', name: 'Demo product' }];
205
+ }
206
+ ```
207
+
208
+ Set the Redis URL in the server's runtime environment. It is not embedded in the
209
+ handler bundle. Replicas sharing results need the same namespace and compatible
210
+ build output. Use separate namespaces for unrelated apps, environments and
211
+ incompatible deployments. This factory handles Cache Components. The separate
212
+ incremental factory below handles native HTML/ISR, fetch and `unstable_cache`.
213
+
214
+ Cache fills use a bounded cross-process lease. Atomic tag generations prevent a
215
+ fill invalidated while running, or an expired lease owner, from publishing old
216
+ data. Soft tag invalidation retains stale-while-revalidate behavior; immediate
217
+ expiration prevents another replica from reading the old entry. Losing the tag
218
+ registry invalidates existing entries rather than resurrecting them.
219
+
220
+ Values are limited to 2 MiB, metadata to 64 KiB and each entry to 256 tags.
221
+ The tag registry is bounded; excess cardinality conservatively invalidates the
222
+ namespace. Default retention is at most seven days. Defaults use a 30-second
223
+ lease, a 2.5-second fill wait and one-second command timeout. Reads can fall back
224
+ to fresh computation on cache failure; invalidation failures remain errors.
225
+ Cached callbacks must be safe to execute more than once after timeout or outage:
226
+ leases reduce duplicate work, not provide exactly-once business operations.
227
+
228
+ An already connected node-redis client can be supplied as `client`; its lifecycle
229
+ remains the application's responsibility. Otherwise the handler connects lazily
230
+ and owns its client. Use Redis appropriate for the application's availability,
231
+ access controls and data sensitivity; a shared cache is not an authorization
232
+ boundary or a durable database.
233
+
234
+ ## Shared Redis pages and request data
235
+
236
+ Configure `cacheHandler` with the constructor returned by the incremental factory:
237
+
238
+ ```js
239
+ // incremental-redis.mjs
240
+ import { createRedisIncrementalCache } from '@thomas.f/prnext/cache/redis';
241
+ export default createRedisIncrementalCache({ namespace: 'my-app-production' });
242
+ ```
243
+
244
+ ```js
245
+ // prnext.config.mjs
246
+ export default {
247
+ cacheHandler: './incremental-redis.mjs',
248
+ cacheMaxMemorySize: 0,
249
+ };
250
+ ```
251
+
252
+ Set `REDIS_URL` at runtime and install the optional `@redis/client@^6.3.0` client.
253
+ The same configuration works for App and Pages HTML/ISR, static Route Handlers,
254
+ cacheable `fetch` and `unstable_cache`. It does not cache private dynamic pages.
255
+ Replicas must use compatible builds and share an application-specific namespace.
256
+ Cache Components still select their own `cacheHandlers`; both factories may use
257
+ the same namespace to share tag invalidations.
258
+
259
+ The runtime acquires a shared generation lease before rendering, checks for a
260
+ winner's result after waiting and carries that lease through publication. An
261
+ invalidation or expired lease rejects late writes atomically. Runtime reads and
262
+ publication remain bounded; unavailable Redis can prevent a page regeneration
263
+ from succeeding. A shared cache is not a durable database or an exactly-once
264
+ execution mechanism. Keep producers safe to retry.
265
+
266
+ Incremental entries preserve Buffer and Map values, have a 16 MiB serialized
267
+ limit and use V8-major-specific keys. The default generation lease is 60 seconds;
268
+ `leaseMs` can be configured up to 120 seconds. Configure it for the expected
269
+ generation duration. The default maximum storage age remains seven days.
270
+ New cacheHandler builds include a native generation-lease capability check;
271
+ use the matching PRNext native binary and rebuild existing output.
272
+
117
273
  ## Background work and durable data
118
274
 
119
- `after()` from `next/server` is supported in Node Route Handlers and Server
120
- Actions. It runs tracked callbacks after the response completes or disconnects;
121
- it is not supported for Server Components or Edge lifecycles. Cookie reads are
122
- allowed in that retained context, but cookie writes and path/tag invalidation
123
- are rejected there. Invalidate before returning from the mutation.
275
+ `after()` from `next/server` is supported in Node Server Components (including
276
+ layouts and metadata), Route Handlers and Server Actions. It runs tracked
277
+ callbacks after the response completes or disconnects; static renders execute
278
+ them during the build. Edge lifecycles remain unsupported. In a Server Component,
279
+ read request data before registering the callback and capture the needed values:
280
+ `headers()`, `cookies()` and `draftMode()` inside that callback are rejected.
281
+ Route Handler and Action callbacks can read their retained request context.
282
+ Cookie writes and path/tag invalidation are rejected after the response;
283
+ invalidate before returning from the mutation.
124
284
 
125
285
  Tracked callbacks are bounded by the route's `maxDuration` and shutdown
126
286
  deadlines; forced termination can interrupt them. Use an external durable queue
@@ -20,6 +20,22 @@ run `next start` on a PRNext build or hand-edit its manifests to bypass runtime
20
20
  compatibility checks. PRNext's native server handles HTTP while React rendering
21
21
  uses Node workers. Published native components must match the framework version.
22
22
 
23
+ Each production artifact also has its own deployment identity, even when
24
+ `generateBuildId` returns a constant. App navigation, Pages data and JavaScript
25
+ Server Actions check this identity before decoding a response. A stale GET can
26
+ fall back to a document navigation. A stale action fails without executing or
27
+ automatically replaying the mutation; preserve the user's work and ask them to
28
+ reload before retrying it explicitly.
29
+
30
+ When rebuilding into the same output directory, PRNext can carry forward public
31
+ hashed assets from two previous artifacts for up to one hour, within a 64 MiB
32
+ budget. This does not retain old server code, HTML, data responses or sessions.
33
+ It does not coordinate independent servers or a CDN: deploy matching artifacts
34
+ and configure the proxy to preserve the deployment headers and cache variation.
35
+ Use `deploymentRetention: false` to disable carry-over, or configure
36
+ `deploymentRetention: { maxAge: 3600, maxGenerations: 2, maxBytes: 67108864 }`;
37
+ `maxAge` is in seconds. A new output directory has no previous assets to retain.
38
+
23
39
  For an existing reverse proxy, bind the application to loopback and configure
24
40
  the proxy according to the deployment environment. Set required production
25
41
  environment variables and exercise a real health route before switching
@@ -35,6 +51,141 @@ relative files. Build for the target OS/architecture and retain compatible Node;
35
51
  a standalone bundle is not a universal executable. Read the shipped
36
52
  [build output contract](../../build/ADAPTERS.md) for artifact boundaries.
37
53
 
54
+ ## HTML rendering paths
55
+
56
+ Buffered App HTML decodes UTF-8 chunks incrementally, avoiding a second aggregate
57
+ binary copy. The existing 16 MiB response limit still applies, including to large
58
+ chunks and multi-byte text.
59
+
60
+ The default remains React's Web stream renderer. To evaluate the experimental
61
+ Node pipeable renderer, set `PRNEXT_SSR_STREAM=node` in the server environment.
62
+ It uses bounded backpressure and cancellation for ordinary Node App rendering.
63
+ Partial/PPR continuations keep the Web API and their original resume state;
64
+ Edge rendering is unchanged. Unset the variable to restore the default.
65
+ This is a workload comparison option, not a promised performance multiplier:
66
+ small-page CPU and larger-page timings can move in different directions.
67
+
68
+ ## Readiness and first-request latency
69
+
70
+ `prnext start --prewarm` initializes the framework renderers before accepting
71
+ HTTP requests. It warms one render host and its RSC isolate; additional hosts
72
+ remain lazy. It does not call application pages, API handlers or data loaders.
73
+ Normal instrumentation initialization still runs. Existing `workerPreload`
74
+ configuration, if present, separately imports the application modules selected
75
+ by that configuration. Prewarming failure prevents HTTP readiness.
76
+
77
+ For a supervisor, add `--readiness-file /private/run/instance-ready.json`.
78
+ Create its parent directory first, and use a separate path per application or
79
+ independent instance. The atomically replaced JSON contains `ready`, `pid`,
80
+ `address`, `generation`, and `render: 'prewarmed' | 'lazy'`. HTTP readiness alone
81
+ does not mean a lazy React renderer is loaded. Replacement and shutdown
82
+ coordinate through a private sidecar lock; normal shutdown removes only its
83
+ own generation. A forced termination can leave a stale marker, so the supervisor
84
+ must also verify the process identity and a real application health check.
85
+
86
+ Idle retirement saves memory at the cost of the next render's wake latency.
87
+ For an application that must keep its used hosts alive, explicitly pass
88
+ `--worker-idle-seconds 0`. Prewarming is a startup operation, not periodic
89
+ synthetic traffic, and does not silently disable retirement. Measure the first
90
+ dynamic page as well as startup time; prewarming moves work earlier rather
91
+ than eliminating all cold work.
92
+
93
+ ## Profiles under resource limits
94
+
95
+ Balanced remains the default. On Linux, effective policies consider CPU quotas
96
+ and memory ceilings at visible cgroup-v2 ancestors, as well as available CPUs.
97
+ Speed uses a smaller nursery and admission/buffer budgets when limited to one
98
+ CPU or at most 1 GiB memory. Balanced also lowers its budgets below 512 MiB.
99
+ The native launcher passes its effective policy to the RSC isolate so both use
100
+ the same resource decision. Explicit `NODE_OPTIONS` heap flags keep their precedence.
101
+
102
+ Memory favors a smaller nursery and earlier idle retirement, without forcing
103
+ V8's optimize-for-size policy. It can still trade CPU and latency for RAM.
104
+ Classic preserves the historical policy. These settings are advisory budgets,
105
+ not total RSS caps; use an OS/container limit for hard isolation. Explicit
106
+ worker counts are not automatically increased to match the host CPU count.
107
+ Compare throughput, CPU per correct response, p95 and memory together before
108
+ choosing Speed or adding workers.
109
+
110
+ ## Production worker memory and recovery
111
+
112
+ A production Node host can be drained when its RSS stays above an explicit soft
113
+ budget for three one-second samples:
114
+
115
+ ```js
116
+ export default { workerMemory: { maxRssMb: 512 } };
117
+ ```
118
+
119
+ This is opt-in (64–65536 MiB), separate from the build's prerender budget. It
120
+ observes process RSS, including rendering threads; it does not cap V8's heap or
121
+ force garbage collection. Existing responses and tracked work finish on the old
122
+ host while new work uses a replacement. At most two process generations per
123
+ host group can overlap, including child reaping; further replacement waits for
124
+ capacity. This is not an OS memory sandbox: a busy draining host can exceed the
125
+ soft target. Serial maintenance workers check their budget after requests.
126
+ Development keeps its existing worker behavior.
127
+
128
+ Repeated failed Node starts and unexpected host exits use a shared startup
129
+ cooldown, from 100 ms up to 30 seconds. A sustained healthy period of 60 seconds
130
+ resets the circuit. Queued callers keep their original deadlines; no failed
131
+ request, POST or Server Action is automatically replayed. Ordinary client
132
+ cancellations and planned memory/idle retirements do not count as crashes.
133
+
134
+ The private local data cache uses a versioned binary value transport when
135
+ negotiated by the native worker launcher. Existing JSON clients remain accepted.
136
+ Authentication, limits, leases, tag/path invalidation and cache policy are
137
+ unchanged. This internal transport is not a public application API.
138
+
139
+ ## Production prerendering
140
+
141
+ App and Pages static paths can render in separate Node processes during a
142
+ production build. Path discovery runs once; results are published in a stable
143
+ order. Small batches and `workers: 1` use the local renderer. Defaults account
144
+ for available CPUs and memory. To set explicit ceilings:
145
+
146
+ ```js
147
+ export default {
148
+ prerender: { workers: 4, maxMemoryMb: 2048 },
149
+ };
150
+ ```
151
+
152
+ `workers` accepts 1–8; `maxMemoryMb` accepts 256–16384 MiB and covers the sum of
153
+ observed child-process RSS, including their rendering threads. This is a sampled
154
+ admission budget, not an OS memory limit; it does not include the build
155
+ coordinator. A path error or exhausted budget fails the build, stops pending
156
+ work and closes the workers. Paths are not automatically retried: keep static
157
+ data loaders free of mutations and independent of execution order.
158
+
159
+ Build workers share a temporary, bounded cache for eligible App reads which the
160
+ application explicitly elects to cache. It is discarded after the build and
161
+ never uses the production cache. `no-store` requests and `Set-Cookie` responses
162
+ are not coalesced. Authentication does not opt a read into caching; cached reads
163
+ retain request-derived keys. Pages loaders retain their existing cache behavior.
164
+
165
+ ## Selected pages without browser JavaScript
166
+
167
+ Use the PRNext-specific `htmlOnly` option for fully static, noninteractive App
168
+ pages. Select registered route patterns exactly, or use a final `/**` subtree:
169
+
170
+ ```js
171
+ export default {
172
+ htmlOnly: ['/about', '/articles/[slug]', '/manual/**'],
173
+ };
174
+ ```
175
+
176
+ Build validation requires Node App routes with complete static output. A
177
+ dynamic pattern needs `dynamicParams = false` and a nonempty
178
+ `generateStaticParams` result. Client Components, Server Actions, interactive
179
+ `next/link`, scripts, ISR, PPR, `cacheComponents`, Edge and advanced parallel or
180
+ intercepting routes are rejected for selected pages. Use ordinary `<a>` links.
181
+ Unknown patterns fail the production build rather than silently doing nothing.
182
+
183
+ HTML keeps CSS, fonts, metadata and resolved Suspense content. It has no
184
+ bootstrap scripts or Flight payload; entering it performs document navigation.
185
+ Unknown generated parameters return 404. The mode is enforced in production,
186
+ including static export and standalone output; development retains its normal
187
+ JavaScript tools. It is an explicit PRNext extension, not a Next.js config key.
188
+
38
189
  ## Static browser output
39
190
 
40
191
  PRNext can serve a plain HTML/CSS/JavaScript directory:
@@ -103,6 +254,13 @@ an explicit choice, not a reason to print credentials or copy `.env` into assets
103
254
 
104
255
  ## Observe before tuning
105
256
 
257
+ Production rendering, API and regeneration admission use bounded queues grouped
258
+ by resolved route pattern. Different parameter values share one pattern's
259
+ waiting budget. Waiting patterns take turns as capacity becomes available;
260
+ a single pattern can still use all execution slots. Excess requests receive
261
+ 503 with `Retry-After`; an expensive request already executing is not preempted.
262
+ This limits backlog monopolization, not all CPU use or application-level quotas.
263
+
106
264
  Use `prnext start --inspect` and `prnext inspect --json` for sampled route/cache
107
265
  and runtime observations. `prnext analyze --json` describes built browser
108
266
  JavaScript; it is not an SSR latency measurement. A production profile can be
@@ -19,11 +19,36 @@ dependencies enabled. Consumers do not need Rust; building PRNext from its sourc
19
19
  repository is a different workflow. Do not install a platform package separately
20
20
  or combine package versions to work around an installation failure.
21
21
 
22
- ## Minimal App Router application
22
+ ## Create a new application
23
23
 
24
- For a new application, start with the manifest below, then install dependencies.
25
- For an existing application, merge only the necessary scripts and preserve its
26
- other fields. Follow [Compatibility](compatibility.md) for a Next.js migration.
24
+ ```sh
25
+ npx @thomas.f/prnext create my-app
26
+ cd my-app
27
+ npm run dev
28
+ ```
29
+
30
+ `create` writes a minimal App Router application and runs `npm install`. The
31
+ default template uses TypeScript and includes the application components, CSS,
32
+ scripts, TypeScript configuration and an `AGENTS.md` pointer to the installed
33
+ PRNext documentation. It pins PRNext to the version running the command and the
34
+ matching React peer versions.
35
+
36
+ Use an empty directory or a new path. Quote paths containing spaces. Options:
37
+
38
+ - `--js` creates JavaScript components without TypeScript configuration or type dependencies.
39
+ - `--no-install` writes the files without installing dependencies; run `npm install` in the new application before starting it.
40
+ - `--json` returns a structured creation report for automation.
41
+
42
+ `npm i @thomas.f/prnext` only installs the package in the current project. It does
43
+ not create application files, scripts or configuration. For an existing
44
+ application, preserve its package manager and follow
45
+ [Compatibility](compatibility.md) for a Next.js migration.
46
+
47
+ ## Set up an application manually
48
+
49
+ To set up a new application manually, start with the manifest below, then install
50
+ dependencies. For an existing application, merge only the necessary scripts and
51
+ preserve its other fields.
27
52
 
28
53
  ### `package.json`
29
54