@timber-js/app 0.2.0-alpha.196 → 0.2.0-alpha.198

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 (289) hide show
  1. package/dist/_chunks/{actions-CWYtq6ii.js → actions-BS-m5SLv.js} +3 -3
  2. package/dist/_chunks/{actions-CWYtq6ii.js.map → actions-BS-m5SLv.js.map} +1 -1
  3. package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
  4. package/dist/_chunks/{build-manifest-DWppEdLB.js → build-manifest-DTmSGLRz.js} +51 -2
  5. package/dist/_chunks/build-manifest-DTmSGLRz.js.map +1 -0
  6. package/dist/_chunks/{cache-api-CQeYzA5g.js → cache-api-DqzgTEqk.js} +4 -49
  7. package/dist/_chunks/cache-api-DqzgTEqk.js.map +1 -0
  8. package/dist/_chunks/{chains-h7EO-u3n.js → chains-CZG7E5zg.js} +2 -2
  9. package/dist/_chunks/{chains-h7EO-u3n.js.map → chains-CZG7E5zg.js.map} +1 -1
  10. package/dist/_chunks/{cli-check-BVthpfLS.js → cli-check-dVDi1GQz.js} +3 -3
  11. package/dist/_chunks/{cli-check-BVthpfLS.js.map → cli-check-dVDi1GQz.js.map} +1 -1
  12. package/dist/_chunks/{cli-schema-sync-3Wutm8pH.js → cli-schema-sync-DTy_-Msq.js} +2 -2
  13. package/dist/_chunks/{cli-schema-sync-3Wutm8pH.js.map → cli-schema-sync-DTy_-Msq.js.map} +1 -1
  14. package/dist/_chunks/{cloudflare-BKJC3SC_.js → cloudflare-BFb__LYG.js} +2 -2
  15. package/dist/_chunks/{cloudflare-BKJC3SC_.js.map → cloudflare-BFb__LYG.js.map} +1 -1
  16. package/dist/_chunks/{convention-lint-DO10_pVl.js → convention-lint-Ph6luW4c.js} +4 -2
  17. package/dist/_chunks/convention-lint-Ph6luW4c.js.map +1 -0
  18. package/dist/_chunks/{error-boundary-D-lkwyaD.js → error-boundary-BvRCCmbN.js} +3 -3
  19. package/dist/_chunks/{error-boundary-D-lkwyaD.js.map → error-boundary-BvRCCmbN.js.map} +1 -1
  20. package/dist/_chunks/{href-validation-CMc5JRls.js → href-validation-BIrxavIy.js} +74 -2
  21. package/dist/_chunks/href-validation-BIrxavIy.js.map +1 -0
  22. package/dist/_chunks/{live-graph-Bx4HodF1.js → live-graph-BXDsdzBv.js} +3 -3
  23. package/dist/_chunks/{live-graph-Bx4HodF1.js.map → live-graph-BXDsdzBv.js.map} +1 -1
  24. package/dist/_chunks/{logger-pumCm3Il.js → logger-DDirEsn7.js} +3 -4
  25. package/dist/_chunks/{logger-pumCm3Il.js.map → logger-DDirEsn7.js.map} +1 -1
  26. package/dist/_chunks/navigation-root-B00jjGd5.js +233 -0
  27. package/dist/_chunks/navigation-root-B00jjGd5.js.map +1 -0
  28. package/dist/_chunks/{segment-context-CjOlyB8Y.js → param-value-C8TNYchQ.js} +2 -33
  29. package/dist/_chunks/param-value-C8TNYchQ.js.map +1 -0
  30. package/dist/_chunks/{poison-scan-BAxfTT5L.js → poison-scan-BoDLgbix.js} +2 -2
  31. package/dist/_chunks/{poison-scan-BAxfTT5L.js.map → poison-scan-BoDLgbix.js.map} +1 -1
  32. package/dist/_chunks/{router-ref-BzqbPwYC.js → router-ref-8gr8qsxN.js} +2 -2
  33. package/dist/_chunks/{router-ref-BzqbPwYC.js.map → router-ref-8gr8qsxN.js.map} +1 -1
  34. package/dist/_chunks/{rsc-cache-key-DD0fl_-s.js → rsc-cache-key-ClUiXQnK.js} +2 -2
  35. package/dist/_chunks/{rsc-cache-key-DD0fl_-s.js.map → rsc-cache-key-ClUiXQnK.js.map} +1 -1
  36. package/dist/_chunks/{scanner-BRIOmHE2.js → scanner-tdFPvDYi.js} +174 -7
  37. package/dist/_chunks/scanner-tdFPvDYi.js.map +1 -0
  38. package/dist/_chunks/segment-context-D9_89u34.js +34 -0
  39. package/dist/_chunks/segment-context-D9_89u34.js.map +1 -0
  40. package/dist/_chunks/singleflight-2lUWfcAk.js +54 -0
  41. package/dist/_chunks/singleflight-2lUWfcAk.js.map +1 -0
  42. package/dist/_chunks/{ssr-data-Ya2HJPFp.js → ssr-data-BQGhTPAK.js} +2 -17
  43. package/dist/_chunks/ssr-data-BQGhTPAK.js.map +1 -0
  44. package/dist/_chunks/{walkers-BU6z9xRV.js → walkers-DNX05dC0.js} +2 -2
  45. package/dist/_chunks/{walkers-BU6z9xRV.js.map → walkers-DNX05dC0.js.map} +1 -1
  46. package/dist/adapters/cloudflare-dev.js +1 -1
  47. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  48. package/dist/adapters/cloudflare.js +1 -1
  49. package/dist/adapters/nitro.d.ts +1 -1
  50. package/dist/adapters/nitro.d.ts.map +1 -1
  51. package/dist/adapters/nitro.js.map +1 -1
  52. package/dist/analyze/crawl-entry.js +2 -2
  53. package/dist/analyze/graph-command.js +2 -2
  54. package/dist/cache/index.js +1 -1
  55. package/dist/cache/singleflight.d.ts +2 -0
  56. package/dist/cache/singleflight.d.ts.map +1 -1
  57. package/dist/cli.js +2 -2
  58. package/dist/client/browser-entry/hydrate.d.ts +21 -15
  59. package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
  60. package/dist/client/browser-entry/index.d.ts +4 -3
  61. package/dist/client/browser-entry/index.d.ts.map +1 -1
  62. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
  63. package/dist/client/browser-entry/router-init.d.ts +17 -1
  64. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  65. package/dist/client/error-boundary.js +1 -1
  66. package/dist/client/global-context.d.ts +15 -0
  67. package/dist/client/global-context.d.ts.map +1 -0
  68. package/dist/client/index.js +138 -35
  69. package/dist/client/index.js.map +1 -1
  70. package/dist/client/internal.d.ts +0 -1
  71. package/dist/client/internal.d.ts.map +1 -1
  72. package/dist/client/internal.js +206 -55
  73. package/dist/client/internal.js.map +1 -1
  74. package/dist/client/link.d.ts.map +1 -1
  75. package/dist/client/location-search.d.ts +12 -0
  76. package/dist/client/location-search.d.ts.map +1 -0
  77. package/dist/client/navigation-api.d.ts.map +1 -1
  78. package/dist/client/navigation-commit.d.ts +18 -0
  79. package/dist/client/navigation-commit.d.ts.map +1 -1
  80. package/dist/client/navigation-context.d.ts +13 -11
  81. package/dist/client/navigation-context.d.ts.map +1 -1
  82. package/dist/client/navigation-root.d.ts +47 -108
  83. package/dist/client/navigation-root.d.ts.map +1 -1
  84. package/dist/client/navigation-transition.d.ts +136 -0
  85. package/dist/client/navigation-transition.d.ts.map +1 -0
  86. package/dist/client/nuqs-adapter.d.ts.map +1 -1
  87. package/dist/client/params-context.d.ts +4 -5
  88. package/dist/client/params-context.d.ts.map +1 -1
  89. package/dist/client/react-root.d.ts +44 -0
  90. package/dist/client/react-root.d.ts.map +1 -0
  91. package/dist/client/router-pipeline.d.ts +2 -2
  92. package/dist/client/router-pipeline.d.ts.map +1 -1
  93. package/dist/client/router-types.d.ts +12 -2
  94. package/dist/client/router-types.d.ts.map +1 -1
  95. package/dist/client/router.d.ts.map +1 -1
  96. package/dist/client/segment-cache.d.ts +39 -0
  97. package/dist/client/segment-cache.d.ts.map +1 -1
  98. package/dist/client/segment-context.d.ts.map +1 -1
  99. package/dist/client/segment-outlet.d.ts +25 -14
  100. package/dist/client/segment-outlet.d.ts.map +1 -1
  101. package/dist/client/segment-update-context.d.ts +3 -9
  102. package/dist/client/segment-update-context.d.ts.map +1 -1
  103. package/dist/client/slot-content-cache-context.d.ts +35 -0
  104. package/dist/client/slot-content-cache-context.d.ts.map +1 -0
  105. package/dist/client/ssr-data.d.ts +8 -2
  106. package/dist/client/ssr-data.d.ts.map +1 -1
  107. package/dist/client/state.d.ts +0 -15
  108. package/dist/client/state.d.ts.map +1 -1
  109. package/dist/client/use-pathname.d.ts +13 -11
  110. package/dist/client/use-pathname.d.ts.map +1 -1
  111. package/dist/client/use-search-params.d.ts +13 -13
  112. package/dist/client/use-search-params.d.ts.map +1 -1
  113. package/dist/client/use-segment-params.d.ts +18 -68
  114. package/dist/client/use-segment-params.d.ts.map +1 -1
  115. package/dist/config-types.d.ts +17 -0
  116. package/dist/config-types.d.ts.map +1 -1
  117. package/dist/config-validation.d.ts.map +1 -1
  118. package/dist/cookies/index.js +1 -1
  119. package/dist/dev-tools/holding-server.d.ts +15 -10
  120. package/dist/dev-tools/holding-server.d.ts.map +1 -1
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/index.js +44 -43
  123. package/dist/index.js.map +1 -1
  124. package/dist/plugins/dev-server.d.ts.map +1 -1
  125. package/dist/plugins/entries.d.ts.map +1 -1
  126. package/dist/plugins/shims.d.ts.map +1 -1
  127. package/dist/plugins/static-build.d.ts +2 -2
  128. package/dist/plugins/static-build.d.ts.map +1 -1
  129. package/dist/routing/codegen-write.d.ts.map +1 -1
  130. package/dist/routing/index.js +2 -2
  131. package/dist/routing/interception-overlap.d.ts +35 -0
  132. package/dist/routing/interception-overlap.d.ts.map +1 -0
  133. package/dist/routing/interception.d.ts.map +1 -1
  134. package/dist/rsc-runtime/ssr.d.ts +3 -1
  135. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  136. package/dist/server/als-registry.d.ts +6 -0
  137. package/dist/server/als-registry.d.ts.map +1 -1
  138. package/dist/server/csp-nonce.d.ts +45 -0
  139. package/dist/server/csp-nonce.d.ts.map +1 -0
  140. package/dist/server/default-status-page.d.ts.map +1 -1
  141. package/dist/server/deny-renderer.d.ts.map +1 -1
  142. package/dist/server/flight-scripts.d.ts +5 -2
  143. package/dist/server/flight-scripts.d.ts.map +1 -1
  144. package/dist/server/html-injector-core.d.ts +17 -2
  145. package/dist/server/html-injector-core.d.ts.map +1 -1
  146. package/dist/server/html-injectors.d.ts +3 -2
  147. package/dist/server/html-injectors.d.ts.map +1 -1
  148. package/dist/server/index.js +2 -2
  149. package/dist/server/internal.js +86 -37
  150. package/dist/server/internal.js.map +1 -1
  151. package/dist/server/metadata-render.d.ts.map +1 -1
  152. package/dist/server/node-stream-transforms.d.ts +3 -17
  153. package/dist/server/node-stream-transforms.d.ts.map +1 -1
  154. package/dist/server/nuqs-ssr-provider.d.ts +7 -3
  155. package/dist/server/nuqs-ssr-provider.d.ts.map +1 -1
  156. package/dist/server/pipeline-phases.d.ts.map +1 -1
  157. package/dist/server/prebuilt/key-discipline.d.ts +32 -3
  158. package/dist/server/prebuilt/key-discipline.d.ts.map +1 -1
  159. package/dist/server/primitives.d.ts.map +1 -1
  160. package/dist/server/render-utils.d.ts +4 -3
  161. package/dist/server/render-utils.d.ts.map +1 -1
  162. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
  163. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  164. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  165. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  166. package/dist/server/ssr-bridge-types.d.ts +22 -2
  167. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  168. package/dist/server/ssr-entry.d.ts.map +1 -1
  169. package/dist/server/ssr-render.d.ts +5 -1
  170. package/dist/server/ssr-render.d.ts.map +1 -1
  171. package/dist/server/ssr-wrappers.d.ts +59 -27
  172. package/dist/server/ssr-wrappers.d.ts.map +1 -1
  173. package/dist/server/types.d.ts +10 -0
  174. package/dist/server/types.d.ts.map +1 -1
  175. package/dist/shims/navigation-rsc.d.ts +21 -0
  176. package/dist/shims/navigation-rsc.d.ts.map +1 -0
  177. package/docs/api/30-api-server.mdx +1 -0
  178. package/docs/api/35-api-typescript.mdx +4 -83
  179. package/docs/learn/03-fetching-data.mdx +1 -1
  180. package/docs/learn/{03b-access-control.mdx → 04-access-control.mdx} +2 -17
  181. package/docs/learn/05-the-flush-point.mdx +175 -0
  182. package/docs/learn/{05-typed-params.mdx → 06-typed-params.mdx} +1 -1
  183. package/docs/learn/07-typed-routes.mdx +25 -49
  184. package/docs/learn/{08-streaming.mdx → 09-streaming.mdx} +1 -7
  185. package/docs/learn/{10-middleware.mdx → 11-middleware.mdx} +1 -0
  186. package/package.json +3 -3
  187. package/src/adapters/nitro.ts +7 -7
  188. package/src/cache/singleflight.ts +5 -0
  189. package/src/client/browser-entry/hydrate.ts +54 -104
  190. package/src/client/browser-entry/index.ts +16 -6
  191. package/src/client/browser-entry/post-hydration.ts +3 -2
  192. package/src/client/browser-entry/router-init.ts +84 -33
  193. package/src/client/global-context.ts +31 -0
  194. package/src/client/internal.ts +1 -2
  195. package/src/client/link.tsx +18 -18
  196. package/src/client/location-search.ts +15 -0
  197. package/src/client/navigation-api.ts +4 -2
  198. package/src/client/navigation-commit.ts +48 -2
  199. package/src/client/navigation-context.ts +25 -37
  200. package/src/client/navigation-root.tsx +55 -411
  201. package/src/client/navigation-transition.ts +278 -0
  202. package/src/client/nuqs-adapter.tsx +4 -5
  203. package/src/client/params-context.ts +13 -18
  204. package/src/client/react-root.ts +72 -0
  205. package/src/client/router-lifecycle.ts +1 -1
  206. package/src/client/router-pipeline.ts +96 -22
  207. package/src/client/router-types.ts +12 -2
  208. package/src/client/router.ts +48 -36
  209. package/src/client/segment-cache.ts +70 -2
  210. package/src/client/segment-context.ts +7 -4
  211. package/src/client/segment-outlet.tsx +41 -86
  212. package/src/client/segment-update-context.ts +7 -26
  213. package/src/client/slot-content-cache-context.ts +43 -0
  214. package/src/client/ssr-data.ts +8 -2
  215. package/src/client/state.ts +0 -26
  216. package/src/client/use-pathname.ts +21 -31
  217. package/src/client/use-search-params.ts +31 -29
  218. package/src/client/use-segment-params.ts +27 -126
  219. package/src/config-types.ts +17 -0
  220. package/src/config-validation.ts +17 -0
  221. package/src/dev-tools/holding-server.ts +23 -12
  222. package/src/index.ts +26 -11
  223. package/src/plugins/dev-server.ts +9 -12
  224. package/src/plugins/entries.ts +3 -0
  225. package/src/plugins/shims.ts +8 -7
  226. package/src/plugins/static-build.ts +9 -5
  227. package/src/react-canary.d.ts +2 -0
  228. package/src/routing/codegen-write.ts +2 -0
  229. package/src/routing/interception-overlap.ts +141 -0
  230. package/src/routing/interception.ts +118 -5
  231. package/src/rsc-runtime/ssr.ts +3 -2
  232. package/src/server/als-registry.ts +6 -0
  233. package/src/server/csp-nonce.ts +70 -0
  234. package/src/server/default-status-page.ts +1 -0
  235. package/src/server/deny-renderer.ts +7 -3
  236. package/src/server/flight-scripts.ts +9 -4
  237. package/src/server/html-injector-core.ts +26 -9
  238. package/src/server/html-injectors.ts +8 -8
  239. package/src/server/metadata-render.ts +26 -4
  240. package/src/server/node-stream-transforms.ts +7 -20
  241. package/src/server/nuqs-ssr-provider.tsx +8 -7
  242. package/src/server/pipeline-phases.ts +5 -0
  243. package/src/server/prebuilt/key-discipline.ts +82 -13
  244. package/src/server/prebuilt-runtime.ts +2 -2
  245. package/src/server/primitives.ts +4 -4
  246. package/src/server/render-utils.ts +8 -4
  247. package/src/server/rsc-entry/action-middleware-runner.ts +7 -0
  248. package/src/server/rsc-entry/error-renderer.ts +5 -2
  249. package/src/server/rsc-entry/index.ts +8 -0
  250. package/src/server/rsc-entry/ssr-renderer.ts +11 -4
  251. package/src/server/ssr-bridge-types.ts +22 -2
  252. package/src/server/ssr-entry.ts +35 -28
  253. package/src/server/ssr-render.ts +13 -4
  254. package/src/server/ssr-wrappers.tsx +81 -61
  255. package/src/server/types.ts +10 -0
  256. package/src/shared/slot-params.ts +3 -4
  257. package/src/shims/navigation-rsc.ts +47 -0
  258. package/dist/_chunks/build-manifest-DWppEdLB.js.map +0 -1
  259. package/dist/_chunks/cache-api-CQeYzA5g.js.map +0 -1
  260. package/dist/_chunks/convention-lint-DO10_pVl.js.map +0 -1
  261. package/dist/_chunks/href-validation-CMc5JRls.js.map +0 -1
  262. package/dist/_chunks/scanner-BRIOmHE2.js.map +0 -1
  263. package/dist/_chunks/segment-context-CjOlyB8Y.js.map +0 -1
  264. package/dist/_chunks/slot-params-BCTmZkQB.js +0 -76
  265. package/dist/_chunks/slot-params-BCTmZkQB.js.map +0 -1
  266. package/dist/_chunks/ssr-data-Ya2HJPFp.js.map +0 -1
  267. package/dist/_chunks/use-segment-params-DzTBpkvj.js +0 -398
  268. package/dist/_chunks/use-segment-params-DzTBpkvj.js.map +0 -1
  269. package/docs/learn/04-loading-states.mdx +0 -67
  270. package/docs/learn/04b-the-flush-point.mdx +0 -115
  271. package/docs/learn/12-client-navigation.mdx +0 -176
  272. package/docs/learn/13-configuration.mdx +0 -166
  273. package/docs/more/01-advanced-routing.mdx +0 -344
  274. package/docs/more/02-advanced-forms.mdx +0 -137
  275. package/docs/more/03-coming-from-nextjs.mdx +0 -186
  276. package/docs/more/04-metadata-and-fonts.mdx +0 -193
  277. package/docs/more/04b-mdx.mdx +0 -229
  278. package/docs/more/05-content-collections.mdx +0 -90
  279. package/docs/more/06-instrumentation.mdx +0 -214
  280. package/docs/more/07-security.mdx +0 -129
  281. package/docs/more/08-developer-experience.mdx +0 -134
  282. package/docs/more/40-why-timber.mdx +0 -50
  283. package/docs/more/41-timber-vs-nextjs.mdx +0 -81
  284. package/docs/more/42-timber-vs-others.mdx +0 -68
  285. package/docs/more/50-ai-agent-instructions.mdx +0 -171
  286. /package/docs/learn/{06-forms-and-actions.mdx → 08-forms-and-actions.mdx} +0 -0
  287. /package/docs/learn/{09-caching.mdx → 10-caching.mdx} +0 -0
  288. /package/docs/learn/{11-error-handling.mdx → 12-error-handling.mdx} +0 -0
  289. /package/docs/learn/{14-deploying.mdx → 13-deploying.mdx} +0 -0
@@ -24,6 +24,7 @@ import { pipeline } from 'node:stream/promises';
24
24
  import {
25
25
  BufferAggregator,
26
26
  FlightInjectorCore,
27
+ type FlightInjectorOptions,
27
28
  SUFFIX_BYTES,
28
29
  createInjectorState,
29
30
  createSuffixState,
@@ -34,7 +35,7 @@ import {
34
35
  } from './html-injector-core.ts';
35
36
  import { logStreamingError } from './logger.ts';
36
37
  import { createTickCoalescer } from './coalesce-tick.ts';
37
- import { isAbortError, NOINDEX_SCRIPT } from './render-utils.ts';
38
+ import { isAbortError, noindexScript } from './render-utils.ts';
38
39
 
39
40
  // ─── Buffered Transform ──────────────────────────────────────────────────────
40
41
 
@@ -167,21 +168,6 @@ export function createNodeHeadInjector(headHtml: string): Transform {
167
168
 
168
169
  // ─── RSC Flight Injection ────────────────────────────────────────────────────
169
170
 
170
- /**
171
- * Options for the Node.js flight injector.
172
- */
173
- export interface NodeFlightInjectorOptions {
174
- /**
175
- * Timeout in milliseconds for individual RSC stream reads. If a single
176
- * `rscReader.read()` call does not resolve within this duration, the
177
- * read is aborted and the stream errors with a `RenderTimeoutError`.
178
- * Default: 30000 (30s).
179
- */
180
- renderTimeoutMs?: number;
181
- /** Signal that fires when an unhandled RSC error is detected. */
182
- errorSignal?: AbortSignal;
183
- }
184
-
185
171
  /**
186
172
  * Node.js `Transform` that merges RSC script tags into the HTML stream.
187
173
  *
@@ -202,7 +188,7 @@ export interface NodeFlightInjectorOptions {
202
188
  */
203
189
  export function createNodeFlightInjector(
204
190
  rscStream: ReadableStream<Uint8Array> | undefined,
205
- options?: NodeFlightInjectorOptions
191
+ options?: FlightInjectorOptions
206
192
  ): Transform {
207
193
  if (!rscStream) {
208
194
  return new Transform({
@@ -212,7 +198,7 @@ export function createNodeFlightInjector(
212
198
  });
213
199
  }
214
200
 
215
- const core = new FlightInjectorCore(rscStream, options?.renderTimeoutMs, options?.errorSignal);
201
+ const core = new FlightInjectorCore(rscStream, options);
216
202
 
217
203
  const drain = (): void => {
218
204
  while (core.pending.length > 0) transform.push(Buffer.from(core.pending.shift()!));
@@ -300,7 +286,8 @@ export function createNodeFlightInjector(
300
286
  export function runNodePipelineWithErrorHandling(
301
287
  source: Readable,
302
288
  transforms: readonly [Transform, ...Transform[]],
303
- signal?: AbortSignal
289
+ signal?: AbortSignal,
290
+ nonce?: string
304
291
  ): PassThrough {
305
292
  const output = new PassThrough();
306
293
  const last = transforms[transforms.length - 1]!;
@@ -329,7 +316,7 @@ export function runNodePipelineWithErrorHandling(
329
316
  return;
330
317
  }
331
318
  logStreamingError({ error });
332
- output.end(Buffer.from(NOINDEX_SCRIPT, 'utf-8'));
319
+ output.end(Buffer.from(noindexScript(nonce), 'utf-8'));
333
320
  });
334
321
 
335
322
  return output;
@@ -45,17 +45,18 @@ function makeNuqsSsrAdapter(searchParams: URLSearchParams) {
45
45
  * Wrap the SSR element tree with a nuqs adapter context.
46
46
  *
47
47
  * Called by ssr-entry.ts before passing the element to renderSsrStream.
48
- * Takes the NavContext search params and provides them to nuqs hooks
48
+ * Takes the NavContext raw query string and provides it to nuqs hooks
49
49
  * running during SSR so they render with the correct initial values.
50
50
  *
51
- * @param searchParamsRecord - The request's search params as a plain record
51
+ * The raw string — not a collapsed record — is what preserves repeated
52
+ * query keys (`?tag=a&tag=b`) for nuqs multi parsers, matching the raw
53
+ * `location.search` the client adapter reads after hydration (TIM-1428).
54
+ *
55
+ * @param search - The request's raw app-visible query string (`''` or `?…`)
52
56
  * @param children - The React element tree to wrap
53
57
  */
54
- export function withNuqsSsrAdapter(
55
- searchParamsRecord: Record<string, string>,
56
- children: ReactNode
57
- ): ReactNode {
58
- const searchParams = new URLSearchParams(searchParamsRecord);
58
+ export function withNuqsSsrAdapter(search: string, children: ReactNode): ReactNode {
59
+ const searchParams = new URLSearchParams(search);
59
60
  const Provider = makeNuqsSsrAdapter(searchParams);
60
61
  // AdapterProvider types require children in the props object (not as 3rd arg)
61
62
  // eslint-disable-next-line react/no-children-prop
@@ -23,6 +23,7 @@ import {
23
23
  setMatchedSegmentPath,
24
24
  } from './request-context.ts';
25
25
  import { withSpan } from './tracing.ts';
26
+ import { requestCspNonce } from './csp-nonce.ts';
26
27
  import { logRenderError } from './logger.ts';
27
28
  import { RedirectSignal, DenySignal } from './primitives.ts';
28
29
  import { ParamCoercionError } from './route-element-builder.ts';
@@ -126,6 +127,10 @@ export async function runMiddlewarePhase(
126
127
  requestHeaders: requestHeaderOverlay,
127
128
  headers: responseHeaders,
128
129
  segmentParams: match.segmentParams,
130
+ // Lazy: reading it is the opt-in that makes the render nonce its scripts.
131
+ get nonce() {
132
+ return requestCspNonce();
133
+ },
129
134
  earlyHints: (hints) => {
130
135
  for (const hint of hints) {
131
136
  // Match Cloudflare's cached Early Hints attribute order: `as` before `rel`.
@@ -6,13 +6,19 @@
6
6
  * values in cached output are the data-leak danger zone (design/06
7
7
  * security note). During a capture render the ALS store is wrapped in a
8
8
  * transparent observing proxy; any request-context read produces a
9
- * once-per-component warning.
9
+ * structured, rate-limited production diagnostic.
10
+ *
11
+ * The diagnostic fires per offending component, rate-limited to once per
12
+ * interval (default 60 s) — not once per process. This ensures the
13
+ * signal stays visible in log aggregators across restarts and deploys.
14
+ *
15
+ * When `strictKeyDiscipline` is enabled (via timber.config.ts
16
+ * `cache.strictKeyDiscipline: true`), the diagnostic is promoted to a
17
+ * thrown error that prevents the tainted payload from being cached.
10
18
  */
11
19
 
12
20
  import type { RequestContextStore } from '../als-registry.ts';
13
-
14
- /** Components already warned — exported so tests can reset between cases. */
15
- export const keyDisciplineWarned = new Set<string>();
21
+ import { getLogger } from '../logger.ts';
16
22
 
17
23
  const REQUEST_CONTEXT_FIELDS: Record<string, string> = {
18
24
  headers: 'headers()',
@@ -20,6 +26,35 @@ const REQUEST_CONTEXT_FIELDS: Record<string, string> = {
20
26
  searchParams: 'searchParams',
21
27
  };
22
28
 
29
+ /** Rate-limit interval: one diagnostic per component per 60 seconds. */
30
+ const RATE_LIMIT_MS = 60_000;
31
+
32
+ /** Last emission timestamp per component ID. */
33
+ const lastEmitted = new Map<string, number>();
34
+
35
+ let strict = false;
36
+
37
+ /**
38
+ * Configure strict key-discipline mode. When true, a violation throws
39
+ * instead of logging — the tainted payload is never cached.
40
+ * Called at boot from the RSC entry with the resolved config value.
41
+ */
42
+ export function setKeyDisciplineStrict(enabled: boolean): void {
43
+ strict = enabled;
44
+ }
45
+
46
+ /**
47
+ * Whether the proxy should be created for this component on this render.
48
+ * Returns true when strict mode is on (always check) or enough time has
49
+ * elapsed since the last diagnostic for this component.
50
+ */
51
+ export function shouldCheckKeyDiscipline(id: string): boolean {
52
+ if (strict) return true;
53
+ const last = lastEmitted.get(id);
54
+ if (last === undefined) return true;
55
+ return Date.now() - last >= RATE_LIMIT_MS;
56
+ }
57
+
23
58
  /**
24
59
  * Wrap a store with a proxy that records per-request field access.
25
60
  * The proxy forwards all reads/writes transparently — it only observes.
@@ -39,15 +74,49 @@ export function createKeyDisciplineProxy(store: RequestContextStore): {
39
74
  return { proxy, accessedApis };
40
75
  }
41
76
 
77
+ /**
78
+ * Emit the key-discipline diagnostic or throw in strict mode.
79
+ *
80
+ * In strict mode: throws an error so the tainted payload is never cached.
81
+ * In default mode: emits a structured error-level diagnostic through the
82
+ * logger (same path as render errors), rate-limited per component.
83
+ */
42
84
  export function emitKeyDisciplineWarning(id: string, apis: Set<string>): void {
43
- if (keyDisciplineWarned.has(id)) return;
44
- keyDisciplineWarned.add(id);
45
85
  const apiList = Array.from(apis).join(', ');
46
- console.warn(
47
- `[timber] cache.component key-discipline warning: "${id}" reads ${apiList} ` +
48
- `but the cache key is computed from props and segment params only. ` +
49
- `If the output varies by ${apiList}, different users may see each ` +
50
- `other's cached content. Either pass request-derived values as ` +
51
- `props (included in the cache key) or remove the ${apiList} call.`
52
- );
86
+
87
+ if (strict) {
88
+ throw new Error(
89
+ `[timber] cache.component key-discipline violation: "${id}" reads ${apiList} ` +
90
+ `during a cache capture render. The cache key does not include ` +
91
+ `request-derived values — caching this output would serve one ` +
92
+ `user's data to another. Either pass request-derived values as ` +
93
+ `props (included in the cache key) or remove the ${apiList} call. ` +
94
+ `To downgrade this to a warning, set cache.strictKeyDiscipline: false ` +
95
+ `in timber.config.ts.`
96
+ );
97
+ }
98
+
99
+ const now = Date.now();
100
+ const last = lastEmitted.get(id);
101
+ if (last !== undefined && now - last < RATE_LIMIT_MS) return;
102
+ lastEmitted.set(id, now);
103
+
104
+ getLogger().error('cache.component cross-user data leak risk', {
105
+ component: id,
106
+ accessedApis: Array.from(apis),
107
+ message:
108
+ `cache.component "${id}" reads ${apiList} but the cache key is ` +
109
+ `computed from props and segment params only. If the output varies ` +
110
+ `by ${apiList}, different users may see each other's cached content. ` +
111
+ `Either pass request-derived values as props (included in the cache ` +
112
+ `key) or remove the ${apiList} call.`,
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Reset all diagnostic state. Exported for tests only.
118
+ */
119
+ export function resetKeyDisciplineState(): void {
120
+ lastEmitted.clear();
121
+ strict = false;
53
122
  }
@@ -48,7 +48,7 @@ import {
48
48
  import {
49
49
  createKeyDisciplineProxy,
50
50
  emitKeyDisciplineWarning,
51
- keyDisciplineWarned,
51
+ shouldCheckKeyDiscipline,
52
52
  } from './prebuilt/key-discipline.ts';
53
53
  import {
54
54
  isCaptureActive,
@@ -348,7 +348,7 @@ async function captureComponentPayload(
348
348
  // store to detect request-context reads during the capture render.
349
349
  // The proxy is transparent — it never blocks or throws; it only observes.
350
350
  const store = requestContextAls.getStore();
351
- const discipline = store && !keyDisciplineWarned.has(id) ? createKeyDisciplineProxy(store) : null;
351
+ const discipline = store && shouldCheckKeyDiscipline(id) ? createKeyDisciplineProxy(store) : null;
352
352
 
353
353
  const doCapture = async (): Promise<Uint8Array> => {
354
354
  beginCapture(id);
@@ -476,11 +476,11 @@ export function _resetWaitUntilWarning(): void {
476
476
  * react-dom/server which cannot be loaded in the RSC environment.
477
477
  */
478
478
  export class SsrStreamError extends Error {
479
- constructor(
480
- message: string,
481
- public readonly cause: unknown
482
- ) {
479
+ override readonly cause: unknown;
480
+
481
+ constructor(message: string, cause: unknown) {
483
482
  super(message);
484
483
  this.name = 'SsrStreamError';
484
+ this.cause = cause;
485
485
  }
486
486
  }
@@ -6,6 +6,8 @@
6
6
  * ssr-render.ts, and rsc-entry/helpers.ts. See TIM-1071 item 1.
7
7
  */
8
8
 
9
+ import { nonceAttr } from './csp-nonce.ts';
10
+
9
11
  /**
10
12
  * Check if an error is an abort error (connection closed by client).
11
13
  *
@@ -24,8 +26,10 @@ export function isAbortError(error: unknown): boolean {
24
26
  * document head. Injected when a render-phase error replaces the shell so
25
27
  * search engines don't index error pages.
26
28
  *
27
- * MUST be byte-identical across all stream wrappers (Node and Web) to
28
- * ensure consistent Content-Length headers.
29
+ * MUST be byte-identical across all stream wrappers (Node and Web) for a
30
+ * given nonce to ensure consistent Content-Length headers — which is why
31
+ * both wrappers call this one function rather than inlining the string.
29
32
  */
30
- export const NOINDEX_SCRIPT =
31
- '<script>document.head.appendChild(Object.assign(document.createElement("meta"),{name:"robots",content:"noindex"}))</script>';
33
+ export function noindexScript(nonce?: string): string {
34
+ return `<script${nonceAttr(nonce)}>document.head.appendChild(Object.assign(document.createElement("meta"),{name:"robots",content:"noindex"}))</script>`;
35
+ }
@@ -30,6 +30,7 @@ import { DenySignal, RedirectSignal } from '../primitives.ts';
30
30
  import { ParamCoercionError } from '../route-element-builder.ts';
31
31
  import type { RouteMatch } from '../pipeline.ts';
32
32
  import type { MiddlewareContext } from '../types.ts';
33
+ import { requestCspNonce } from '../csp-nonce.ts';
33
34
 
34
35
  /** Coerce a matched route's segment params via its `params.ts` codecs. */
35
36
  export type CoerceSegmentParamsFn = (match: RouteMatch) => Promise<void>;
@@ -112,6 +113,12 @@ export async function runMiddlewareForAction(
112
113
  // Provide a no-op so user middleware can call `ctx.earlyHints()`
113
114
  // unconditionally without branching on request kind.
114
115
  earlyHints: () => {},
116
+ // Same reasoning: an action response carries no inline scripts, so a
117
+ // nonce has nothing to stamp. Still a real value so middleware that
118
+ // builds its CSP header unconditionally keeps working.
119
+ get nonce() {
120
+ return requestCspNonce();
121
+ },
115
122
  };
116
123
 
117
124
  // ─── Run the chain ──────────────────────────────────────────────
@@ -23,6 +23,7 @@ import type { ManifestSegmentNode } from '../route-matcher.ts';
23
23
  import { DenySignal, RenderError } from '../primitives.ts';
24
24
  import type { ClientBootstrapConfig } from '../html-injectors.ts';
25
25
  import { flightInitScript, htmlEscapeJsonString } from '../flight-scripts.ts';
26
+ import { getCspNonce } from '../csp-nonce.ts';
26
27
  import { renderDenyPage, renderDenyPageAsRsc } from '../deny-renderer.ts';
27
28
  import type { DefaultPageRenderer, LayoutEntry } from '../deny-renderer.ts';
28
29
  import type { NavContext } from '../ssr-bridge-types.ts';
@@ -324,15 +325,17 @@ async function renderErrorPageViaRsc(
324
325
  // bootstrap script. Read here for the SSR-side ALS context below.
325
326
  const slotParams = getSlotParamsForClient();
326
327
 
328
+ const nonce = getCspNonce();
327
329
  const navContext: NavContext = {
328
330
  pathname: url.pathname,
331
+ search: appVisibleSearch(url).search,
329
332
  segmentParams: match.segmentParams,
330
- searchParams: Object.fromEntries(appVisibleSearch(url).params),
331
333
  slotParams,
332
334
  statusCode: status,
333
335
  responseHeaders,
334
- headHtml: flightInitScript(),
336
+ headHtml: flightInitScript(nonce),
335
337
  bootstrapScriptContent: clientBootstrap.bootstrapScriptContent,
338
+ nonce,
336
339
  rscStream: inlineStream,
337
340
  signal: req.signal,
338
341
  cookies: getCookiesForSsr(),
@@ -64,6 +64,7 @@ import { requestContextAls } from '../als-registry.ts';
64
64
  import { capturePrebuiltPayloads } from '../prebuilt-builder.ts';
65
65
  import { setPrebuiltPayloadSource } from '../prebuilt/payload-source.ts';
66
66
  import { setRscFunctions, setComponentRefreshTimeout } from '../prebuilt-runtime.ts';
67
+ import { setKeyDisciplineStrict } from '../prebuilt/key-discipline.ts';
67
68
  import { createAutoSitemapHandler } from '../sitemap-handler.ts';
68
69
  import { generateStaticSite } from '../static-generator.ts';
69
70
 
@@ -505,6 +506,13 @@ if (renderTimeoutMs) {
505
506
  setComponentRefreshTimeout(renderTimeoutMs);
506
507
  }
507
508
 
509
+ const cacheConfig = (config as Record<string, unknown>).cache as
510
+ | { strictKeyDiscipline?: boolean }
511
+ | undefined;
512
+ if (cacheConfig?.strictKeyDiscipline === true) {
513
+ setKeyDisciplineStrict(true);
514
+ }
515
+
508
516
  // Run instrumentation before the capture-mode guard — components may
509
517
  // depend on globals or SDKs initialized in register().
510
518
  await loadInstrumentation(loadUserInstrumentation);
@@ -14,6 +14,7 @@
14
14
 
15
15
  import type { ClientBootstrapConfig } from '../html-injectors.ts';
16
16
  import { flightInitScript } from '../flight-scripts.ts';
17
+ import { getCspNonce, nonceAttr } from '../csp-nonce.ts';
17
18
  import { appVisibleSearch } from '../../shared/rsc-cache-key.ts';
18
19
  import { getSlotParamsForClient } from '../request-context.ts';
19
20
  import type { LayoutEntry } from '../deny-renderer.ts';
@@ -179,10 +180,15 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
179
180
  // `getSegmentParams(slotPath)` from.
180
181
  const slotParams = getSlotParamsForClient();
181
182
 
183
+ // Middleware has already run; if it read `ctx.nonce` for its CSP header,
184
+ // that is the value every inline script must wear.
185
+ const nonce = getCspNonce();
186
+
187
+ const reqUrl = new URL(req.url);
182
188
  const navContext: NavContext = {
183
- pathname: new URL(req.url).pathname,
189
+ pathname: reqUrl.pathname,
190
+ search: appVisibleSearch(reqUrl).search,
184
191
  segmentParams: match.segmentParams,
185
- searchParams: Object.fromEntries(appVisibleSearch(new URL(req.url)).params),
186
192
  slotParams,
187
193
  statusCode: 200,
188
194
  responseHeaders,
@@ -191,12 +197,13 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
191
197
  clientBootstrap.preloadLinks +
192
198
  // Initialize __timber_f in <head> so it exists before any streaming
193
199
  // chunk scripts arrive in <body>. See flight-scripts.ts, LOCAL-415.
194
- (clientJsDisabled ? '' : flightInitScript()),
200
+ (clientJsDisabled ? '' : flightInitScript(nonce)),
195
201
  getHeadSegmentScript: clientJsDisabled
196
202
  ? undefined
197
203
  : () =>
198
- `<script>self.__timber_segments=${htmlEscapeJsonString(JSON.stringify(buildSegmentInfo(segments, layoutComponents, opts.slotSkipInfo, undefined, getFirstDeniedSegmentIndex())))}</script>`,
204
+ `<script${nonceAttr(nonce)}>self.__timber_segments=${htmlEscapeJsonString(JSON.stringify(buildSegmentInfo(segments, layoutComponents, opts.slotSkipInfo, undefined, getFirstDeniedSegmentIndex())))}</script>`,
199
205
  bootstrapScriptContent: clientBootstrap.bootstrapScriptContent,
206
+ nonce,
200
207
  // Skip RSC inline stream when client JS is disabled — no client to hydrate.
201
208
  rscStream: clientJsDisabled ? undefined : inlineStream,
202
209
  deferSuspenseFor: deferSuspenseFor > 0 ? deferSuspenseFor : undefined,
@@ -41,10 +41,20 @@ import type { SlotParamsRecord } from '../shared/slot-params.ts';
41
41
  export interface NavContext {
42
42
  /** The requested pathname */
43
43
  pathname: string;
44
+ /**
45
+ * The app-visible raw query string (`''` or `?…`), with the framework's
46
+ * `_rsc` cache key stripped — from `appVisibleSearch()`. The single
47
+ * representation of the request's search state on this contract: it feeds
48
+ * `NavigationProvider`, the nuqs SSR adapter, and the ALS-backed SSR data,
49
+ * so `useSearchParams()`, nuqs hooks, and `<Link preserveSearchParams>`
50
+ * read the identical value they will read from the address bar after
51
+ * hydration. The raw string preserves repeated keys (`?tag=a&tag=b`),
52
+ * which a collapsed `Record<string, string>` cannot represent — do not
53
+ * reintroduce one beside this field (TIM-1428).
54
+ */
55
+ search: string;
44
56
  /** Extracted route segment params. Post-coercion, values may be non-string (TIM-1347). */
45
57
  segmentParams: CoercedParams;
46
- /** Search params from the URL */
47
- searchParams: Record<string, string>;
48
58
  /**
49
59
  * Per-slot segment params, keyed by slot tree path. Absent when the request
50
60
  * rendered no slot with params of its own. Feeds `useSegmentParams(path)`
@@ -59,6 +69,16 @@ export interface NavContext {
59
69
  headHtml: string;
60
70
  /** Inline JS for React's bootstrapScriptContent — kicks off module loading */
61
71
  bootstrapScriptContent: string;
72
+ /**
73
+ * Request CSP nonce, generated when middleware read `ctx.nonce` for its
74
+ * `Content-Security-Policy` header (`getCspNonce()`). SSR passes it to
75
+ * Fizz (`nonce` option → bootstrap + runtime scripts), to the Flight
76
+ * decoder (preinit scripts), and to every timber-generated inline
77
+ * `<script>`. Undefined when no middleware asked for one, in which case
78
+ * the markup is byte-identical to a nonce-less render.
79
+ * See design/13-security.md §"Content Security Policy" (TIM-1414).
80
+ */
81
+ nonce?: string;
62
82
  /** Tee'd RSC stream for client-side hydration (inlined into HTML) */
63
83
  rscStream?: ReadableStream<Uint8Array>;
64
84
  /** Max Suspense hold window (ms). SSR delays the first flush by this
@@ -39,9 +39,8 @@ import { SsrStreamError } from './primitives.ts';
39
39
  import { createBufferedTransformStream, injectHead, injectRscPayload } from './html-injectors.ts';
40
40
  import { wrapSsrElement } from './ssr-wrappers.tsx';
41
41
  import { withSpan } from './tracing.ts';
42
- import { setCurrentParams } from '../client/use-segment-params.ts';
43
42
  import { registerSsrDataProvider, type SsrData } from '../client/ssr-data.ts';
44
- import { readPayloadTree } from '../shared/payload-root.ts';
43
+ import { splitPayloadRoot } from '../shared/payload-root.ts';
45
44
  import type { NavContext } from './ssr-bridge-types.ts';
46
45
 
47
46
  // Pre-import Node.js stream modules at module load time — not per-request.
@@ -123,7 +122,7 @@ export async function handleSsr(
123
122
  // useSearchParams, useCookie, useParams, etc.).
124
123
  const ssrData: SsrData = {
125
124
  pathname: navContext.pathname,
126
- searchParams: navContext.searchParams,
125
+ search: navContext.search,
127
126
  cookies: navContext.cookies ?? new Map(),
128
127
  params: navContext.segmentParams,
129
128
  slotParams: navContext.slotParams,
@@ -136,12 +135,6 @@ export async function handleSsr(
136
135
  // Client hooks read from getSsrData() which delegates to this
137
136
  // ALS store via the registered provider.
138
137
  return ssrDataAls.run(ssrData, async () => {
139
- // Also set the module-level currentParams for useSegmentParams().
140
- // useParams reads from getSsrData() during SSR (ALS-backed),
141
- // but setCurrentParams is kept for the client-side path where
142
- // the segment router updates params on navigation.
143
- setCurrentParams(navContext.segmentParams);
144
-
145
138
  // Decode the RSC stream into a React element tree.
146
139
  // createFromReadableStream resolves client component references
147
140
  // (from "use client" modules) using the SSR environment's module
@@ -152,30 +145,41 @@ export async function handleSsr(
152
145
  // On Node.js: convert Web ReadableStream → Node Readable → createFromNodeStream
153
146
  // (eliminates Promise-per-chunk overhead from Web Streams reader)
154
147
  // On Workers: createFromReadableStream (Web Streams are V8-native C++ there)
148
+ //
149
+ // The request CSP nonce goes to the Flight decoder too: Flight emits
150
+ // `<script>` preinits for client-reference modules, and those need
151
+ // the nonce as much as timber's own inline tags do (TIM-1414).
152
+ const nonce = navContext.nonce;
155
153
  let root: unknown;
156
154
  if (hasNodeStreamDecode && _nodeStreamImports) {
157
155
  const nodeRscStream = _nodeStreamImports.ReadableFromWeb(
158
156
  rscStream as import('stream/web').ReadableStream
159
157
  );
160
- root = createFromNodeStream(nodeRscStream);
158
+ root = createFromNodeStream(nodeRscStream, { nonce });
161
159
  } else {
162
- root = createFromReadableStream(rscStream);
160
+ root = createFromReadableStream(rscStream, { nonce });
163
161
  }
164
162
  // The payload root is `{ tree, params, slotParams }` — params ride
165
- // beside the tree rather than in a provider around it (TIM-1297). SSR
166
- // renders the tree; the params it needs are already in `ssrData` above,
167
- // which is where `useSegmentParams()` reads them from during SSR. The
168
- // client renders its own provider over the same records.
169
- const element = Promise.resolve(root).then(readPayloadTree) as React.ReactNode;
163
+ // beside the tree rather than in a provider around it (TIM-1297).
164
+ // splitPayloadRoot is the same split the browser performs: the tree
165
+ // renders, and the params half feeds PayloadRoot inside the wrapper
166
+ // chain so `useSegmentParams()` reads the identical derivation on
167
+ // both sides (TIM-1424).
168
+ const { tree, params } = splitPayloadRoot(root);
169
+ const element = tree as React.ReactNode;
170
170
  const _decodeEnd = performance.now();
171
171
 
172
- // Wrap with the same component tree structure as the client hydration
173
- // tree (NavigationRoot → TopLoader →
174
- // TimberNuqsAdapter → NuqsAdapterProvider → NavigationProvider).
175
- // This ensures useId() produces matching IDs on both sides, preventing
176
- // hydration mismatches in libraries like Radix UI. See TIM-532.
172
+ // Wrap with the same component tree the client hydrates —
173
+ // real NavigationProvider and PayloadRoot, not no-op mirrors —
174
+ // so hooks resolve identically and useId() matches on both sides.
175
+ // See TIM-532, TIM-1424.
177
176
  const hasTopLoader = _runtimeConfig.topLoader?.enabled !== false;
178
- const wrappedElement = wrapSsrElement(element, navContext.searchParams, hasTopLoader);
177
+ const wrappedElement = wrapSsrElement(element, {
178
+ pathname: navContext.pathname,
179
+ search: navContext.search,
180
+ paramsSource: params,
181
+ hasTopLoader,
182
+ });
179
183
 
180
184
  // Render to HTML stream (waits for onShellReady).
181
185
  //
@@ -201,6 +205,7 @@ export async function handleSsr(
201
205
  try {
202
206
  nodeHtmlStream = await renderSsrNodeStream(wrappedElement, {
203
207
  bootstrapScriptContent: navContext.bootstrapScriptContent || undefined,
208
+ nonce,
204
209
  deferSuspenseFor: navContext.deferSuspenseFor,
205
210
  signal: navContext.signal,
206
211
  renderTimeoutMs,
@@ -240,13 +245,15 @@ export async function handleSsr(
240
245
  const flightInjector = createNodeFlightInjector(navContext.rscStream, {
241
246
  renderTimeoutMs,
242
247
  errorSignal: navContext._errorSignal,
248
+ nonce,
243
249
  });
244
250
  const moveSuffix = createNodeMoveSuffixTransform();
245
251
 
246
252
  const output = runNodePipelineWithErrorHandling(
247
253
  nodeHtmlStream,
248
254
  [bufferedTransform, headInjector, flightInjector, moveSuffix],
249
- navContext.signal
255
+ navContext.signal,
256
+ nonce
250
257
  );
251
258
  const _pipelineEnd = performance.now();
252
259
 
@@ -271,6 +278,7 @@ export async function handleSsr(
271
278
  try {
272
279
  htmlStream = await renderSsrStream(wrappedElement, {
273
280
  bootstrapScriptContent: navContext.bootstrapScriptContent || undefined,
281
+ nonce,
274
282
  deferSuspenseFor: navContext.deferSuspenseFor,
275
283
  signal: navContext.signal,
276
284
  renderTimeoutMs,
@@ -300,12 +308,11 @@ export async function handleSsr(
300
308
  const resolvedHeadHtml = navContext.headHtml + (navContext.getHeadSegmentScript?.() ?? '');
301
309
  let outputStream = htmlStream.pipeThrough(createBufferedTransformStream());
302
310
  outputStream = injectHead(outputStream, resolvedHeadHtml);
303
- outputStream = injectRscPayload(
304
- outputStream,
305
- navContext.rscStream,
311
+ outputStream = injectRscPayload(outputStream, navContext.rscStream, {
306
312
  renderTimeoutMs,
307
- navContext._errorSignal
308
- );
313
+ errorSignal: navContext._errorSignal,
314
+ nonce,
315
+ });
309
316
  const _pipelineEnd = performance.now();
310
317
 
311
318
  navContext._ssrTimings = {