@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,214 +0,0 @@
1
- ---
2
- title: 'Instrumentation & Tracing'
3
- description: 'Server instrumentation, OTEL tracing, custom logging, and Server-Timing headers.'
4
- slug: 'instrumentation'
5
- ---
6
-
7
- # Instrumentation & Tracing
8
-
9
- timber.js provides structured observability out of the box: per-request trace IDs, OTEL span integration, pluggable logging, and Server-Timing headers.
10
-
11
- ## `instrumentation.ts`
12
-
13
- Create an `instrumentation.ts` file at your project root. It runs once at server startup, before the first request is handled.
14
-
15
- ```ts
16
- // instrumentation.ts
17
-
18
- // Called once at startup. Initialize your OTEL SDK, database pools, etc.
19
- export async function register() {
20
- // e.g. initialize OpenTelemetry
21
- const { NodeSDK } = await import('@opentelemetry/sdk-node');
22
- const sdk = new NodeSDK({
23
- /* ... */
24
- });
25
- sdk.start();
26
- }
27
-
28
- // Called on every unhandled server error.
29
- export function onRequestError(error, request, context) {
30
- // Send to Sentry, Datadog, etc.
31
- console.error(`[${context.phase}] ${request.method} ${request.path}`, error);
32
- }
33
-
34
- // Optional: replace the default logger.
35
- export { logger } from './lib/logger';
36
- ```
37
-
38
- ### `register()`
39
-
40
- Called once before the server starts accepting requests. Use it to:
41
-
42
- - Initialize an OpenTelemetry SDK
43
- - Set up database connection pools
44
- - Configure external services
45
-
46
- The server blocks until `register()` resolves.
47
-
48
- ### `onRequestError(error, request, context)`
49
-
50
- Called for every unhandled error in the server pipeline. The handler receives:
51
-
52
- | Parameter | Type | Description |
53
- | ------------------- | ------------------------ | ----------------------------------------------------- |
54
- | `error` | `unknown` | The thrown error |
55
- | `request.method` | `string` | HTTP method (`'GET'`, `'POST'`, etc.) |
56
- | `request.path` | `string` | Request path (`'/dashboard/projects/123'`) |
57
- | `request.headers` | `Record<string, string>` | Request headers |
58
- | `context.phase` | `string` | Pipeline phase: `'proxy'`, `'handler'`, or `'render'` |
59
- | `context.routePath` | `string` | Request pathname (`'/dashboard/projects/123'`) |
60
- | `context.routeType` | `string` | Currently always `'page'` |
61
- | `context.traceId` | `string` | 32-char hex trace ID for correlation |
62
-
63
- The handler must not affect the response. Errors thrown by the handler are caught and logged.
64
-
65
- ### `logger`
66
-
67
- Export a logger object to replace the default logger:
68
-
69
- ```ts
70
- // instrumentation.ts
71
- import pino from 'pino';
72
-
73
- export const logger = pino({ level: 'info' });
74
- ```
75
-
76
- Any object with `info`, `warn`, `error`, and `debug` methods works:
77
-
78
- ```ts
79
- interface TimberLogger {
80
- info(msg: string, data?: Record<string, unknown>): void;
81
- warn(msg: string, data?: Record<string, unknown>): void;
82
- error(msg: string, data?: Record<string, unknown>): void;
83
- debug(msg: string, data?: Record<string, unknown>): void;
84
- }
85
- ```
86
-
87
- The default logger writes human-readable lines to stderr with automatic trace ID injection.
88
-
89
- ## Tracing
90
-
91
- ### `getTraceId()`
92
-
93
- Returns the current request's trace ID — always a 32-char lowercase hex string. Available in middleware, access checks, server components, and server actions.
94
-
95
- ```ts
96
- import { getTraceId } from '@timber-js/app/server';
97
-
98
- const id = getTraceId(); // "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
99
- ```
100
-
101
- - **With OTEL**: returns the real OTEL trace ID (matches Jaeger, Honeycomb, Datadog)
102
- - **Without OTEL**: returns a `crypto.randomUUID()`-derived fallback in the same format
103
-
104
- Use `getTraceId()` to correlate logs, errors, and external service calls for a single request.
105
-
106
- ### `getSpanId()`
107
-
108
- Returns the current OTEL span ID if available, `undefined` otherwise.
109
-
110
- ```ts
111
- import { getSpanId } from '@timber-js/app/server';
112
-
113
- const sid = getSpanId(); // "1234567890abcdef" or undefined
114
- ```
115
-
116
- ### `withSpan(name, attributes, fn)`
117
-
118
- Run a function within a span. Emits an OTEL span when an SDK is active, and a native platform span on Cloudflare (see [Cloudflare Native Traces](#cloudflare-native-traces)). With neither, the function runs directly with zero overhead.
119
-
120
- ```ts
121
- import { withSpan } from '@timber-js/app/server';
122
-
123
- declare const userId: string;
124
- declare const db: { users: { findUnique(opts: { where: { id: string } }): Promise<{ id: string; name: string }> } };
125
-
126
- const user = await withSpan('db.getUser', { userId }, async () => {
127
- return db.users.findUnique({ where: { id: userId } });
128
- });
129
- ```
130
-
131
- The span automatically:
132
-
133
- - Creates as a child of the current active span
134
- - Records exceptions on error
135
- - Ends when the function completes
136
-
137
- ### `addSpanEvent(name, attributes?)`
138
-
139
- Add an event to the current active span. Used for recording cache hits/misses, checkpoints, etc.
140
-
141
- ```ts
142
- import { addSpanEvent } from '@timber-js/app/server';
143
-
144
- await addSpanEvent('cache.hit', { key: 'user:123' });
145
- ```
146
-
147
- ## Cloudflare Native Traces
148
-
149
- When you deploy with the Cloudflare adapter, timber's pipeline spans (`timber.proxy`, `timber.middleware`, `timber.access`, `timber.render`, `timber.action`, …) appear **natively in the Cloudflare Observability dashboard** — no OTEL SDK required. They nest alongside Cloudflare's auto-instrumented D1, KV, and `fetch` spans with the same attributes timber emits over OTEL (`http.request.method`, `url.path`, `http.route`, `http.response.status_code`, `timber.result`, …).
150
-
151
- This works out of the box:
152
-
153
- - The generated `_worker.js` registers timber's spans with the Workers custom spans API (`tracing.enterSpan()` from `cloudflare:workers`).
154
- - The generated `wrangler.jsonc` enables the trace destination (`observability.traces.enabled`).
155
-
156
- To turn native traces off, use the `wrangler` escape hatch:
157
-
158
- ```ts
159
- // timber.config.ts
160
- import { cloudflare } from '@timber-js/app/adapters/cloudflare';
161
-
162
- export default {
163
- adapter: cloudflare({
164
- wrangler: { observability: { traces: { enabled: false } } },
165
- }),
166
- };
167
- ```
168
-
169
- Native emission is additive — if you also initialize an OTEL SDK in `register()`, spans are emitted on both channels, so external collectors (Honeycomb, Jaeger, Datadog) keep working. `timber.cache` HIT/MISS **span events** are OTEL-only; the Workers span API has no event equivalent.
170
-
171
- On runtimes without the custom spans API (older compatibility dates or an outdated local `wrangler`), the worker silently falls back to OTEL-only emission.
172
-
173
- ## Server-Timing
174
-
175
- timber.js can emit `Server-Timing` headers for browser DevTools performance inspection.
176
-
177
- ```ts
178
- // timber.config.ts
179
- export default {
180
- serverTiming: 'detailed', // 'detailed' | 'total' | false
181
- };
182
- ```
183
-
184
- | Value | Description |
185
- | ------------ | -------------------------------------------------------------------------- |
186
- | `'detailed'` | Per-phase timing (proxy, middleware, access, render). Default in dev mode. |
187
- | `'total'` | Total request duration only. Default in production. |
188
- | `false` | No Server-Timing header. |
189
-
190
- View the timings in Chrome DevTools under Network > Timing.
191
-
192
- ## Log–Trace Correlation
193
-
194
- All framework log messages automatically include `trace_id` and `span_id` (when OTEL is active). Custom loggers receive these in the `data` parameter:
195
-
196
- ```json
197
- {
198
- "msg": "request completed",
199
- "method": "GET",
200
- "path": "/dashboard",
201
- "status": 200,
202
- "durationMs": 42,
203
- "trace_id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
204
- "span_id": "1234567890abcdef"
205
- }
206
- ```
207
-
208
- This enables filtering logs by trace ID in your logging backend to see all events for a single request.
209
-
210
- ## Dev Mode
211
-
212
- In development, timber automatically initializes a minimal OTEL SDK with a `DevSpanProcessor` that outputs span information to the dev log. No configuration needed — just run `pnpm dev`.
213
-
214
- In production, OTEL spans are only emitted when you initialize an SDK in `register()`.
@@ -1,129 +0,0 @@
1
- ---
2
- title: 'Security'
3
- description: 'Built-in CSRF protection, action encryption, redirect safety, and header immutability.'
4
- slug: 'security'
5
- ---
6
-
7
- # Security
8
-
9
- timber.js ships with several security protections enabled by default. You don't need to configure most of them — they're structural, built into how the framework handles requests.
10
-
11
- ## CSRF Protection
12
-
13
- All server actions validate the `Origin` header automatically. The `Origin` is compared against the request's full origin (scheme + host + port) — not just the hostname. Behind a reverse proxy, the scheme is derived from `X-Forwarded-Proto`. If the origin doesn't match, the action is rejected with 403. No configuration required.
14
-
15
- This protects against cross-site request forgery attacks where a malicious page submits a form to your server. The protection works for both JavaScript-enhanced actions and plain HTML form submissions.
16
-
17
- ## Server Action Encryption
18
-
19
- When a server action captures variables from its closure (bound args), those values are serialized into the RSC payload sent to the client. timber encrypts them with AES-256-GCM before they leave the server.
20
-
21
- ```tsx
22
- import { SEGMENT_PATH } from './$segment';
23
- import { getSegmentParams } from '@timber-js/app/server';
24
-
25
- declare const db: {
26
- products: { find(id: string): Promise<{ id: string; name: string }> };
27
- cart: { add(productId: string): Promise<void> };
28
- };
29
- declare function AddToCartButton(props: { action: () => Promise<void> }): React.ReactElement;
30
-
31
- export default async function ProductPage() {
32
- const { id } = getSegmentParams(SEGMENT_PATH);
33
- const product = await db.products.find(id);
34
-
35
- async function addToCart() {
36
- 'use server';
37
- // `product.id` is a bound arg — encrypted in the client payload
38
- await db.cart.add(product.id);
39
- }
40
-
41
- return <AddToCartButton action={addToCart} />;
42
- }
43
- ```
44
-
45
- The client receives an opaque, encrypted blob. It can't read or tamper with the bound args — the GCM authentication tag ensures integrity.
46
-
47
- ### Encryption Key for Multi-Deploy
48
-
49
- By default, timber generates a random encryption key per build. This means server actions created by one build can't be decrypted by another. For rolling or blue-green deployments where two builds serve traffic simultaneously, set a shared key:
50
-
51
- ```bash
52
- TIMBER_ACTIONS_ENCRYPTION_KEY=<base64-encoded-32-byte-key>
53
- ```
54
-
55
- Generate a key:
56
-
57
- ```bash
58
- node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
59
- ```
60
-
61
- ## Redirect Safety
62
-
63
- Open redirect vulnerabilities let an attacker craft a URL on your domain that redirects users to a malicious site (e.g., `/login?next=https://evil.com`). timber prevents this structurally — `redirect()` cannot produce an external redirect.
64
-
65
- ### `redirect()` is relative-only
66
-
67
- The `redirect()` function only accepts relative paths. Absolute URLs and protocol-relative URLs are rejected:
68
-
69
- ```ts
70
- import { redirect } from '@timber-js/app/server';
71
-
72
- redirect('/dashboard'); // ✅ relative path
73
- redirect('https://evil.com'); // ❌ throws — absolute URL
74
- redirect('//evil.com'); // ❌ throws — protocol-relative
75
- ```
76
-
77
- This applies everywhere — middleware, access checks, server actions, and server components. There is no code path where `redirect()` can send users off-site.
78
-
79
- ### `redirectExternal()` with allow-list
80
-
81
- When you legitimately need to redirect to an external URL (e.g., an OAuth provider), use `redirectExternal()` with an explicit origin allow-list. Only `http:` and `https:` schemes are permitted:
82
-
83
- ```ts
84
- import { redirectExternal } from '@timber-js/app/server';
85
-
86
- redirectExternal('https://auth.example.com/login', ['https://auth.example.com']);
87
-
88
- // With a custom status code
89
- redirectExternal('https://docs.example.com/guide', ['https://docs.example.com'], 301);
90
- ```
91
-
92
- If the target origin isn't in the allow-list, the scheme isn't http(s), or the URL is invalid, the call throws. This gives you external redirects without the open redirect risk.
93
-
94
- <Callout variant="tip" title="vs. Next.js">
95
- Next.js `redirect()` accepts absolute URLs by default. In timber, external redirects require an explicit allow-list — open redirect bugs are structurally impossible.
96
- </Callout>
97
-
98
- ## Header Immutability
99
-
100
- Request headers are immutable after the request enters the pipeline:
101
-
102
- - `getHeaders()` returns a frozen, read-only `Headers` object — calling `.set()` or `.delete()` throws
103
- - Middleware can add headers via `ctx.requestHeaders` (an additive overlay), but cannot delete or modify the original request headers
104
- - The original `Request` object is never mutated
105
-
106
- This prevents a class of middleware bypass attacks where a malicious or buggy middleware could delete authentication headers before they reach access checks.
107
-
108
- ## Link Safety
109
-
110
- The `<Link>` component rejects dangerous URL schemes. Links with `javascript:`, `data:`, or `vbscript:` protocols are blocked at render time, preventing XSS via URL injection.
111
-
112
- ## Error Information
113
-
114
- Unexpected server errors return `{ code: 'INTERNAL_ERROR' }` to the client with no stack trace or internal details. To send structured error data across the boundary, use `deny()` with explicit opt-in:
115
-
116
- ```ts
117
- import { deny } from '@timber-js/app/server';
118
-
119
- // Only the data you explicitly pass crosses the boundary
120
- deny({ status: 404, data: { productId: id } });
121
- ```
122
-
123
- In development, error details are shown in the browser error overlay. In production, they're suppressed.
124
-
125
- ## What's Next
126
-
127
- - [Authorization](/docs/access-control) — per-segment access control with `access.ts`
128
- - [Forms & Server Actions](/docs/forms-and-actions) — action clients, validation, and progressive enhancement
129
- - [Error Handling](/docs/error-handling) — status codes, deny(), and error boundaries
@@ -1,134 +0,0 @@
1
- ---
2
- title: 'Developer Experience'
3
- description: 'Dev logging, hydration diffs, Server-Timing headers, build reports, and more.'
4
- slug: 'developer-experience'
5
- ---
6
-
7
- # Developer Experience
8
-
9
- timber.js includes a suite of development tools that surface what your app is doing — where time is spent, what's cached, where errors come from — without reaching for external profiling tools.
10
-
11
- ## Dev Logging
12
-
13
- Every request in development emits a structured tree to `stderr` showing the full execution pipeline with timing:
14
-
15
- ```
16
- POST /dashboard/projects/123 trace_id: 4bf92f3577b34da6a3ce929d0e0e4736
17
- ├─ [proxy] proxy.ts 0ms → 2ms
18
- ├─ [rsc] middleware.ts 2ms → 4ms
19
- │ ├── fired: requireUser() (timber.cache prefetch)
20
- │ ├── fired: getProject("123") (timber.cache prefetch)
21
- │ └── fired: getTaskCounts("123") (timber.cache prefetch)
22
- ├─ [rsc] render 4ms
23
- │ ├─ [rsc] AccessGate (authenticated) 4ms → 5ms
24
- │ │ └── requireUser() timber.cache HIT <1ms
25
- │ ├─ [rsc] ProjectPage 8ms → 12ms
26
- │ │ └── getTaskCounts("123") timber.cache HIT <1ms
27
- │ └── onShellReady 12ms
28
- ├─ [ssr] hydration render 13ms → 18ms
29
- └─ ✓ 200 OK total 18ms
30
- ```
31
-
32
- The tree mirrors the execution structure. `[rsc]`/`[ssr]`/`[client]` labels show which Vite environment each phase runs in. Cache hits and misses are annotated inline.
33
-
34
- ### Fetch Instrumentation
35
-
36
- `fetch()` calls from server components appear as children of the component that made them:
37
-
38
- ```
39
- ├─ [rsc] page / 6ms → 101ms
40
- │ ├─ fetch GET https://api.example.com/products 12ms → 89ms (77ms)
41
- │ └─ fetch GET https://api.example.com/user 12ms → 45ms (33ms) [cdn: HIT]
42
- ```
43
-
44
- Start times reveal whether fetches ran concurrently or sequentially. Cache status from `X-Cache` or `CF-Cache-Status` headers is surfaced automatically. Fetch instrumentation is dev-only — `globalThis.fetch` is not patched in production.
45
-
46
- ### Log Modes
47
-
48
- Control the verbosity with `TIMBER_DEV_LOG`:
49
-
50
- | Mode | Output |
51
- | --------- | ---------------------------------------------------------- |
52
- | `tree` | Full indented tree per request (default) |
53
- | `verbose` | Detailed tree showing every component render |
54
- | `summary` | One line per request: `POST /path → 200 OK 18ms` |
55
- | `json` | Chronological NDJSON of all spans with full attributes |
56
-
57
- Suppress all dev logging with `TIMBER_DEV_QUIET=1`.
58
-
59
- ### Slow Phase Warnings
60
-
61
- Phases that exceed a configurable threshold are highlighted in the tree output. The default is 200ms:
62
-
63
- ```ts title="timber.config.ts"
64
- export default {
65
- dev: {
66
- slowPhaseMs: 200,
67
- },
68
- };
69
- ```
70
-
71
- This surfaces performance bottlenecks during development without requiring explicit profiling.
72
-
73
- ## Server-Timing Header
74
-
75
- timber.js emits a `Server-Timing` HTTP header for every response. The level of detail is configurable:
76
-
77
- | Value | Output | Default in |
78
- | ------------ | -------------------------------------------------------- | ----------- |
79
- | `'detailed'` | Per-phase breakdown: proxy, middleware, render, SSR | Development |
80
- | `'total'` | Single `total;dur=N` entry | Production |
81
- | `false` | No header | — |
82
-
83
- ```ts title="timber.config.ts"
84
- export default {
85
- serverTiming: 'detailed',
86
- };
87
- ```
88
-
89
- In production, you can opt into `'detailed'` for APM integration — browser DevTools and monitoring tools parse `Server-Timing` automatically.
90
-
91
- ## Hydration Mismatch Diff
92
-
93
- When React 19 detects a hydration mismatch, timber parses the diff and renders it with color-coded lines:
94
-
95
- - Green (`+`) lines show the client-side markup
96
- - Red (`-`) lines show the server-side markup
97
-
98
- This replaces Vite's plain-text rendering of hydration errors, making mismatches easy to spot and fix. Multiple hydration errors accumulate in the same overlay.
99
-
100
- ## Error Overlay
101
-
102
- Errors during development are shown in a browser overlay with:
103
-
104
- - **Component stacks** — the React component hierarchy leading to the error
105
- - **Phase labels** — whether the error occurred in middleware, access check, RSC render, SSR, or a client component
106
- - **Source-mapped stack traces** — pointing to your original source files
107
-
108
- Client-side errors are forwarded to the overlay via Vite's HMR channel, so they get the same treatment as server errors.
109
-
110
- ## Compiling Indicator
111
-
112
- A small "Compiling…" indicator appears in the bottom-left corner during HMR updates. It has a 200ms debounce — fast updates never show it. It fades out when the update completes.
113
-
114
- ## Build Report
115
-
116
- After `timber build`, a route table shows every route with its bundle size, route type, and first-load JS:
117
-
118
- | Symbol | Meaning |
119
- | ------ | -------------- |
120
- | ○ | Static page |
121
- | λ | Dynamic (SSR) |
122
- | ƒ | API route |
123
-
124
- This helps identify large bundles and verify route classification.
125
-
126
- ## HTTPS / HTTP-2 Dev Server
127
-
128
- timber supports `server.https` in your Vite config for local HTTPS development. The request scheme is derived from the TLS socket (not hardcoded), so CSRF protection works correctly under HTTPS. HTTP/2 pseudo-headers are handled automatically.
129
-
130
- ## What's Next
131
-
132
- - [Instrumentation](/docs/instrumentation) — production tracing with OpenTelemetry
133
- - [Configuration](/docs/configuration) — all config options
134
- - [Streaming](/docs/streaming) — Suspense placement and `deferSuspenseFor`
@@ -1,50 +0,0 @@
1
- ---
2
- title: 'Why timber.js?'
3
- description: 'The design decisions behind timber.js and why they matter.'
4
- slug: 'why-timber'
5
- ---
6
-
7
- # Why timber.js?
8
-
9
- timber.js exists because we think the current generation of React frameworks made a wrong turn on streaming.
10
-
11
- ## The Premise
12
-
13
- Most React frameworks stream HTML as fast as possible. The moment the server starts rendering, bytes go to the browser. This sounds great in theory — faster Time to First Byte, progressive rendering, the user sees _something_ sooner.
14
-
15
- The cost is real:
16
-
17
- - **Every page returns HTTP 200.** A 404? That's a 200 with an error boundary. A redirect? That's a 200 with client-side navigation. A 500? Also 200. Once you start streaming, you've committed the status code.
18
- - **Pages don't work without JavaScript.** The client needs JS to resolve suspense boundaries, handle error states, and execute redirects that the server couldn't express through HTTP.
19
- - **Loading states everywhere.** `loading.tsx` exists because the framework sends the shell before data is ready. Now you're designing skeleton states for every route, managing layout shift, and adding perceived complexity.
20
-
21
- ## What timber.js Does Differently
22
-
23
- timber.js holds the response until the shell is ready. That's the content outside `<Suspense>` boundaries — the stuff that actually determines the page's HTTP status, headers, and primary content.
24
-
25
- The trade-off is roughly 20ms of server-side buffering before the first byte. In exchange:
26
-
27
- - **Real status codes.** `deny(404)` sends a genuine HTTP 404. CDNs cache it correctly. Search engines deindex it. `curl` sees it. APM tools report it.
28
- - **Pages work without JavaScript.** The initial render arrives complete. No JS needed to show the primary content.
29
- - **No implicit loading states.** There's no `loading.tsx`. If you want a loading state, you place a `<Suspense>` boundary explicitly around the slow content — and you choose where.
30
-
31
- Secondary content (reviews on a product page, comments on a post) can still stream via `<Suspense>`. You decide where the flush boundary sits. The framework doesn't decide for you.
32
-
33
- ## What timber.js is Not
34
-
35
- This is not a framework for every use case. It's opinionated about a few things:
36
-
37
- - **Server-first rendering.** If you're building a single-page app with no server, this isn't the right tool.
38
- - **Explicit over implicit.** There's no magic caching, no implicit data fetching, no hidden loading states. You opt into each behavior.
39
- - **Smaller API surface.** We'd rather have fewer features that work correctly than many features with edge cases.
40
-
41
- Next.js is more mature, more battle-tested, and supports more use cases. If you're happy with it, there's no reason to switch. timber.js is for people who've run into the limitations of streaming-first and want HTTP semantics that work.
42
-
43
- ## Built on Vite
44
-
45
- timber.js is a Vite plugin, not a standalone build system. You get Vite's ecosystem: sub-second HMR, the plugin ecosystem, native ESM, and Rolldown for production builds. Your `vite.config.ts` stays normal — timber.js adds to it, it doesn't replace it.
46
-
47
- ## What's Next
48
-
49
- - [Getting Started](/docs/quick-start) — create a project in under five minutes
50
- - [timber.js vs Next.js](/docs/timber-vs-nextjs) — an honest comparison
@@ -1,81 +0,0 @@
1
- ---
2
- title: 'timber.js vs Next.js'
3
- description: 'An honest comparison of timber.js and Next.js — where they differ and why.'
4
- slug: 'timber-vs-nextjs'
5
- ---
6
-
7
- # timber.js vs Next.js
8
-
9
- Next.js is the most widely used React framework. It's mature, well-documented, and backed by Vercel. timber.js would not exist without it — the app directory routing model, server components, and many API patterns are directly inspired by Next.js.
10
-
11
- That said, we made different design decisions in a few areas. Here's an honest comparison.
12
-
13
- ## HTTP Semantics
14
-
15
- | Behavior | Next.js | timber.js |
16
- | ------------------ | ------------------------------------------------ | ------------------------------------------------ |
17
- | Status codes | Always 200 (streaming commits early) | Real status codes (flush held until shell ready) |
18
- | 404 pages | 200 + `not-found.tsx` error boundary | HTTP 404 + `404.tsx` |
19
- | Redirects in pages | Client-side via `redirect()` after 200 | HTTP 302/301 before any bytes sent |
20
- | Works without JS | Partially (primary content yes, interactions no) | Fully (forms, navigation, status codes all work) |
21
-
22
- This is the core philosophical difference. Next.js optimizes for earliest possible TTFB. timber.js optimizes for correct HTTP responses, at the cost of ~20ms additional server buffering.
23
-
24
- ## Streaming
25
-
26
- Both frameworks support React Suspense streaming. The difference is when it starts:
27
-
28
- - **Next.js:** Streams immediately. `loading.tsx` renders while data loads. Status code is already committed.
29
- - **timber.js:** Holds until the shell is ready (everything outside `<Suspense>`). Then streams Suspense content. No `loading.tsx` — `<Suspense>` is opt-in only.
30
-
31
- ## Routing
32
-
33
- Both use file-system routing in an `app/` directory. The models are similar:
34
-
35
- - Layouts, pages, dynamic segments, catch-all routes — same patterns
36
- - Parallel routes (slots) — both support `@sidebar`, `@modal` patterns
37
- - Route groups — both support `(group)` directories
38
-
39
- **Differences:**
40
-
41
- - **Middleware:** Next.js has a single global `middleware.ts`. timber.js has per-segment `middleware.ts` files.
42
- - **Authorization:** timber.js has `access.ts` files for per-segment access control with `deny()`. Next.js uses middleware or in-component checks.
43
- - **Typed routes:** timber.js generates route types at build time — `<Link>` type-checks `href` and params.
44
-
45
- ## Caching
46
-
47
- | Behavior | Next.js | timber.js |
48
- | ----------------- | ---------------------------------------------------- | ----------------------------------------------------- |
49
- | `fetch()` caching | Cached by default (opt out with `cache: 'no-store'`) | Never cached (explicit is better) |
50
- | Cache API | `unstable_cache` / `'use cache'` | `timber.cache()` with TTL, tags, staleWhileRevalidate |
51
- | Revalidation | `revalidateTag()`, `revalidatePath()` | `revalidateTag()` |
52
-
53
- timber.js takes the position that implicit caching causes more bugs than it prevents. Nothing is cached unless you wrap it in `timber.cache()`.
54
-
55
- ## Build System
56
-
57
- | | Next.js | timber.js |
58
- | ---------------- | ------------------- | ------------------------------------- |
59
- | Bundler | Turbopack / Webpack | Vite 7 (Rolldown) |
60
- | Config | `next.config.js` | `vite.config.ts` + `timber.config.ts` |
61
- | Plugin ecosystem | Next.js-specific | Full Vite plugin ecosystem |
62
-
63
- ## Platform Support
64
-
65
- Next.js is optimized for Vercel and works on other platforms through community adapters. timber.js ships a first-party Cloudflare Workers adapter and a Nitro adapter that covers Node.js, Vercel, Bun, Netlify, AWS Lambda, and more.
66
-
67
- ## When to Use Next.js
68
-
69
- - You need the largest ecosystem and community support
70
- - You're deploying to Vercel
71
- - You need features timber.js hasn't built yet (image optimization, ISR, etc.)
72
- - Your team is already productive with Next.js
73
-
74
- ## When to Use timber.js
75
-
76
- - You care about correct HTTP status codes and headers
77
- - You want pages that work fully without JavaScript
78
- - You're deploying to Cloudflare Workers
79
- - You prefer explicit caching over implicit
80
- - You want per-segment middleware and authorization
81
- - You want Vite's build speed and plugin ecosystem
@@ -1,68 +0,0 @@
1
- ---
2
- title: 'timber.js vs Other Frameworks'
3
- description: 'How timber.js compares to Vinext, Remix, and other React server frameworks.'
4
- slug: 'timber-vs-others'
5
- ---
6
-
7
- # timber.js vs Other Frameworks
8
-
9
- Beyond Next.js, there are several React frameworks worth comparing against. Each makes different trade-offs.
10
-
11
- ## Vinext (Cloudflare)
12
-
13
- Vinext is Cloudflare's implementation of Next.js on Vite. It aims for Next.js API compatibility running natively on Cloudflare Workers.
14
-
15
- **Shared ground:** Both timber.js and Vinext target Cloudflare Workers, use Vite, and support React Server Components.
16
-
17
- **Key differences:**
18
-
19
- | | Vinext | timber.js |
20
- | ------------ | -------------------------------------- | ---------------------------------------- |
21
- | Goal | Next.js compatibility on Vite | Independent design, different trade-offs |
22
- | Streaming | Streams immediately (Next.js behavior) | Holds until shell ready |
23
- | Status codes | 200 for everything (Next.js behavior) | Real HTTP status codes |
24
- | API surface | Next.js-compatible | Similar but divergent where we disagree |
25
- | Caching | Next.js caching model | Explicit-only caching |
26
-
27
- If you want Next.js on Cloudflare with minimal code changes, Vinext is the right choice. If you want a different rendering model with correct HTTP semantics, that's timber.js.
28
-
29
- ## Remix / React Router
30
-
31
- Remix (now React Router v7) pioneered many of the ideas timber.js agrees with: progressive enhancement, forms that work without JavaScript, and server-first rendering.
32
-
33
- **Shared philosophy:**
34
-
35
- - Forms should work without JS
36
- - Server rendering is the default
37
- - Progressive enhancement over client-side-first
38
-
39
- **Key differences:**
40
-
41
- | | Remix / React Router | timber.js |
42
- | ------------- | ------------------------------------ | ---------------------------------------- |
43
- | Routing model | Flat route config or file convention | Nested `app/` directory (Next.js-style) |
44
- | Data loading | `loader` / `action` functions | Async server components + server actions |
45
- | RSC support | Not yet (planned) | Built on RSC from day one |
46
- | Streaming | Supports `defer()` for streaming | `<Suspense>` with explicit flush point |
47
- | Build system | Vite (React Router v7) | Vite |
48
-
49
- Remix's `loader`/`action` model is elegant and well-understood. timber.js bets on RSC as the data loading primitive — your components are your data layer.
50
-
51
- ## TanStack Start
52
-
53
- TanStack Start is a full-stack React framework from the TanStack Router team.
54
-
55
- **Shared ground:** Both use Vite, both aim for good TypeScript support, both support server-side rendering.
56
-
57
- **Key differences:**
58
-
59
- | | TanStack Start | timber.js |
60
- | -------------- | --------------------------------------- | ------------------------------------------ |
61
- | Routing | TanStack Router (code-based, type-safe) | File-system routing with generated types |
62
- | Data loading | TanStack Query integration | Async server components + `timber.cache()` |
63
- | RSC support | Experimental | Core architecture |
64
- | Platform focus | General-purpose | Cloudflare-first, works everywhere |
65
-
66
- ## Summary
67
-
68
- Every framework makes trade-offs. timber.js trades streaming speed for HTTP correctness and trades API surface size for explicitness. If those trade-offs align with how you think about web development, it's worth trying. If they don't, the frameworks above are all excellent choices.