@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
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: 'API: TypeScript'
3
- description: 'Generated route types, $segment modules, typed Link, typed params, and the Routes interface.'
3
+ description: 'Generated route types, $segment module exports, context types, and the Routes interface.'
4
4
  slug: 'api-typescript'
5
5
  ---
6
6
 
@@ -32,95 +32,16 @@ export type AllSegmentParams = { /* union of all route params */ };
32
32
 
33
33
  Types reference your `app/schema.ts` codecs directly, so they stay in sync automatically.
34
34
 
35
- ## `$segment` — Per-Route Type Narrowing
35
+ ## `$segment` Module
36
36
 
37
- Each route directory gets a generated `$segment` module (via TypeScript's `rootDirs`). Import `SEGMENT_PATH` for type-safe param access:
38
-
39
- ```tsx
40
- // app/products/[id]/page.tsx
41
- import { SEGMENT_PATH } from './$segment';
42
- import { getSegmentParams } from '@timber-js/app/server';
43
-
44
- export default async function ProductPage() {
45
- const { id } = getSegmentParams(SEGMENT_PATH);
46
- // id is typed from your schema codec (e.g., number for codec.integer)
47
- }
48
- ```
49
-
50
- ### Exports
37
+ Each route directory gets a generated `$segment` module, resolved via TypeScript's `rootDirs` (setup and usage in [Typed Routes](/docs/typed-routes)).
51
38
 
52
39
  | Export | Type | Description |
53
40
  | -------------- | ------------------- | ------------------------------------------------------------- |
54
41
  | `ROUTE` | String literal | URL route pattern, groups stripped (e.g., `'/products/[id]'`) |
55
42
  | `SEGMENT_PATH` | String literal | Full tree path including groups and slots |
56
43
 
57
- ### tsconfig.json
58
-
59
- Add `rootDirs` so TypeScript can resolve `$segment` imports:
60
-
61
- ```jsonc
62
- {
63
- "compilerOptions": {
64
- "rootDirs": [".", ".timber/types"]
65
- }
66
- }
67
- ```
68
-
69
- ## Typed `<Link>`
70
-
71
- `<Link>` validates `href` against the route map:
72
-
73
- ```tsx
74
- import { Link } from '@timber-js/app/client';
75
-
76
- <Link href="/about">About</Link> // ✓
77
- <Link href="/nonexistent">Bad</Link> // ✗ TypeScript error
78
- ```
79
-
80
- For dynamic routes, pass `segmentParams`:
81
-
82
- ```tsx
83
- import { Link } from '@timber-js/app/client';
84
-
85
- <Link href="/products/[id]" segmentParams={{ id: 42 }}>
86
- Product
87
- </Link>
88
- // → <a href="/products/42">Product</a>
89
- ```
90
-
91
- ## Typed Segment Params
92
-
93
- With `SEGMENT_PATH` from `$segment`, param types are narrowed from your `app/schema.ts` codecs:
94
-
95
- ```tsx
96
- // app/products/[id]/page.tsx
97
- import { SEGMENT_PATH } from './$segment';
98
- import { getSegmentParams } from '@timber-js/app/server';
99
-
100
- export default async function ProductPage() {
101
- const { id } = getSegmentParams(SEGMENT_PATH);
102
- // id: number (from z.coerce.number().int().positive() in schema.ts)
103
- }
104
- ```
105
-
106
- Without `SEGMENT_PATH`, params are `Partial<AllSegmentParams>` — all optional.
107
-
108
- ## Typed Search Params
109
-
110
- Search params are typed by the definition you create and import — there is no convention file:
111
-
112
- ```ts
113
- // app/products/search-params.ts — any module you like
114
- import { defineSearchParams } from '@timber-js/app/search-params';
115
- import { z } from 'zod/v4';
116
-
117
- export const searchParams = defineSearchParams({
118
- page: z.coerce.number().default(1),
119
- sort: z.enum(['price', 'name']).default('price'),
120
- });
121
-
122
- // In page.tsx — searchParams.get() returns { page: number; sort: 'price' | 'name' }
123
- ```
44
+ `<Link>` `href` validation and `segmentParams`/`searchParams` typing are described in [Typed Routes](/docs/typed-routes); the param and search-param definitions they draw from are in [Typed Params](/docs/typed-params).
124
45
 
125
46
  ## Context Types
126
47
 
@@ -76,7 +76,7 @@ export default async function ProductPage() {
76
76
  }
77
77
  ```
78
78
 
79
- `deny(404)` sends a real HTTP 404 — not a 200 with an error message. Search engines, CDNs, and `curl -f` all see the correct status code.
79
+ `deny(404)` sends a real HTTP 404 — not a 200 with an error message. Search engines, CDNs, and `curl -f` all see the correct status code. Other status codes, status-code files, and error boundaries are covered in [Error Handling](/docs/error-handling).
80
80
 
81
81
  For typed params (numbers, UUIDs, enums), define codecs in `app/schema.ts`. See [Typed Params](/docs/typed-params).
82
82
 
@@ -89,15 +89,7 @@ export default async function access() {
89
89
 
90
90
  Access runs root-to-leaf. If the root gate fails, deeper gates never execute.
91
91
 
92
- ## `deny()`
93
-
94
- `deny()` produces the correct HTTP status code and renders the matching status file:
95
-
96
- ```ts
97
- deny(); // 403 Forbidden (default)
98
- deny(404); // 404 Not Found
99
- deny(401); // 401 Unauthorized
100
- ```
92
+ `deny()` defaults to `403`; pass a status code (`deny(404)`, `deny(401)`) to change it. How the status code becomes a rendered page is covered in [Error Handling](/docs/error-handling).
101
93
 
102
94
  ## Slot Degradation
103
95
 
@@ -120,11 +112,4 @@ When access denies, that segment's `layout.tsx` never runs — `denied.tsx` rend
120
112
 
121
113
  ## Middleware vs Access
122
114
 
123
- | | `middleware.ts` | `access.ts` |
124
- | ------------- | -------------------------------------- | --------------------------------- |
125
- | Runs | Before rendering | Inside React tree |
126
- | Purpose | Lightweight checks, redirects, headers | Auth, data gating |
127
- | `React.cache` | Not available | Active — shares scope with layout |
128
- | Slots | N/A | Graceful degradation |
129
-
130
- Use `middleware.ts` for headers, redirects, and cache warming. Use `access.ts` for auth — it has access to `React.cache` and supports slot degradation.
115
+ `access.ts` runs inside the React tree, so it has `React.cache` and supports slot degradation. `middleware.ts` runs before rendering, for headers, redirects, and cache warming. The full comparison is in [Middleware](/docs/middleware).
@@ -0,0 +1,175 @@
1
+ ---
2
+ title: 'The Flush Point'
3
+ description: 'When timber commits the HTTP status code, why there is no loading.tsx, and how Early Hints keep it fast.'
4
+ slug: 'the-flush-point'
5
+ # notAI: true
6
+ ---
7
+
8
+ # The Flush Point
9
+
10
+ The flush point is when the framework sends the first byte of HTML to the browser and commits the HTTP status code. In a traditional PHP website, this happens all at once.
11
+
12
+ But modern web frameworks, and notably react server components, have embraced a streaming architecture. Allowing you to send content _after_ the flush point.
13
+
14
+ While this is ultimately incredibly powerful, it brings forth confusion because contextually, code can have different side effects depending on _when_ you call it.
15
+
16
+ On a next.js app, if I call `notFound()` inside of a `page.tsx` that has a sibling `loading.tsx` – my status code will return `200`. By obfuscating the flush point, we've actually just made developer clarity murky.
17
+
18
+ So timber works differently. At no point does timber flush early, unless _you_ (the developer), choose to place a `<Suspense>` boundary. Often this will end up being below your `page.tsx` level, so you'll still have access to controlling and sending proper status codes.
19
+
20
+ ## You Might Not Need A `loading.tsx`
21
+
22
+ Most React frameworks push you towards building page-level skeletons or loading states. Though it's entirely trivial to build out that experience with timber, we nudge you away from that for several reasons.
23
+
24
+ This is not referring to loading states on things like mutations (like submitting a form) or other actions. This is referring to initial (server) or secondary (client) page loading.
25
+
26
+ This is how we as frontend engineers make browsing the web a less anxiety-inducing experience.
27
+
28
+ ### Loading States Are an Extra Class of UI to Maintain
29
+
30
+ You have to design loading states. Decide between spinners and skeletons, and then mimic your UI in the skeleton.
31
+
32
+ ### Loading States Cause Content Layout Shift
33
+
34
+ Loading states are fixed and content is most often dynamic both in size (think text length) and count (think number of rows). So even if you work incredibly hard to avoid major CLS, you'll invariably end up with CLS somewhere.
35
+
36
+ ### Loading States Are a Flash of UI
37
+
38
+ Loading states are inherently temporary, so this adds flashes of content. This causes anxiety as the page spasms before finally reaching an undetermined final state. The user is left in a daze wondering if the page is finally ready.
39
+
40
+ Every possible "finite state" of UI is another that you and the user have to keep in context. You'll be _surprised_ how much simpler a website feels when a page has one major representation, vs several.
41
+
42
+ ### Loading States Hide Prior Context
43
+
44
+ Go to an old PHP site and click a URL. The _browser_ shows a loading state, but the prior page remains visible. The new page only becomes visible once it is ready. There's little advantage to hiding the past context while the user waits for the new context to load.
45
+
46
+ ### timber Includes a Global Route-Based Loading State
47
+
48
+ On every route change, timber triggers a global page-level toploader loading state. If you want to add a secondary loading state that dims the prior content, you can easily do so.
49
+
50
+ ### Loading States Break JavaScript Disabled Requests
51
+
52
+ `curl` a page with a `loading.tsx` and you'll get back a shell, not the content. Though people downsell the cost, it genuinely does hinder both SEO and agentic requests.
53
+
54
+ Leaning on the web as intended is always better than assuming _all_ consumers have JavaScript enabled. Plus, sticking JavaScript in between your _hot_ rendering path is _inherently_ slower. It's just not necessary when you adopt a comprehensive web architecture like React Server Components.
55
+
56
+ ### Loading States Break Status Codes
57
+
58
+ Loading states flush before the content is even fetched. This feels _faster_, but you are flushing before you know if the user is authenticated, the data exists, or any other class of issue. By flushing immediately, you are forced to send a 200 and lean on JavaScript-injected meta tags to signify errors.
59
+
60
+ This breaks the contract of the web for any consumer that isn't a human. Use real status codes, you will gain downstream benefits from doing so.
61
+
62
+ ### Most Loading States Paper Over a Bad Data Architecture
63
+
64
+ People often use loading states because their database is poorly optimized, or 50ms away from their rendering server, or otherwise. With a well designed data infrastructure, you can often fetch your data plenty fast to render your entire page in < 100ms.
65
+
66
+ And when you can't, you can stream and flush those rare pages trivially.
67
+
68
+ ### Loading States Still Have Value, but More-So as Opt-In Secondary or Tertiary Content
69
+
70
+ To add a loading state as a small sub-section of a page is trivial – just wrap your component in `<Suspense>` and you can defer slower, lower priority content.
71
+
72
+ If you decide you want a full page level loading state, you can wrap your layout or page in `<Suspense>` on a case by case basis. No `loading.tsx` needed.
73
+
74
+ --------
75
+
76
+ A `loading.tsx` forces your flush point up ever so slightly, but you give up all the things above. Instead, timber asks you to place `<Suspense>` boundaries yourself – thereby _explicitly_ opting into the flush point.
77
+
78
+ Now you get a calm, performant website that doesn't have immense content-layout-shift, doesn't hide prior content during loading, and can return proper status codes. The `loading.tsx` seems innocent enough, but its cost both cognitively and architecturally is high.
79
+
80
+ ------
81
+
82
+ AI below..
83
+
84
+ ## The Problem
85
+
86
+ Most streaming frameworks send a `200 OK` immediately and figure out the real outcome later:
87
+
88
+ ```
89
+ Request → 200 OK → ... render ... → oh, it's a 404
90
+ ```
91
+
92
+ By the time the server discovers the page doesn't exist, the status code is already sent. The client sees `200`. Search engines see `200`. CDNs cache it as `200`.
93
+
94
+ This breaks search engines (deleted pages never deindex), CDNs (404s get cached as successes), monitoring (zero errors while users see broken pages), and `curl`/scripts (`curl -f` won't detect the failure).
95
+
96
+ ## timber's Solution
97
+
98
+ timber holds the response until it knows the real outcome:
99
+
100
+ ```
101
+ Request arrives
102
+ → Route matched
103
+ → proxy.ts runs
104
+ → middleware.ts runs
105
+ → access.ts runs
106
+ → React shell renders (onShellReady)
107
+ → ✓ Status code committed ← flush point
108
+ → Shell HTML sent to browser
109
+ → Suspense boundaries stream in
110
+ ```
111
+
112
+ The status code commits when **all three** conditions are met:
113
+
114
+ 1. Middleware completed without returning a response
115
+ 2. All access checks passed (or denied with a real status code)
116
+ 3. React's `onShellReady` fired — the synchronous shell rendered without error
117
+
118
+ A missing page returns a real `404`. A failed auth check returns a real `403`. A redirect returns a real `302`. The HTTP layer tells the truth.
119
+
120
+ ## Before and After the Flush
121
+
122
+ **Before the flush** (blocking):
123
+
124
+ - `proxy.ts` — global request processing
125
+ - `middleware.ts` — route-level request processing
126
+ - `access.ts` — authorization gates
127
+ - Synchronous component rendering (the shell)
128
+
129
+ **After the flush** (streaming):
130
+
131
+ - `<Suspense>` boundaries resolve and stream in
132
+ - Slow data loads complete
133
+ - The page progressively fills in
134
+
135
+ ``` leading="none"
136
+ ┌─────── flush point
137
+ │
138
+ ▼
139
+ ├────────┤──────────────────────────┤
140
+ blocking streaming
141
+ (shell) (suspense boundaries)
142
+ ```
143
+
144
+ Everything before the flush determines the status code. Everything after streams progressively. You control the boundary by choosing what goes inside `<Suspense>` and what doesn't — see [Streaming](/docs/streaming) for how to place those boundaries.
145
+
146
+ ## Pages Work Without JavaScript
147
+
148
+ Because timber holds the flush until the shell is complete, the browser receives a fully-formed HTML document. Content is visible, links work as standard `<a>` tags, and forms submit as standard POSTs. JavaScript adds client-side navigation, interactive components, and streaming updates — but the page works without it.
149
+
150
+ This isn't a special mode. It's how timber works by default.
151
+
152
+ ## Early Hints
153
+
154
+ timber doesn't make you wait for assets while the shell renders. At route-match time — before middleware runs — timber sends `103 Early Hints` with CSS, JS, and font URLs based on the build manifest. The browser starts downloading assets while the server is still working:
155
+
156
+ ```
157
+ 103 Early Hints
158
+ Link: </styles/main.css>; rel=preload; as=style
159
+ Link: </chunks/page-abc.js>; rel=modulepreload
160
+
161
+ ... middleware runs, shell renders ...
162
+
163
+ 200 OK
164
+ <html>...
165
+ ```
166
+
167
+ You get the correctness of a held flush with the performance of early resource loading.
168
+
169
+ ## How to Think About It
170
+
171
+ Primary content — the data that defines whether a page exists, who can see it, and what it contains — should load before the flush. Put it in your components directly.
172
+
173
+ Secondary content — recommendations, activity feeds, analytics widgets — can load after the flush. Wrap it in `<Suspense>`.
174
+
175
+ The flush point is the dividing line between "what the page _is_" and "what the page _also shows_."
@@ -52,7 +52,7 @@ Every dynamic segment key must be unique across segment types (dynamic, catch-al
52
52
 
53
53
  ### Type Narrowing with `$segment`
54
54
 
55
- Each route directory gets a generated `./$segment` virtual module that exports `SEGMENT_PATH` — a string literal typed to the segment's tree path. Import it and pass it to `getSegmentParams()` for exact types:
55
+ Each route directory gets a generated `./$segment` module that exports `SEGMENT_PATH` — a string literal typed to the segment's tree path (how it's generated and the `tsconfig` setup are in [Typed Routes](/docs/typed-routes)). Pass it to `getSegmentParams()` for exact types:
56
56
 
57
57
  ```tsx title="app/products/[id]/page.tsx" use="schema"
58
58
  import { SEGMENT_PATH } from './$segment';
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  title: 'Typed Routes'
3
- description: 'Build-time route types — <Link> validates href, params are inferred from the file tree.'
3
+ description: 'Build-time route types — <Link> validates href, $segment narrows params to the current route.'
4
4
  slug: 'typed-routes'
5
5
  ---
6
6
 
7
7
  # Typed Routes
8
8
 
9
- timber.js generates route types at build time. `<Link>` type-checks `href` against your actual route tree. Segment params are inferred from directory names. Search params are typed when you define them.
9
+ timber.js generates route types at build time. `<Link>` type-checks `href` against your actual route tree, and every route directory gets a `$segment` module that narrows params to that route. This page covers what the codegen gives you; defining and reading params is covered in [Typed Params](/docs/typed-params).
10
10
 
11
11
  ## How It Works
12
12
 
@@ -53,9 +53,26 @@ Or use an interpolated href directly:
53
53
 
54
54
  Both forms are type-safe. With the pattern form (`/products/[id]`), `segmentParams` is required and typed. With the interpolated form, `segmentParams` is forbidden.
55
55
 
56
- ## Typed Segment Params
56
+ ### Search params on `<Link>`
57
57
 
58
- Each route directory gets a generated `$segment` module. Import `SEGMENT_PATH` for type-safe param access:
58
+ Build search params from a `defineSearchParams` definition. `buildSearchParams` type-checks the values and applies the definition's codecs and URL-key aliases:
59
+
60
+ ```tsx
61
+ import { searchParams } from './search-params';
62
+
63
+ <Link
64
+ href="/products/[id]"
65
+ segmentParams={{ id }}
66
+ searchParams={searchParams.buildSearchParams({ sort: 'newest', q: 'boots' })}
67
+ />
68
+ // → /products/42?sort=newest&search=boots (q is aliased to search)
69
+ ```
70
+
71
+ A plain object also works as an untyped escape hatch for one-off params with no definition — but it skips codecs and aliases, so do not use it for keys a definition owns.
72
+
73
+ ## `$segment` and `SEGMENT_PATH`
74
+
75
+ Each route directory gets a generated `$segment` module. It exports `SEGMENT_PATH`, a string literal typed to that directory's position in the route tree. Passing it to `getSegmentParams()` / `useSegmentParams()` narrows the result to exactly the params that exist at that depth:
59
76
 
60
77
  ```tsx
61
78
  // app/products/[id]/page.tsx
@@ -68,7 +85,9 @@ export default async function ProductPage() {
68
85
  }
69
86
  ```
70
87
 
71
- Without `SEGMENT_PATH`, params are untyped — all optional strings.
88
+ Without `SEGMENT_PATH`, params are `Partial<AllSegmentParams>` — every param optional.
89
+
90
+ `$segment` also exports `ROUTE`, the URL pattern with route groups stripped (e.g. `'/products/[id]'`).
72
91
 
73
92
  ### tsconfig.json
74
93
 
@@ -82,51 +101,8 @@ Add `rootDirs` so TypeScript can resolve `$segment` imports:
82
101
  }
83
102
  ```
84
103
 
85
- ## Typed Search Params
86
-
87
- Define search params with `defineSearchParams()` in any module and import it — there is no convention file. Search params are then fully typed throughout the stack:
88
-
89
- ```ts title="app/products/search-params.ts"
90
- import { defineSearchParams, withDefault, withUrlKey } from '@timber-js/app/search-params';
91
- import { parseAsInteger, parseAsStringEnum } from 'nuqs';
92
- import { z } from 'zod/v4';
93
-
94
- export const searchParams = defineSearchParams({
95
- page: withDefault(parseAsInteger, 1),
96
- q: withUrlKey(z.string().nullable().default(null), 'search'),
97
- category: z.string().nullable().default(null),
98
- sort: withDefault(parseAsStringEnum(['price-asc', 'price-desc', 'newest']), 'newest'),
99
- });
100
- ```
101
-
102
- ```tsx title="app/products/page.tsx"
103
- import { searchParams } from './search-params';
104
-
105
- export default async function ProductsPage() {
106
- const { page, q, category, sort } = await searchParams.get();
107
- // page: number, q: string | null, category: string | null, sort: 'price-asc' | ...
108
- }
109
- ```
110
-
111
- Pass them to `<Link>` by building them from the definition. `buildSearchParams` type-checks the values and applies the definition's codecs and URL-key aliases:
112
-
113
- ```tsx
114
- import { searchParams } from './search-params';
115
-
116
- <Link
117
- href="/products/[id]"
118
- segmentParams={{ id }}
119
- searchParams={searchParams.buildSearchParams({ sort: 'newest', q: 'boots' })}
120
- />
121
- // → /products/42?sort=newest&search=boots (q is aliased to search)
122
- ```
123
-
124
- A plain object also works as an untyped escape hatch for one-off params with no definition — but it skips codecs and aliases, so do not use it for keys a definition owns.
125
-
126
- See [Typed Params](/docs/typed-params) for the full search params API.
127
-
128
104
  ## Codegen
129
105
 
130
106
  Types are generated during `timber dev` (on startup and when routes change) and `timber build`. The generated `.timber/routes.d.ts` is gitignored — it's a build artifact. Run `timber dev` once to get types for your editor.
131
107
 
132
- For the full type definitions and context types (`RouteContext`, `MiddlewareContext`, `AccessContext`), see the [TypeScript API Reference](/docs/api-typescript).
108
+ For the generated type shapes and context types (`RouteContext`, `MiddlewareContext`, `AccessContext`), see the [TypeScript API Reference](/docs/api-typescript).
@@ -74,9 +74,7 @@ The product loads before the flush — if it doesn't exist, the server sends a r
74
74
 
75
75
  The rule: if missing content means the page shouldn't exist, don't stream it.
76
76
 
77
- ## No `loading.tsx`
78
-
79
- There is no `loading.tsx` convention. timber does not insert Suspense boundaries for you. If you want a loading state, you place `<Suspense>` explicitly. This keeps you in control of what streams and what blocks.
77
+ There is no `loading.tsx` convention — timber never inserts Suspense boundaries for you. See [The Flush Point](/docs/the-flush-point) for why.
80
78
 
81
79
  ## The Layout Suspense Footgun
82
80
 
@@ -140,7 +138,3 @@ When `deny()` or `redirect()` fires inside a `<Suspense>` boundary:
140
138
  `deferSuspenseFor` extends the window where signals can be promoted.
141
139
 
142
140
  **The rule:** don't rely on Suspense-wrapped content to control the status code. If content must drive the status code, fetch it outside `<Suspense>`. Signal promotion is a safety net, not a guarantee.
143
-
144
- ## Early Hints (103)
145
-
146
- Before the flush, timber sends 103 Early Hints with CSS, fonts, and JS URLs. These fire at route-match time — before middleware runs — based on the build manifest. The browser starts fetching resources while the server is still working.
@@ -83,6 +83,7 @@ export default async function middleware(ctx: MiddlewareContext) {
83
83
  ctx.segmentParams; // Route params (already coerced by schema.ts)
84
84
  ctx.headers; // Response headers (writable)
85
85
  ctx.requestHeaders; // Request headers (injectable)
86
+ ctx.nonce; // Per-request CSP nonce (see Security → Content Security Policy)
86
87
  }
87
88
  ```
88
89
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.196",
3
+ "version": "0.2.0-alpha.198",
4
4
  "description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -165,8 +165,8 @@
165
165
  "@vitejs/plugin-react": "^6.1.0",
166
166
  "@vitejs/plugin-rsc": ">=0.5.28",
167
167
  "nuqs": "^2.0.0",
168
- "react": "19.2.8",
169
- "react-dom": "19.2.8",
168
+ "react": "19.3.0-canary-f789f203-20260825",
169
+ "react-dom": "19.3.0-canary-f789f203-20260825",
170
170
  "satteri": "^0.9.5",
171
171
  "vite": "8.2.1",
172
172
  "vite-plugin-satteri": "^0.2.15",
@@ -5,23 +5,23 @@
5
5
  // compression, graceful shutdown, static file serving, and platform quirks.
6
6
  // See design/11-platform.md and design/25-production-deployments.md.
7
7
 
8
- import { writeFile, readFile, cp, glob } from 'node:fs/promises';
9
- import { join, dirname, basename } from 'node:path';
10
- import type { TimberPlatformAdapter, TimberConfig } from './types.ts';
11
- import { generateCompressModule } from './compress-module.ts';
12
- import { adapterBase, IMMUTABLE_CACHE, generateHeadersFile } from './shared.ts';
8
+ import { cp, glob, readFile, writeFile } from 'node:fs/promises';
9
+ import { basename, dirname, join } from 'node:path';
13
10
  import {
14
- runSharedBuildSteps,
15
11
  buildStaticDirectoryOutput,
16
12
  findUnhashedStaticFiles,
13
+ runSharedBuildSteps,
17
14
  writeHeadersFile,
18
15
  } from './build-output-helper.ts';
19
- import { PRESET_CONFIGS, LOCALLY_PREVIEWABLE, type NitroPreset } from './nitro-presets.ts';
16
+ import { generateCompressModule } from './compress-module.ts';
17
+ import { LOCALLY_PREVIEWABLE, PRESET_CONFIGS, type NitroPreset } from './nitro-presets.ts';
20
18
  import {
21
19
  generatePreviewScript,
22
20
  generateSendResponseModule,
23
21
  spawnNitroPreview,
24
22
  } from './nitro-preview.ts';
23
+ import { IMMUTABLE_CACHE } from './shared.ts';
24
+ import type { TimberConfig, TimberPlatformAdapter } from './types.ts';
25
25
 
26
26
  // NitroPreset is part of the public adapter API (NitroAdapterOptions.preset),
27
27
  // so it stays importable from this module.
@@ -18,6 +18,8 @@ export interface SingleflightOptions {
18
18
 
19
19
  export interface Singleflight {
20
20
  do<T>(key: string, fn: (signal: AbortSignal) => Promise<T>): Promise<T>;
21
+ /** Return the in-flight promise for a key, or undefined if none. */
22
+ get(key: string): Promise<unknown> | undefined;
21
23
  }
22
24
 
23
25
  /**
@@ -80,5 +82,8 @@ export function createSingleflight(opts?: SingleflightOptions): Singleflight {
80
82
  inflight.set(key, tracked);
81
83
  return tracked as Promise<T>;
82
84
  },
85
+ get(key: string): Promise<unknown> | undefined {
86
+ return inflight.get(key);
87
+ },
83
88
  };
84
89
  }