@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,186 +0,0 @@
1
- ---
2
- title: 'Coming from Next.js'
3
- description: 'A practical mapping for Next.js developers — in Next.js you did X, in timber you do Y.'
4
- slug: 'coming-from-nextjs'
5
- ---
6
-
7
- # Coming from Next.js
8
-
9
- This page maps Next.js patterns to timber equivalents. It's not a comparison (see [timber vs Next.js](/docs/timber-vs-nextjs) for that) — it's a migration mental model.
10
-
11
- ## Data Fetching
12
-
13
- **Next.js:** `getServerSideProps`, `getStaticProps`, or `fetch()` with implicit caching.
14
-
15
- **timber:** Async server components. No loaders, no special functions. `fetch()` is never patched or cached — use `timber.cache()` explicitly when you want caching.
16
-
17
- ```tsx title="app/products/page.tsx"
18
- // timber — just an async component
19
- export default async function ProductsPage() {
20
- const products = await db.products.findMany();
21
- return <ProductList products={products} />;
22
- }
23
- ```
24
-
25
- ## `loading.tsx`
26
-
27
- **Next.js:** `loading.tsx` convention inserts a Suspense boundary automatically.
28
-
29
- **timber:** No `loading.tsx`. Place `<Suspense>` explicitly where you want it. This keeps you in control of what streams and what blocks the HTTP status code.
30
-
31
- ```tsx
32
- // timber — explicit Suspense
33
- import { Suspense } from 'react';
34
-
35
- export default async function Page() {
36
- const product = await getProduct(id); // blocks, can affect status code
37
- return (
38
- <div>
39
- <h1>{product.name}</h1>
40
- <Suspense fallback={<Skeleton />}>
41
- <Reviews productId={id} /> {/* streams in */}
42
- </Suspense>
43
- </div>
44
- );
45
- }
46
- ```
47
-
48
- ## Middleware
49
-
50
- **Next.js:** Single global `middleware.ts` at the project root. Matcher patterns select routes.
51
-
52
- **timber:** Per-segment `middleware.ts` files. Place them at the route level that matches the concern. `proxy.ts` for global concerns (CORS, rate limiting, security headers).
53
-
54
- ```
55
- app/
56
- proxy.ts # Global — every request (like Next.js middleware but with next())
57
- dashboard/
58
- middleware.ts # Only runs for /dashboard/*
59
- settings/
60
- middleware.ts # Only runs for /dashboard/settings/*
61
- ```
62
-
63
- ## `useSearchParams`
64
-
65
- **Next.js:** `useSearchParams()` hook returns a `URLSearchParams` object.
66
-
67
- **timber:** Typed search params via `defineSearchParams` in any module you import. Server reads via `.get()`, client reads/writes via `.useQueryStates()`. Segment params are defined globally in `app/schema.ts` via `defineSchema()`, accessed with `getSegmentParams(SEGMENT_PATH)` using the `$segment` module.
68
-
69
- ```ts title="app/products/search-params.ts"
70
- import { defineSearchParams } from '@timber-js/app/search-params';
71
- import { z } from 'zod/v4';
72
-
73
- export const searchParams = defineSearchParams({
74
- page: z.coerce.number().default(1),
75
- sort: z.enum(['price', 'name']).default('name'),
76
- });
77
- ```
78
-
79
- ## Caching
80
-
81
- **Next.js:** Implicit fetch caching, `unstable_cache`, ISR with `revalidate`.
82
-
83
- **timber:** No implicit caching. No ISR. Explicit `cache.data()` with TTL and tags:
84
-
85
- ```ts
86
- import { cache } from '@timber-js/app/cache';
87
-
88
- declare const db: { products: { findPopular(): Promise<{ id: string; name: string }[]> } };
89
-
90
- const getProducts = cache.data(
91
- async () => db.products.findPopular(),
92
- { ttl: 300, tags: ['products'] }
93
- );
94
- ```
95
-
96
- ## Authorization
97
-
98
- **Next.js:** Usually done in middleware or layout components. No built-in access gate pattern.
99
-
100
- **timber:** `access.ts` per segment. Runs inside the React tree with `React.cache` sharing. Supports slot degradation (a denied slot renders `denied.tsx` instead of failing the whole page).
101
-
102
- ```ts title="app/(auth)/access.ts"
103
- import { redirect, getCookieJar } from '@timber-js/app/server';
104
-
105
- declare function getSessionFromCookie(jar: ReturnType<typeof getCookieJar>): { userId: string } | null;
106
-
107
- export default async function access() {
108
- const session = getSessionFromCookie(getCookieJar());
109
- if (!session) redirect('/login');
110
- }
111
- ```
112
-
113
- ## Server Actions
114
-
115
- **Next.js:** Server actions with `'use server'`. `useFormState` / `useFormStatus`.
116
-
117
- **timber:** `createActionClient` for reusable middleware (auth, validation). `useActionState` from `@timber-js/app/client` returns a 4-tuple with auto-derived errors. Forms work without JavaScript via form flash.
118
-
119
- ```ts title="lib/action.ts"
120
- import { createActionClient, ActionError } from '@timber-js/app/server';
121
- import { getUser } from '@/lib/auth';
122
-
123
- export const action = createActionClient({
124
- middleware: async () => {
125
- const user = await getUser();
126
- if (!user) throw new ActionError('UNAUTHORIZED');
127
- return { user };
128
- },
129
- });
130
- ```
131
-
132
- ## `notFound()`
133
-
134
- **Next.js:** `notFound()` function.
135
-
136
- **timber:** `deny(404)` — sends a real HTTP 404 status code:
137
-
138
- ```tsx
139
- import { deny } from '@timber-js/app/server';
140
-
141
- if (!product) deny(404);
142
- ```
143
-
144
- ## `redirect()`
145
-
146
- **Next.js:** `redirect()` from `next/navigation`.
147
-
148
- **timber:** `redirect()` from `@timber-js/app/server`. Same concept, different import:
149
-
150
- ```ts
151
- import { redirect } from '@timber-js/app/server';
152
-
153
- redirect('/login'); // 302 by default
154
- redirect('/login', 301); // Permanent redirect
155
- ```
156
-
157
- Code that imports `redirect()` or `notFound()` from `next/navigation` works unmodified — including calls during client component render, which timber converts into an SPA navigation (`redirect`) or your nearest 404 boundary (`notFound`), matching Next.js behavior.
158
-
159
- ## Metadata
160
-
161
- **Next.js:** `export const metadata` or `export async function generateMetadata()`.
162
-
163
- **timber:** Same pattern — export `metadata` (static) or a `metadata()` function (dynamic) from any page or layout.
164
-
165
- ## Imports
166
-
167
- | Next.js import | timber import |
168
- | ------------------------- | ------------------------------------------ |
169
- | `next/link` | `@timber-js/app/client` (`Link`) |
170
- | `next/navigation` | `@timber-js/app/client` (`useRouter`, etc) |
171
- | `next/headers` | `@timber-js/app/server` (`getHeaders`) |
172
- | `next/server` | `@timber-js/app/server` |
173
- | `unstable_cache` | `@timber-js/app/cache` (`cache`) |
174
-
175
- ## Key Differences Summary
176
-
177
- | Concept | Next.js | timber |
178
- | -------------------- | -------------------------------- | ------------------------------------- |
179
- | Fetch caching | Implicit, patched `fetch` | Explicit `timber.cache()` |
180
- | ISR | Built-in | Not supported (use cache + TTL) |
181
- | `loading.tsx` | Convention | Use explicit `<Suspense>` |
182
- | Middleware | Single global file | Per-segment + global `proxy.ts` |
183
- | Status codes | Often 200 for errors | Real HTTP status codes always |
184
- | Auth | In middleware or components | `access.ts` with slot degradation |
185
- | Search params | Untyped `URLSearchParams` | Typed codecs with validation |
186
- | Forms without JS | Limited support | Full form flash support |
@@ -1,193 +0,0 @@
1
- ---
2
- title: 'Metadata and Fonts'
3
- description: 'Page titles, Open Graph, metadata routes, Google Fonts, and local fonts.'
4
- slug: 'metadata-and-fonts'
5
- ---
6
-
7
- # Metadata and Fonts
8
-
9
- ## Metadata
10
-
11
- Export `metadata` from any page or layout to control `<head>` tags.
12
-
13
- ### Static Metadata
14
-
15
- ```tsx title="app/page.tsx"
16
- import type { Metadata } from '@timber-js/app/server';
17
-
18
- export const metadata: Metadata = {
19
- title: 'Dashboard',
20
- description: 'Your project dashboard',
21
- };
22
- ```
23
-
24
- ### Dynamic Metadata
25
-
26
- Export `metadata` as an async function to compute values at request time:
27
-
28
- ```tsx title="app/products/[id]/page.tsx"
29
- import type { Metadata } from '@timber-js/app/server';
30
- import { SEGMENT_PATH } from './$segment';
31
- import { getSegmentParams } from '@timber-js/app/server';
32
-
33
- declare function getProduct(id: string): Promise<{ name: string; summary: string; imageUrl: string }>;
34
-
35
- export async function metadata(): Promise<Metadata> {
36
- const { id } = getSegmentParams(SEGMENT_PATH);
37
- const product = await getProduct(id);
38
- return {
39
- title: product.name,
40
- description: product.summary,
41
- openGraph: { images: [product.imageUrl] },
42
- };
43
- }
44
- ```
45
-
46
- Dynamic `metadata` runs during the render pass — `React.cache` is active, so data fetched here and in the page component is deduplicated.
47
-
48
- ### Title Templates
49
-
50
- Layouts can define a title template. Pages fill in the `%s` placeholder:
51
-
52
- ```tsx title="app/layout.tsx"
53
- export const metadata: Metadata = {
54
- title: { template: '%s | My App', default: 'My App' },
55
- };
56
- ```
57
-
58
- ### Open Graph & Twitter Cards
59
-
60
- ```tsx
61
- export const metadata: Metadata = {
62
- openGraph: {
63
- title: 'My Product',
64
- description: 'The best product ever.',
65
- images: [{ url: '/og-image.png', width: 1200, height: 630 }],
66
- },
67
- twitter: {
68
- card: 'summary_large_image',
69
- title: 'My Product',
70
- images: ['/og-image.png'],
71
- },
72
- };
73
- ```
74
-
75
- ### Metadata Routes
76
-
77
- Special files in your route tree generate metadata endpoints:
78
-
79
- | File | Output |
80
- | --------------------- | ------------------------- |
81
- | `sitemap.ts` | `/sitemap.xml` |
82
- | `robots.ts` | `/robots.txt` |
83
- | `manifest.ts` | `/manifest.json` |
84
- | `opengraph-image.tsx` | `/opengraph-image.png` + auto `og:image` and `twitter:image` meta tags |
85
- | `favicon.ico` / `favicon.tsx` | `/favicon.ico` |
86
- | `icon.tsx` | `/icon.png` + auto `<link rel="icon">` |
87
-
88
- When `opengraph-image.tsx` exists in a segment, timber automatically emits both `<meta property="og:image">` and `<meta name="twitter:image">` in `<head>` — no need to declare them in your `metadata` export. The auto-linked URL includes a cache-busting hash so social platforms pick up changes on redeploy.
89
-
90
- If you declare `openGraph.images` in your metadata, both auto-linked tags are suppressed. If you declare only `twitter.images`, the auto-linked `twitter:image` is suppressed but `og:image` is still emitted. User-declared metadata always wins.
91
-
92
- `favicon.tsx` lets you generate favicons dynamically (e.g. tenant branding). The URL stays `/favicon.ico` for browser compatibility.
93
-
94
- For OG image generation, use `takumi-js/response` (BYO package — not bundled with timber):
95
-
96
- ```tsx title="app/opengraph-image.tsx"
97
- import { ImageResponse } from 'takumi-js/response';
98
-
99
- export default async function OpenGraphImage() {
100
- return new ImageResponse(
101
- <div style={{ fontSize: 48, background: 'white', width: '100%', height: '100%',
102
- display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
103
- My App
104
- </div>,
105
- { width: 1200, height: 630 }
106
- );
107
- }
108
- ```
109
-
110
- Metadata is always complete before the flush — no client-side injection, no partial `<head>`.
111
-
112
- ---
113
-
114
- ## Fonts
115
-
116
- timber.js downloads, subsets, and self-hosts fonts at build time. No requests to Google at runtime. No layout shift.
117
-
118
- ### Google Fonts
119
-
120
- ```tsx title="app/layout.tsx"
121
- import { Inter, JetBrains_Mono } from '@timber-js/app/fonts/google';
122
-
123
- const inter = Inter({
124
- subsets: ['latin'],
125
- display: 'swap',
126
- variable: '--font-sans',
127
- });
128
-
129
- const jetbrainsMono = JetBrains_Mono({
130
- subsets: ['latin'],
131
- display: 'swap',
132
- variable: '--font-mono',
133
- });
134
-
135
- export default function RootLayout({ children }: { children: React.ReactNode }) {
136
- return (
137
- <html lang="en" className={`${inter.variable} ${jetbrainsMono.variable}`}>
138
- <body className={inter.className}>{children}</body>
139
- </html>
140
- );
141
- }
142
- ```
143
-
144
- ### Local Fonts
145
-
146
- ```tsx
147
- import localFont from '@timber-js/app/fonts/local';
148
-
149
- const myFont = localFont({
150
- src: [
151
- { path: './fonts/MyFont-Regular.woff2', weight: '400' },
152
- { path: './fonts/MyFont-Bold.woff2', weight: '700' },
153
- ],
154
- display: 'swap',
155
- variable: '--font-custom',
156
- });
157
- ```
158
-
159
- ### Static Analysis Constraints
160
-
161
- Font calls look like runtime functions but are a build-time transform, so they must be statically analyzable. The canonical shape is a module-scope `const` (or `export const`) with a literal config object:
162
-
163
- ```tsx
164
- const inter = Inter({ subsets: ['latin'] }); // ✅
165
- export const mono = JetBrains_Mono({ subsets: ['latin'] }); // ✅
166
- ```
167
-
168
- Anything else fails the build with a descriptive error instead of silently breaking at runtime:
169
-
170
- ```tsx
171
- export default Inter({ subsets: ['latin'] }); // ❌ build error
172
- const theme = { font: Inter({ subsets: ['latin'] }) }; // ❌ build error
173
- const cls = Inter({ subsets: ['latin'] }).className; // ❌ build error
174
- import * as fonts from 'next/font/google'; // ❌ build error
175
- ```
176
-
177
- ### CSS Variables
178
-
179
- Use the `variable` option and reference it in your styles:
180
-
181
- ```css title="globals.css"
182
- body { font-family: var(--font-sans), system-ui, sans-serif; }
183
- code { font-family: var(--font-mono); }
184
- ```
185
-
186
- ### Next.js Compatibility
187
-
188
- Imports from `next/font/google` and `next/font/local` are shimmed — existing code works without changes:
189
-
190
- ```tsx
191
- import { Inter } from 'next/font/google';
192
- import localFont from 'next/font/local';
193
- ```
@@ -1,229 +0,0 @@
1
- ---
2
- title: 'MDX'
3
- description: 'Use MDX to write pages in Markdown with embedded React components — server-rendered by default, zero client JS.'
4
- ---
5
-
6
- # MDX
7
-
8
- MDX lets you write pages in Markdown with embedded React components. In timber.js, MDX pages are **server components by default** — they render on the server with zero client JavaScript unless a component explicitly opts in with `'use client'`.
9
-
10
- ## Setup
11
-
12
- Add `'mdx'` to `pageExtensions` in your config:
13
-
14
- ```ts title="timber.config.ts"
15
- export default {
16
- pageExtensions: ['tsx', 'ts', 'jsx', 'js', 'mdx'],
17
- };
18
- ```
19
-
20
- Install the MDX compiler — timber.js uses [Satteri](https://satteri.bruits.org), a Rust-based Markdown/MDX compiler with a native Vite plugin:
21
-
22
- ```bash title="Terminal"
23
- pnpm add -D vite-plugin-satteri satteri
24
- ```
25
-
26
- That's it. Any `page.mdx` file in your `app/` directory is now a route.
27
-
28
- ## Page-Level MDX
29
-
30
- A `page.mdx` works exactly like `page.tsx` — it becomes a route based on its directory:
31
-
32
- ```
33
- app/
34
- docs/
35
- getting-started/
36
- page.mdx # /docs/getting-started
37
- api-reference/
38
- page.mdx # /docs/api-reference
39
- layout.tsx # shared docs layout
40
- ```
41
-
42
- MDX pages support `export const metadata` for page titles and descriptions:
43
-
44
- ````mdx title="app/docs/getting-started/page.mdx"
45
- export const metadata = { title: 'Getting Started' }
46
-
47
- # Getting Started
48
-
49
- Install timber.js:
50
-
51
- ```bash
52
- pnpm add @timber-js/app
53
- ```
54
-
55
- This page is a **server component** — no JavaScript shipped to the browser.
56
- ````
57
-
58
- ## Frontmatter
59
-
60
- Frontmatter parsing is built in — YAML between `---` fences or TOML between `+++` fences. Frontmatter is exported as a single `frontmatter` object:
61
-
62
- ````mdx title="app/blog/hello/page.mdx"
63
- ---
64
- title: Hello World
65
- description: My first post
66
- ---
67
-
68
- # {frontmatter.title}
69
-
70
- {frontmatter.description}
71
- ````
72
-
73
- You can import the frontmatter object from another MDX file:
74
-
75
- ```tsx title="app/blog/layout.tsx"
76
- import { frontmatter } from './page.mdx';
77
-
78
- // frontmatter.title, frontmatter.description, etc.
79
- ```
80
-
81
- For page metadata, use an explicit `export const metadata` — frontmatter alone does not set metadata automatically:
82
-
83
- ````mdx title="app/docs/getting-started/page.mdx"
84
- ---
85
- title: Getting Started
86
- ---
87
-
88
- export const metadata = { title: 'Getting Started' }
89
-
90
- # {frontmatter.title}
91
- ````
92
-
93
- ## Custom Components
94
-
95
- Create `mdx-components.tsx` at the project root to customize how Markdown elements render:
96
-
97
- ```tsx title="mdx-components.tsx"
98
- import type { MDXComponents } from 'mdx/types';
99
-
100
- export function useMDXComponents(components: MDXComponents): MDXComponents {
101
- return {
102
- ...components,
103
- h1: (props) => <h1 className="text-3xl font-bold mt-8 mb-4" {...props} />,
104
- pre: (props) => <pre className="bg-gray-900 text-gray-100 p-4 rounded-lg" {...props} />,
105
- code: (props) => <code className="bg-gray-100 px-1.5 py-0.5 rounded text-sm" {...props} />,
106
- };
107
- }
108
- ```
109
-
110
- timber.js detects this file at startup and applies it to all MDX files automatically. The file can also live in `src/`.
111
-
112
- ## Using Client Components in MDX
113
-
114
- MDX files stay on the server. To add interactivity, import a `'use client'` component:
115
-
116
- ````mdx title="app/docs/getting-started/page.mdx"
117
- import { CopyButton } from '../../components/copy-button'
118
-
119
- # Getting Started
120
-
121
- ```bash
122
- pnpm add @timber-js/app
123
- ```
124
-
125
- <CopyButton text="pnpm add @timber-js/app" />
126
- ````
127
-
128
- Only the `CopyButton` ships JavaScript. The rest of the page renders as static HTML.
129
-
130
- ## Plugins and Features
131
-
132
- GFM (tables, footnotes, strikethrough, task lists) and frontmatter are on by default. Additional syntax is enabled through `features`, and custom transforms are written as Satteri visitor plugins through the `mdx` key in your config:
133
-
134
- ```ts title="timber.config.ts"
135
- import { defineHastPlugin } from 'satteri';
136
-
137
- const externalLinks = defineHastPlugin({
138
- name: 'external-links',
139
- element: {
140
- filter: ['a'],
141
- visit(node, ctx) {
142
- const href = node.properties.href;
143
- if (typeof href === 'string' && href.startsWith('http')) {
144
- ctx.setProperty(node, 'target', '_blank');
145
- }
146
- },
147
- },
148
- });
149
-
150
- export default {
151
- pageExtensions: ['tsx', 'ts', 'jsx', 'js', 'mdx'],
152
- mdx: {
153
- hastPlugins: [externalLinks],
154
- features: { math: true },
155
- },
156
- };
157
- ```
158
-
159
- The `mdx` config maps directly to `vite-plugin-satteri` options. Available fields:
160
-
161
- | Option | Type | Description |
162
- | -------------- | -------------------- | ------------------------------------------------------------------------------- |
163
- | `mdastPlugins` | `MdastPluginInput[]` | Markdown AST visitors (created with `defineMdastPlugin`) |
164
- | `hastPlugins` | `HastPluginInput[]` | HTML AST visitors (created with `defineHastPlugin`) |
165
- | `features` | `Features` | Parser toggles — `gfm`, `frontmatter`, `math`, `directive`, `wikilinks`, … |
166
-
167
- Satteri plugins are filtered visitors, not unified plugins — **remark/rehype plugins do not run** on Satteri's Rust-side AST. Visitors can be async and can replace nodes, which is enough to build things like shiki-based syntax highlighting (shiki transformers such as `@shikijs/twoslash` still work, since they run inside shiki).
168
-
169
- If you need the unified MDX pipeline (for example, CodeHike), bypass timber's MDX support entirely: set `mdx: false` in your config and register `@mdx-js/rollup` yourself in `vite.config.ts` with `enforce: 'pre'`:
170
-
171
- ```ts title="timber.config.ts"
172
- export default {
173
- mdx: false, // disable timber's built-in MDX — bring your own compiler
174
- };
175
- ```
176
-
177
- ## Dynamic MDX Loading
178
-
179
- For content-driven pages like a blog, use `import.meta.glob()` to build a map of MDX modules, then load the right one based on the URL:
180
-
181
- ```tsx title="app/blog/[slug]/page.tsx"
182
- import { allBlogs } from 'content-collections';
183
- import { deny, getSegmentParams } from '@timber-js/app/server';
184
- import { SEGMENT_PATH } from './$segment';
185
-
186
- const mdxModules = import.meta.glob<{ default: React.ComponentType }>(
187
- '../../../content/blog/*.mdx'
188
- );
189
-
190
- export default async function BlogPost() {
191
- const { slug } = getSegmentParams(SEGMENT_PATH);
192
- const post = allBlogs.find((p) => p.slug === slug);
193
- if (!post) deny(404);
194
-
195
- const key = `../../../content/blog/${post._meta.fileName}`;
196
- const loader = mdxModules[key];
197
- if (!loader) deny(404);
198
-
199
- const { default: MdxComponent } = await loader();
200
-
201
- return (
202
- <article>
203
- <h1>{post.title}</h1>
204
- <MdxComponent />
205
- </article>
206
- );
207
- }
208
- ```
209
-
210
- `import.meta.glob()` is resolved at build time by Vite — each `.mdx` file becomes a real ES module in the bundle with no `eval` or `new Function()` at runtime. The glob path is relative to the file that calls it.
211
-
212
- This pattern pairs with [content collections](/docs/content-collections) — the collection provides typed metadata (title, date, tags), while `import.meta.glob()` loads the MDX body as a React component.
213
-
214
- An alternative is `compileMDX()` from `@content-collections/mdx`, which compiles MDX to a serialized string during the content collection transform step. You then render it at runtime with `useMDXComponent(code)`. This avoids glob paths but trades them for a runtime eval of the compiled output. `import.meta.glob()` keeps everything as real ES modules resolved at build time.
215
-
216
- ## Content Collections
217
-
218
- For structured content outside the route tree (blog posts, docs, changelogs), use [content collections](/docs/content-collections). Collections give you typed schemas, slug generation, and build-time validation — while MDX page routes are for pages that live directly in `app/`.
219
-
220
- ## Coming from Next.js
221
-
222
- | Next.js | timber.js |
223
- | ----------------------------------------- | --------------------------------------------- |
224
- | `@next/mdx` wrapper package | Built-in — just add `'mdx'` to pageExtensions |
225
- | `next.config.mjs` `withMDX()` wrapper | `timber.config.ts` `mdx` key |
226
- | MDX pages are client components by default | MDX pages are server components by default |
227
- | unified (remark/rehype) plugins | Satteri visitor plugins + built-in features |
228
- | Custom loader for `.md` files | Built in — `.md` imports export an HTML string |
229
- | `mdx-components.tsx` at project root | Same convention |
@@ -1,90 +0,0 @@
1
- ---
2
- title: 'Content Collections'
3
- description: 'Typed file-based content with Zod schemas, MDX rendering, and virtual module access.'
4
- ---
5
-
6
- # Content Collections
7
-
8
- Content collections let you define typed, file-based content in a `content/` directory. Each collection has a Zod schema for frontmatter validation and a transform for processing (like MDX compilation).
9
-
10
- ## Defining a Collection
11
-
12
- ```ts
13
- // content-collections.ts
14
- import { defineCollection, defineConfig } from '@content-collections/core';
15
- import { compileMDX } from '@content-collections/mdx';
16
- import { z } from 'zod/v4';
17
-
18
- const docs = defineCollection({
19
- name: 'docs',
20
- directory: 'content/docs',
21
- include: '**/*.{mdx,md}',
22
- schema: z.object({
23
- title: z.string(),
24
- description: z.string(),
25
- order: z.number(),
26
- section: z.string().optional(),
27
- }),
28
- transform: async (document, context) => {
29
- const mdx = await compileMDX(context, document);
30
- return { ...document, mdx };
31
- },
32
- });
33
-
34
- export default defineConfig({ collections: [docs] });
35
- ```
36
-
37
- ## Using Collections
38
-
39
- Import the generated collection and query it:
40
-
41
- ```tsx
42
- import { allDocs } from 'content-collections';
43
- import { getSegmentParams, deny } from '@timber-js/app/server';
44
-
45
- declare const allDocs: { title: string; _meta: { fileName: string }; mdx: { default: React.ComponentType } }[];
46
-
47
- export default async function DocsPage() {
48
- const { slug } = getSegmentParams();
49
- const doc = allDocs.find((d) => d._meta.fileName === slug);
50
- if (!doc) deny(404);
51
-
52
- return (
53
- <article className="prose">
54
- <h1>{doc.title}</h1>
55
- <doc.mdx.default />
56
- </article>
57
- );
58
- }
59
- ```
60
-
61
- ## Content Directory Structure
62
-
63
- ```tsx
64
- content / docs / v1 / getting - started.mdx;
65
- routing.mdx;
66
- blog / hello - world.mdx;
67
- ```
68
-
69
- Each file's `_meta` includes `path`, `fileName`, and `directory` — useful for filtering by version, building sidebar navigation, or generating static params.
70
-
71
- ## MDX Components
72
-
73
- Customize MDX rendering with `mdx-components.tsx`:
74
-
75
- ```tsx
76
- // mdx-components.tsx
77
- import type { MDXComponents } from 'mdx/types';
78
-
79
- export function useMDXComponents(components: MDXComponents): MDXComponents {
80
- return {
81
- ...components,
82
- h1: ({ children }) => <h1 className="text-3xl font-bold">{children}</h1>,
83
- code: ({ children }) => <code className="bg-gray-100 rounded px-1">{children}</code>,
84
- };
85
- }
86
- ```
87
-
88
- ## Type Generation
89
-
90
- Content collections generate TypeScript types at build time. Every document is fully typed — frontmatter fields, computed properties from transforms, and `_meta` information.