@timber-js/app 0.2.0-alpha.195 → 0.2.0-alpha.197

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 (285) 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-bE3H5Bjr.js} +3 -3
  11. package/dist/_chunks/{cli-check-BVthpfLS.js.map → cli-check-bE3H5Bjr.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-n3RJLgww.js} +2 -2
  17. package/dist/_chunks/{convention-lint-DO10_pVl.js.map → convention-lint-n3RJLgww.js.map} +1 -1
  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 +139 -36
  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/index.js +2 -2
  130. package/dist/routing/interception-overlap.d.ts +35 -0
  131. package/dist/routing/interception-overlap.d.ts.map +1 -0
  132. package/dist/routing/interception.d.ts.map +1 -1
  133. package/dist/rsc-runtime/ssr.d.ts +3 -1
  134. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  135. package/dist/server/als-registry.d.ts +6 -0
  136. package/dist/server/als-registry.d.ts.map +1 -1
  137. package/dist/server/csp-nonce.d.ts +45 -0
  138. package/dist/server/csp-nonce.d.ts.map +1 -0
  139. package/dist/server/default-status-page.d.ts.map +1 -1
  140. package/dist/server/deny-renderer.d.ts.map +1 -1
  141. package/dist/server/flight-scripts.d.ts +5 -2
  142. package/dist/server/flight-scripts.d.ts.map +1 -1
  143. package/dist/server/html-injector-core.d.ts +17 -2
  144. package/dist/server/html-injector-core.d.ts.map +1 -1
  145. package/dist/server/html-injectors.d.ts +3 -2
  146. package/dist/server/html-injectors.d.ts.map +1 -1
  147. package/dist/server/index.js +2 -2
  148. package/dist/server/internal.js +86 -37
  149. package/dist/server/internal.js.map +1 -1
  150. package/dist/server/metadata-render.d.ts.map +1 -1
  151. package/dist/server/node-stream-transforms.d.ts +3 -17
  152. package/dist/server/node-stream-transforms.d.ts.map +1 -1
  153. package/dist/server/nuqs-ssr-provider.d.ts +7 -3
  154. package/dist/server/nuqs-ssr-provider.d.ts.map +1 -1
  155. package/dist/server/pipeline-phases.d.ts.map +1 -1
  156. package/dist/server/prebuilt/key-discipline.d.ts +32 -3
  157. package/dist/server/prebuilt/key-discipline.d.ts.map +1 -1
  158. package/dist/server/primitives.d.ts.map +1 -1
  159. package/dist/server/render-utils.d.ts +4 -3
  160. package/dist/server/render-utils.d.ts.map +1 -1
  161. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
  162. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  163. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  164. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  165. package/dist/server/ssr-bridge-types.d.ts +22 -2
  166. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  167. package/dist/server/ssr-entry.d.ts.map +1 -1
  168. package/dist/server/ssr-render.d.ts +5 -1
  169. package/dist/server/ssr-render.d.ts.map +1 -1
  170. package/dist/server/ssr-wrappers.d.ts +59 -27
  171. package/dist/server/ssr-wrappers.d.ts.map +1 -1
  172. package/dist/server/types.d.ts +10 -0
  173. package/dist/server/types.d.ts.map +1 -1
  174. package/dist/shims/navigation-rsc.d.ts +21 -0
  175. package/dist/shims/navigation-rsc.d.ts.map +1 -0
  176. package/docs/api/30-api-server.mdx +1 -0
  177. package/docs/api/35-api-typescript.mdx +4 -83
  178. package/docs/learn/03-fetching-data.mdx +1 -1
  179. package/docs/learn/{03b-access-control.mdx → 04-access-control.mdx} +2 -17
  180. package/docs/learn/05-the-flush-point.mdx +175 -0
  181. package/docs/learn/{05-typed-params.mdx → 06-typed-params.mdx} +1 -1
  182. package/docs/learn/07-typed-routes.mdx +25 -49
  183. package/docs/learn/{08-streaming.mdx → 09-streaming.mdx} +1 -7
  184. package/docs/learn/{10-middleware.mdx → 11-middleware.mdx} +1 -0
  185. package/package.json +3 -3
  186. package/src/adapters/nitro.ts +7 -7
  187. package/src/cache/singleflight.ts +5 -0
  188. package/src/client/browser-entry/hydrate.ts +54 -104
  189. package/src/client/browser-entry/index.ts +16 -6
  190. package/src/client/browser-entry/post-hydration.ts +3 -2
  191. package/src/client/browser-entry/router-init.ts +84 -33
  192. package/src/client/global-context.ts +31 -0
  193. package/src/client/internal.ts +1 -2
  194. package/src/client/link.tsx +25 -19
  195. package/src/client/location-search.ts +15 -0
  196. package/src/client/navigation-api.ts +4 -2
  197. package/src/client/navigation-commit.ts +48 -2
  198. package/src/client/navigation-context.ts +25 -37
  199. package/src/client/navigation-root.tsx +55 -411
  200. package/src/client/navigation-transition.ts +278 -0
  201. package/src/client/nuqs-adapter.tsx +4 -5
  202. package/src/client/params-context.ts +13 -18
  203. package/src/client/react-root.ts +72 -0
  204. package/src/client/router-lifecycle.ts +1 -1
  205. package/src/client/router-pipeline.ts +96 -22
  206. package/src/client/router-types.ts +12 -2
  207. package/src/client/router.ts +48 -36
  208. package/src/client/segment-cache.ts +70 -2
  209. package/src/client/segment-context.ts +7 -4
  210. package/src/client/segment-outlet.tsx +41 -86
  211. package/src/client/segment-update-context.ts +7 -26
  212. package/src/client/slot-content-cache-context.ts +43 -0
  213. package/src/client/ssr-data.ts +8 -2
  214. package/src/client/state.ts +0 -26
  215. package/src/client/use-pathname.ts +21 -31
  216. package/src/client/use-search-params.ts +31 -29
  217. package/src/client/use-segment-params.ts +27 -126
  218. package/src/config-types.ts +17 -0
  219. package/src/config-validation.ts +17 -0
  220. package/src/dev-tools/holding-server.ts +23 -12
  221. package/src/index.ts +26 -11
  222. package/src/plugins/dev-server.ts +9 -12
  223. package/src/plugins/entries.ts +3 -0
  224. package/src/plugins/shims.ts +8 -7
  225. package/src/plugins/static-build.ts +9 -5
  226. package/src/routing/interception-overlap.ts +141 -0
  227. package/src/routing/interception.ts +118 -5
  228. package/src/rsc-runtime/ssr.ts +3 -2
  229. package/src/server/als-registry.ts +6 -0
  230. package/src/server/csp-nonce.ts +70 -0
  231. package/src/server/default-status-page.ts +1 -0
  232. package/src/server/deny-renderer.ts +7 -3
  233. package/src/server/flight-scripts.ts +9 -4
  234. package/src/server/html-injector-core.ts +26 -9
  235. package/src/server/html-injectors.ts +8 -8
  236. package/src/server/metadata-render.ts +26 -4
  237. package/src/server/node-stream-transforms.ts +7 -20
  238. package/src/server/nuqs-ssr-provider.tsx +8 -7
  239. package/src/server/pipeline-phases.ts +5 -0
  240. package/src/server/prebuilt/key-discipline.ts +82 -13
  241. package/src/server/prebuilt-runtime.ts +2 -2
  242. package/src/server/primitives.ts +4 -4
  243. package/src/server/render-utils.ts +8 -4
  244. package/src/server/rsc-entry/action-middleware-runner.ts +7 -0
  245. package/src/server/rsc-entry/error-renderer.ts +5 -2
  246. package/src/server/rsc-entry/index.ts +8 -0
  247. package/src/server/rsc-entry/ssr-renderer.ts +11 -4
  248. package/src/server/ssr-bridge-types.ts +22 -2
  249. package/src/server/ssr-entry.ts +35 -28
  250. package/src/server/ssr-render.ts +13 -4
  251. package/src/server/ssr-wrappers.tsx +81 -61
  252. package/src/server/types.ts +10 -0
  253. package/src/shared/slot-params.ts +3 -4
  254. package/src/shims/navigation-rsc.ts +47 -0
  255. package/dist/_chunks/build-manifest-DWppEdLB.js.map +0 -1
  256. package/dist/_chunks/cache-api-CQeYzA5g.js.map +0 -1
  257. package/dist/_chunks/href-validation-CMc5JRls.js.map +0 -1
  258. package/dist/_chunks/scanner-BRIOmHE2.js.map +0 -1
  259. package/dist/_chunks/segment-context-CjOlyB8Y.js.map +0 -1
  260. package/dist/_chunks/slot-params-BCTmZkQB.js +0 -76
  261. package/dist/_chunks/slot-params-BCTmZkQB.js.map +0 -1
  262. package/dist/_chunks/ssr-data-Ya2HJPFp.js.map +0 -1
  263. package/dist/_chunks/use-segment-params-DzTBpkvj.js +0 -398
  264. package/dist/_chunks/use-segment-params-DzTBpkvj.js.map +0 -1
  265. package/docs/learn/04-loading-states.mdx +0 -67
  266. package/docs/learn/04b-the-flush-point.mdx +0 -115
  267. package/docs/learn/12-client-navigation.mdx +0 -176
  268. package/docs/learn/13-configuration.mdx +0 -166
  269. package/docs/more/01-advanced-routing.mdx +0 -344
  270. package/docs/more/02-advanced-forms.mdx +0 -137
  271. package/docs/more/03-coming-from-nextjs.mdx +0 -186
  272. package/docs/more/04-metadata-and-fonts.mdx +0 -193
  273. package/docs/more/04b-mdx.mdx +0 -229
  274. package/docs/more/05-content-collections.mdx +0 -90
  275. package/docs/more/06-instrumentation.mdx +0 -214
  276. package/docs/more/07-security.mdx +0 -129
  277. package/docs/more/08-developer-experience.mdx +0 -134
  278. package/docs/more/40-why-timber.mdx +0 -50
  279. package/docs/more/41-timber-vs-nextjs.mdx +0 -81
  280. package/docs/more/42-timber-vs-others.mdx +0 -68
  281. package/docs/more/50-ai-agent-instructions.mdx +0 -171
  282. /package/docs/learn/{06-forms-and-actions.mdx → 08-forms-and-actions.mdx} +0 -0
  283. /package/docs/learn/{09-caching.mdx → 10-caching.mdx} +0 -0
  284. /package/docs/learn/{11-error-handling.mdx → 12-error-handling.mdx} +0 -0
  285. /package/docs/learn/{14-deploying.mdx → 13-deploying.mdx} +0 -0
@@ -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.