@timber-js/app 0.2.0-alpha.158 → 0.2.0-alpha.159

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 (286) hide show
  1. package/dist/_chunks/{actions-BA5tIpVu.js → actions-CBbP1hHO.js} +3 -3
  2. package/dist/_chunks/{actions-BA5tIpVu.js.map → actions-CBbP1hHO.js.map} +1 -1
  3. package/dist/_chunks/{build-output-helper-C8q4Zpf5.js → build-output-helper-DF5uScqS.js} +4 -1
  4. package/dist/_chunks/{build-output-helper-C8q4Zpf5.js.map → build-output-helper-DF5uScqS.js.map} +1 -1
  5. package/dist/_chunks/cache-api-DsWEYwVZ.js +1120 -0
  6. package/dist/_chunks/cache-api-DsWEYwVZ.js.map +1 -0
  7. package/dist/_chunks/{cli-schema-sync-DcmQrzJG.js → cli-schema-sync-Bivx4Rn7.js} +4 -3
  8. package/dist/_chunks/{cli-schema-sync-DcmQrzJG.js.map → cli-schema-sync-Bivx4Rn7.js.map} +1 -1
  9. package/dist/_chunks/dist-BA3u1z3W.js +423 -0
  10. package/dist/_chunks/dist-BA3u1z3W.js.map +1 -0
  11. package/dist/_chunks/{logger-C5v1IMuh.js → logger-CrRmZMlo.js} +81 -56
  12. package/dist/_chunks/logger-CrRmZMlo.js.map +1 -0
  13. package/dist/_chunks/{plugin-context-DUl3dnCF.js → plugin-context-DeAxFRMq.js} +3 -36
  14. package/dist/_chunks/plugin-context-DeAxFRMq.js.map +1 -0
  15. package/dist/_chunks/sizeof-lXffQ1Vr.js +35 -0
  16. package/dist/_chunks/sizeof-lXffQ1Vr.js.map +1 -0
  17. package/dist/_chunks/{walkers-D5jCGPum.js → walkers-DXbJkeCY.js} +217 -19
  18. package/dist/_chunks/walkers-DXbJkeCY.js.map +1 -0
  19. package/dist/adapters/build-output-helper.d.ts.map +1 -1
  20. package/dist/adapters/cloudflare.d.ts +28 -0
  21. package/dist/adapters/cloudflare.d.ts.map +1 -1
  22. package/dist/adapters/cloudflare.js +143 -9
  23. package/dist/adapters/cloudflare.js.map +1 -1
  24. package/dist/adapters/nitro.js +1 -1
  25. package/dist/cache/cache-api.d.ts +2 -0
  26. package/dist/cache/cache-api.d.ts.map +1 -1
  27. package/dist/cache/handler-store.d.ts +2 -0
  28. package/dist/cache/handler-store.d.ts.map +1 -1
  29. package/dist/cache/index.d.ts +2 -0
  30. package/dist/cache/index.d.ts.map +1 -1
  31. package/dist/cache/index.js +3 -186
  32. package/dist/cache/redis-handler.d.ts +17 -5
  33. package/dist/cache/redis-handler.d.ts.map +1 -1
  34. package/dist/cache/singleflight.d.ts +1 -1
  35. package/dist/cache/singleflight.d.ts.map +1 -1
  36. package/dist/cache/store.d.ts +53 -0
  37. package/dist/cache/store.d.ts.map +1 -0
  38. package/dist/cache/stores/cloudflare-kv.d.ts +38 -0
  39. package/dist/cache/stores/cloudflare-kv.d.ts.map +1 -0
  40. package/dist/cache/stores/cloudflare-kv.js +29 -0
  41. package/dist/cache/stores/cloudflare-kv.js.map +1 -0
  42. package/dist/cache/stores/memory.d.ts +42 -0
  43. package/dist/cache/stores/memory.d.ts.map +1 -0
  44. package/dist/cache/stores/memory.js +90 -0
  45. package/dist/cache/stores/memory.js.map +1 -0
  46. package/dist/cache/stores/redis.d.ts +69 -0
  47. package/dist/cache/stores/redis.d.ts.map +1 -0
  48. package/dist/cache/stores/redis.js +35 -0
  49. package/dist/cache/stores/redis.js.map +1 -0
  50. package/dist/cache/stores/vercel.d.ts +36 -0
  51. package/dist/cache/stores/vercel.d.ts.map +1 -0
  52. package/dist/cache/stores/vercel.js +33 -0
  53. package/dist/cache/stores/vercel.js.map +1 -0
  54. package/dist/cache/tag-aware-handler.d.ts +51 -0
  55. package/dist/cache/tag-aware-handler.d.ts.map +1 -0
  56. package/dist/cache/timber-cache.d.ts.map +1 -1
  57. package/dist/cdn/cloudflare-purge.d.ts +23 -0
  58. package/dist/cdn/cloudflare-purge.d.ts.map +1 -0
  59. package/dist/cdn/fastly-purge.d.ts +25 -0
  60. package/dist/cdn/fastly-purge.d.ts.map +1 -0
  61. package/dist/cdn/headers.d.ts +82 -0
  62. package/dist/cdn/headers.d.ts.map +1 -0
  63. package/dist/cdn/index.d.ts +5 -0
  64. package/dist/cdn/index.d.ts.map +1 -0
  65. package/dist/cdn/purge-store.d.ts +23 -0
  66. package/dist/cdn/purge-store.d.ts.map +1 -0
  67. package/dist/cli.js +2 -2
  68. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  69. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
  70. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  71. package/dist/client/history.d.ts +0 -3
  72. package/dist/client/history.d.ts.map +1 -1
  73. package/dist/client/index.js +0 -1
  74. package/dist/client/index.js.map +1 -1
  75. package/dist/client/internal.js +11 -86
  76. package/dist/client/internal.js.map +1 -1
  77. package/dist/client/navigation-api.d.ts +1 -3
  78. package/dist/client/navigation-api.d.ts.map +1 -1
  79. package/dist/client/router.d.ts +1 -4
  80. package/dist/client/router.d.ts.map +1 -1
  81. package/dist/client/rsc-fetch.d.ts +1 -11
  82. package/dist/client/rsc-fetch.d.ts.map +1 -1
  83. package/dist/client/segment-cache.d.ts +1 -3
  84. package/dist/client/segment-cache.d.ts.map +1 -1
  85. package/dist/client/use-cookie.d.ts.map +1 -1
  86. package/dist/config-types.d.ts +3 -1
  87. package/dist/config-types.d.ts.map +1 -1
  88. package/dist/config-validation.d.ts.map +1 -1
  89. package/dist/cookies/index.js +15 -11
  90. package/dist/cookies/index.js.map +1 -1
  91. package/dist/dev-tools/browser-logs.d.ts +28 -72
  92. package/dist/dev-tools/browser-logs.d.ts.map +1 -1
  93. package/dist/dev-tools/index.d.ts +1 -1
  94. package/dist/dev-tools/index.d.ts.map +1 -1
  95. package/dist/dev-tools/logger.d.ts.map +1 -1
  96. package/dist/dev-tools/terminal.d.ts.map +1 -1
  97. package/dist/fonts/transform.d.ts +4 -0
  98. package/dist/fonts/transform.d.ts.map +1 -1
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +1269 -359
  101. package/dist/index.js.map +1 -1
  102. package/dist/plugin-context.d.ts +36 -0
  103. package/dist/plugin-context.d.ts.map +1 -1
  104. package/dist/plugins/build-report.d.ts +21 -0
  105. package/dist/plugins/build-report.d.ts.map +1 -1
  106. package/dist/plugins/cache.d.ts +5 -14
  107. package/dist/plugins/cache.d.ts.map +1 -1
  108. package/dist/plugins/callsite-ast.d.ts +95 -0
  109. package/dist/plugins/callsite-ast.d.ts.map +1 -0
  110. package/dist/plugins/callsite-jsx.d.ts +35 -0
  111. package/dist/plugins/callsite-jsx.d.ts.map +1 -0
  112. package/dist/plugins/entries.d.ts.map +1 -1
  113. package/dist/plugins/fonts.d.ts.map +1 -1
  114. package/dist/plugins/prebuilt-capture.d.ts +60 -0
  115. package/dist/plugins/prebuilt-capture.d.ts.map +1 -0
  116. package/dist/plugins/prebuilt.d.ts +82 -0
  117. package/dist/plugins/prebuilt.d.ts.map +1 -0
  118. package/dist/plugins/prerender-sugar.d.ts +42 -0
  119. package/dist/plugins/prerender-sugar.d.ts.map +1 -0
  120. package/dist/plugins/server-bundle.d.ts.map +1 -1
  121. package/dist/plugins/shims.d.ts.map +1 -1
  122. package/dist/plugins/static-build.d.ts.map +1 -1
  123. package/dist/routing/convention-lint.d.ts.map +1 -1
  124. package/dist/routing/export-detect.d.ts +56 -0
  125. package/dist/routing/export-detect.d.ts.map +1 -0
  126. package/dist/routing/index.js +2 -2
  127. package/dist/routing/manifest-codegen.d.ts.map +1 -1
  128. package/dist/rsc-runtime/rsc.d.ts +1 -1
  129. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  130. package/dist/server/action-handler.d.ts.map +1 -1
  131. package/dist/server/actions.d.ts +0 -3
  132. package/dist/server/actions.d.ts.map +1 -1
  133. package/dist/server/als-registry.d.ts +7 -0
  134. package/dist/server/als-registry.d.ts.map +1 -1
  135. package/dist/server/cookie-parsing.d.ts +15 -1
  136. package/dist/server/cookie-parsing.d.ts.map +1 -1
  137. package/dist/server/deny-renderer.d.ts.map +1 -1
  138. package/dist/server/html-injectors.d.ts +1 -0
  139. package/dist/server/html-injectors.d.ts.map +1 -1
  140. package/dist/server/index.js +2 -2
  141. package/dist/server/internal.d.ts +6 -1
  142. package/dist/server/internal.d.ts.map +1 -1
  143. package/dist/server/internal.js +563 -74
  144. package/dist/server/internal.js.map +1 -1
  145. package/dist/server/prebuilt/cache-key.d.ts +58 -0
  146. package/dist/server/prebuilt/cache-key.d.ts.map +1 -0
  147. package/dist/server/prebuilt/capture-state.d.ts +57 -0
  148. package/dist/server/prebuilt/capture-state.d.ts.map +1 -0
  149. package/dist/server/prebuilt/manifest-tag-index.d.ts +27 -0
  150. package/dist/server/prebuilt/manifest-tag-index.d.ts.map +1 -0
  151. package/dist/server/prebuilt/overlay.d.ts +100 -0
  152. package/dist/server/prebuilt/overlay.d.ts.map +1 -0
  153. package/dist/server/prebuilt/payload-source.d.ts +75 -0
  154. package/dist/server/prebuilt/payload-source.d.ts.map +1 -0
  155. package/dist/server/prebuilt/synthetic-store.d.ts +64 -0
  156. package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -0
  157. package/dist/server/prebuilt-builder.d.ts +110 -0
  158. package/dist/server/prebuilt-builder.d.ts.map +1 -0
  159. package/dist/server/prebuilt-runtime.d.ts +101 -0
  160. package/dist/server/prebuilt-runtime.d.ts.map +1 -0
  161. package/dist/server/route-element-builder.d.ts +10 -8
  162. package/dist/server/route-element-builder.d.ts.map +1 -1
  163. package/dist/server/rsc-entry/helpers.d.ts +0 -5
  164. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  165. package/dist/server/rsc-entry/index.d.ts +10 -0
  166. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  167. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  168. package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
  169. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -4
  170. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  171. package/dist/server/sitemap-generator.d.ts +6 -6
  172. package/dist/server/sitemap-generator.d.ts.map +1 -1
  173. package/dist/server/tracing.d.ts +37 -3
  174. package/dist/server/tracing.d.ts.map +1 -1
  175. package/docs/api/32-api-cache.mdx +10 -4
  176. package/docs/learn/00-introduction.mdx +8 -13
  177. package/docs/learn/02-pages-and-layouts.mdx +9 -0
  178. package/docs/learn/06-forms-and-actions.mdx +0 -4
  179. package/docs/learn/07-typed-routes.mdx +127 -0
  180. package/docs/learn/09-caching.mdx +12 -1
  181. package/docs/learn/10-middleware.mdx +1 -0
  182. package/docs/learn/11-error-handling.mdx +9 -0
  183. package/docs/learn/13-configuration.mdx +28 -0
  184. package/docs/learn/14-deploying.mdx +52 -0
  185. package/docs/more/06-instrumentation.mdx +27 -1
  186. package/docs/more/07-security.mdx +8 -2
  187. package/docs/more/08-developer-experience.mdx +134 -0
  188. package/package.json +31 -2
  189. package/src/adapters/build-output-helper.ts +10 -0
  190. package/src/adapters/cloudflare.ts +178 -4
  191. package/src/cache/cache-api.ts +60 -0
  192. package/src/cache/handler-store.ts +8 -0
  193. package/src/cache/index.ts +4 -0
  194. package/src/cache/redis-handler.ts +25 -13
  195. package/src/cache/singleflight.ts +11 -11
  196. package/src/cache/store.ts +71 -0
  197. package/src/cache/stores/cloudflare-kv.ts +52 -0
  198. package/src/cache/stores/memory.ts +125 -0
  199. package/src/cache/stores/redis.ts +97 -0
  200. package/src/cache/stores/vercel.ts +59 -0
  201. package/src/cache/tag-aware-handler.ts +233 -0
  202. package/src/cache/timber-cache.ts +17 -7
  203. package/src/cdn/cloudflare-purge.ts +50 -0
  204. package/src/cdn/fastly-purge.ts +63 -0
  205. package/src/cdn/headers.ts +127 -0
  206. package/src/cdn/index.ts +16 -0
  207. package/src/cdn/purge-store.ts +34 -0
  208. package/src/client/browser-entry/action-dispatch.ts +1 -10
  209. package/src/client/browser-entry/post-hydration.ts +0 -1
  210. package/src/client/browser-entry/router-init.ts +0 -5
  211. package/src/client/history.ts +0 -4
  212. package/src/client/navigation-api.ts +5 -11
  213. package/src/client/router.ts +9 -43
  214. package/src/client/rsc-fetch.ts +5 -27
  215. package/src/client/segment-cache.ts +1 -4
  216. package/src/client/use-cookie.ts +18 -12
  217. package/src/config-types.ts +3 -1
  218. package/src/config-validation.ts +1 -0
  219. package/src/dev-tools/browser-logs.ts +37 -273
  220. package/src/dev-tools/index.ts +2 -2
  221. package/src/dev-tools/instrumentation.ts +1 -1
  222. package/src/dev-tools/logger.ts +33 -51
  223. package/src/dev-tools/terminal.ts +21 -22
  224. package/src/fonts/transform.ts +4 -43
  225. package/src/index.ts +16 -3
  226. package/src/plugin-context.ts +36 -0
  227. package/src/plugins/build-report.ts +133 -1
  228. package/src/plugins/cache.ts +32 -131
  229. package/src/plugins/callsite-ast.ts +167 -0
  230. package/src/plugins/callsite-jsx.ts +243 -0
  231. package/src/plugins/entries.ts +23 -4
  232. package/src/plugins/fonts.ts +16 -2
  233. package/src/plugins/prebuilt-capture.ts +208 -0
  234. package/src/plugins/prebuilt.ts +673 -0
  235. package/src/plugins/prerender-sugar.ts +329 -0
  236. package/src/plugins/server-bundle.ts +12 -0
  237. package/src/plugins/shims.ts +9 -0
  238. package/src/plugins/static-build.ts +0 -1
  239. package/src/routing/codegen.ts +1 -21
  240. package/src/routing/convention-lint.ts +85 -49
  241. package/src/routing/export-detect.ts +304 -0
  242. package/src/routing/manifest-codegen.ts +2 -11
  243. package/src/rsc-runtime/rsc.ts +7 -0
  244. package/src/server/action-handler.ts +11 -27
  245. package/src/server/actions.ts +3 -4
  246. package/src/server/als-registry.ts +7 -0
  247. package/src/server/cookie-parsing.ts +75 -59
  248. package/src/server/deny-renderer.ts +7 -15
  249. package/src/server/html-injectors.ts +11 -3
  250. package/src/server/internal.ts +17 -1
  251. package/src/server/prebuilt/cache-key.ts +224 -0
  252. package/src/server/prebuilt/capture-state.ts +89 -0
  253. package/src/server/prebuilt/manifest-tag-index.ts +46 -0
  254. package/src/server/prebuilt/overlay.ts +256 -0
  255. package/src/server/prebuilt/payload-source.ts +192 -0
  256. package/src/server/prebuilt/synthetic-store.ts +108 -0
  257. package/src/server/prebuilt-builder.ts +640 -0
  258. package/src/server/prebuilt-runtime.ts +706 -0
  259. package/src/server/route-element-builder.ts +40 -12
  260. package/src/server/rsc-entry/helpers.ts +0 -17
  261. package/src/server/rsc-entry/index.ts +83 -0
  262. package/src/server/rsc-entry/render-route.ts +6 -33
  263. package/src/server/rsc-entry/revalidate-renderer.ts +16 -11
  264. package/src/server/rsc-entry/rsc-payload.ts +5 -11
  265. package/src/server/sitemap-generator.ts +16 -10
  266. package/src/server/tracing.ts +88 -3
  267. package/dist/_chunks/cache-api-Cbyh3RW4.js +0 -401
  268. package/dist/_chunks/cache-api-Cbyh3RW4.js.map +0 -1
  269. package/dist/_chunks/logger-C5v1IMuh.js.map +0 -1
  270. package/dist/_chunks/plugin-context-DUl3dnCF.js.map +0 -1
  271. package/dist/_chunks/validation-CnKhGJsp.js +0 -113
  272. package/dist/_chunks/validation-CnKhGJsp.js.map +0 -1
  273. package/dist/_chunks/walkers-D5jCGPum.js.map +0 -1
  274. package/dist/cache/index.js.map +0 -1
  275. package/dist/client/head.d.ts +0 -16
  276. package/dist/client/head.d.ts.map +0 -1
  277. package/dist/client/navigation-api-types.d.ts +0 -90
  278. package/dist/client/navigation-api-types.d.ts.map +0 -1
  279. package/dist/server/client-module-map.d.ts +0 -47
  280. package/dist/server/client-module-map.d.ts.map +0 -1
  281. package/dist/shared/head-header.d.ts +0 -54
  282. package/dist/shared/head-header.d.ts.map +0 -1
  283. package/src/client/head.ts +0 -61
  284. package/src/client/navigation-api-types.ts +0 -112
  285. package/src/server/client-module-map.ts +0 -58
  286. package/src/shared/head-header.ts +0 -69
@@ -17,7 +17,6 @@
17
17
  * See design/19-client-navigation.md
18
18
  */
19
19
 
20
- import type { NavigationApi, NavigateEvent } from './navigation-api-types.js';
21
20
  import { isHardNavigating } from './navigation-root.js';
22
21
 
23
22
  // ─── Feature Detection ───────────────────────────────────────────
@@ -27,20 +26,15 @@ import { isHardNavigating } from './navigation-root.js';
27
26
  * Feature-detected at runtime — no polyfill.
28
27
  */
29
28
  export function hasNavigationApi(): boolean {
30
- return (
31
- typeof window !== 'undefined' &&
32
- 'navigation' in window &&
33
- (window as unknown as { navigation: unknown }).navigation != null
34
- );
29
+ return typeof window !== 'undefined' && 'navigation' in window && window.navigation != null;
35
30
  }
36
31
 
37
32
  /**
38
33
  * Get the Navigation API instance. Returns null if unavailable.
39
- * Uses type assertion — we never import Navigation API types unconditionally.
40
34
  */
41
- export function getNavigationApi(): NavigationApi | null {
35
+ export function getNavigationApi(): Navigation | null {
42
36
  if (!hasNavigationApi()) return null;
43
- return (window as unknown as { navigation: NavigationApi }).navigation;
37
+ return window.navigation;
44
38
  }
45
39
 
46
40
  // ─── Navigation API Controller ───────────────────────────────────
@@ -285,7 +279,7 @@ export function setupNavigationApi(callbacks: NavigationApiCallbacks): Navigatio
285
279
  }
286
280
  }
287
281
 
288
- nav.addEventListener('navigate', handleNavigate as EventListener);
282
+ nav.addEventListener('navigate', handleNavigate);
289
283
 
290
284
  return {
291
285
  setRouterNavigating(value: boolean): void {
@@ -341,7 +335,7 @@ export function setupNavigationApi(callbacks: NavigationApiCallbacks): Navigatio
341
335
  },
342
336
 
343
337
  cleanup(): void {
344
- nav.removeEventListener('navigate', handleNavigate as EventListener);
338
+ nav.removeEventListener('navigate', handleNavigate);
345
339
  },
346
340
  };
347
341
  }
@@ -4,7 +4,6 @@
4
4
  import { SegmentCache, PrefetchCache, buildSegmentTree } from './segment-cache';
5
5
  import type { SegmentInfo } from './segment-cache';
6
6
  import { HistoryStack } from './history';
7
- import type { HeadElement } from './head';
8
7
  import { setCurrentParams } from './use-segment-params.js';
9
8
  import {
10
9
  setNavigationState,
@@ -82,8 +81,6 @@ export interface RouterDeps {
82
81
  * In tests, this runs the callback synchronously.
83
82
  */
84
83
  afterPaint?: (callback: () => void) => void;
85
- /** Apply resolved head elements (title, meta tags) to the DOM after navigation. */
86
- applyHead?: (elements: HeadElement[]) => void;
87
84
  /**
88
85
  * Run a navigation inside a React transition with optimistic pending URL.
89
86
  * The pending URL shows immediately (useOptimistic urgent update) and
@@ -180,7 +177,7 @@ export interface RouterInstance {
180
177
  * Renders the element tree and updates head elements without a server fetch.
181
178
  * See design/08-forms-and-actions.md §"Single-Roundtrip Revalidation".
182
179
  */
183
- applyRevalidation(element: unknown, headElements: HeadElement[] | null): void;
180
+ applyRevalidation(element: unknown): void;
184
181
  /**
185
182
  * Populate the segment cache from server-provided segment metadata.
186
183
  * Called on initial hydration with segment info embedded in the HTML.
@@ -364,27 +361,19 @@ export function createRouter(deps: RouterDeps): RouterInstance {
364
361
  * FetchResult plus the NavigationState (so it can be passed explicitly
365
362
  * to wrapPayload/renderRoot without temporal coupling).
366
363
  */
367
- interface TransitionResult {
368
- headElements: HeadElement[] | null;
369
- decodePromise: Promise<void> | null;
370
- }
371
-
372
364
  async function renderViaTransition(
373
365
  url: string,
374
366
  perform: () => Promise<FetchResult & { navState: NavigationState }>
375
- ): Promise<TransitionResult> {
367
+ ): Promise<void> {
376
368
  if (deps.navigateTransition) {
377
- let headElements: HeadElement[] | null = null;
378
369
  await deps.navigateTransition(url, async (wrapPayload) => {
379
370
  const result = await perform();
380
- headElements = result.headElements;
381
371
 
382
372
  if (isPartialNavigation(result.skippedSegments)) {
383
373
  const segmentUpdates = buildSegmentUpdates(result);
384
374
 
385
375
  historyStack.push(url, {
386
376
  payload: null,
387
- headElements: result.headElements,
388
377
  params: result.params,
389
378
  });
390
379
 
@@ -402,22 +391,18 @@ export function createRouter(deps: RouterDeps): RouterInstance {
402
391
  // Full navigation — empty updates, render the new tree.
403
392
  historyStack.push(url, {
404
393
  payload: result.payload,
405
- headElements: result.headElements,
406
394
  params: result.params,
407
395
  });
408
396
  const element = wrapPayload(result.payload, result.navState);
409
397
  return { element, decodePromise: result.decodePromise };
410
398
  });
411
- // decodePromise is awaited inside the transition's startTransition
412
- // callback (NavigationRoot), so we don't need it here.
413
- return { headElements, decodePromise: null };
399
+ return;
414
400
  }
415
401
  // Fallback: no transition (tests, no React tree)
416
402
  const result = await perform();
417
403
  if (isPartialNavigation(result.skippedSegments)) {
418
404
  historyStack.push(url, {
419
405
  payload: null,
420
- headElements: result.headElements,
421
406
  params: result.params,
422
407
  });
423
408
  } else {
@@ -430,19 +415,10 @@ export function createRouter(deps: RouterDeps): RouterInstance {
430
415
  : result.payload;
431
416
  historyStack.push(url, {
432
417
  payload,
433
- headElements: result.headElements,
434
418
  params: result.params,
435
419
  });
436
420
  renderPayload(payload, result.navState);
437
421
  }
438
- return { headElements: result.headElements, decodePromise: result.decodePromise };
439
- }
440
-
441
- /** Apply head elements (title, meta tags) to the DOM if available. */
442
- function applyHead(elements: HeadElement[] | null | undefined): void {
443
- if (elements && deps.applyHead) {
444
- deps.applyHead(elements);
445
- }
446
422
  }
447
423
 
448
424
  /** Run a callback after the next paint (after React commit). */
@@ -495,7 +471,6 @@ export function createRouter(deps: RouterDeps): RouterInstance {
495
471
  ? {
496
472
  payload: prefetched.payload,
497
473
  decodePromise: null,
498
- headElements: prefetched.headElements,
499
474
  segmentInfo: prefetched.segmentInfo ?? null,
500
475
  params: prefetched.params ?? null,
501
476
  skippedSegments: prefetched.skippedSegments ?? null,
@@ -592,7 +567,7 @@ export function createRouter(deps: RouterDeps): RouterInstance {
592
567
  setPending(true, url);
593
568
 
594
569
  try {
595
- const { headElements } = await renderViaTransition(fetchUrl, () =>
570
+ await renderViaTransition(fetchUrl, () =>
596
571
  performNavigationFetch(fetchUrl, {
597
572
  replace,
598
573
  commitUrl: url,
@@ -601,9 +576,6 @@ export function createRouter(deps: RouterDeps): RouterInstance {
601
576
  })
602
577
  );
603
578
 
604
- // Update document.title and <meta> tags with the new page's metadata
605
- applyHead(headElements);
606
-
607
579
  // Notify nuqs adapter (and any other listeners) that navigation completed.
608
580
  window.dispatchEvent(new Event('timber:navigation-end'));
609
581
 
@@ -685,7 +657,7 @@ export function createRouter(deps: RouterDeps): RouterInstance {
685
657
  setPending(true, currentUrl);
686
658
 
687
659
  try {
688
- const { headElements } = await renderViaTransition(currentUrl, async () => {
660
+ await renderViaTransition(currentUrl, async () => {
689
661
  // No state tree sent — server renders the complete RSC payload
690
662
  const result = await fetchRscPayload(
691
663
  currentUrl,
@@ -699,8 +671,6 @@ export function createRouter(deps: RouterDeps): RouterInstance {
699
671
  const navState = updateNavigationState(result.params, currentUrl);
700
672
  return { ...result, navState };
701
673
  });
702
-
703
- applyHead(headElements);
704
674
  } catch (error) {
705
675
  if (isAbortError(error)) return;
706
676
  throw error;
@@ -739,7 +709,6 @@ export function createRouter(deps: RouterDeps): RouterInstance {
739
709
  setPending(false);
740
710
  const navState = updateNavigationState(entry.params, url);
741
711
  renderPayload(entry.payload, navState);
742
- applyHead(entry.headElements);
743
712
  restoreScrollAfterPaint(scrollY);
744
713
  } else {
745
714
  // No cached payload — fetch from server.
@@ -749,7 +718,7 @@ export function createRouter(deps: RouterDeps): RouterInstance {
749
718
  const navAbort = createNavAbort(externalSignal);
750
719
  setPending(true, url);
751
720
  try {
752
- const { headElements } = await renderViaTransition(url, async () => {
721
+ await renderViaTransition(url, async () => {
753
722
  const stateTree = segmentCache.serializeStateTree();
754
723
  const result = await fetchRscPayload(url, deps, stateTree, undefined, navAbort.signal);
755
724
  updateSegmentCache(result.segmentInfo);
@@ -758,7 +727,6 @@ export function createRouter(deps: RouterDeps): RouterInstance {
758
727
  return { ...result, navState };
759
728
  });
760
729
 
761
- applyHead(headElements);
762
730
  restoreScrollAfterPaint(scrollY);
763
731
  } catch (error) {
764
732
  if (isAbortError(error)) return;
@@ -811,10 +779,10 @@ export function createRouter(deps: RouterDeps): RouterInstance {
811
779
  return () => pendingListeners.delete(listener);
812
780
  },
813
781
  prefetch,
814
- applyRevalidation(element: unknown, headElements: HeadElement[] | null): void {
782
+ applyRevalidation(element: unknown): void {
815
783
  // Render the piggybacked element tree from a server action response.
816
- // Updates the current history entry with the fresh payload and applies
817
- // head elements — same as refresh() but without a server fetch.
784
+ // Updates the current history entry with the fresh payload —
785
+ // same as refresh() but without a server fetch.
818
786
  // Revalidation payloads are already resolved (no Flight thenable),
819
787
  // so no segment merging is needed — just cache and render.
820
788
  const currentUrl = deps.getCurrentUrl();
@@ -829,11 +797,9 @@ export function createRouter(deps: RouterDeps): RouterInstance {
829
797
  // to {} (TIM-1037).
830
798
  historyStack.push(currentUrl, {
831
799
  payload: element,
832
- headElements,
833
800
  params: navState.params,
834
801
  });
835
802
  renderPayload(element, navState);
836
- applyHead(headElements);
837
803
  },
838
804
  initSegmentCache: (segments: SegmentInfo[]) => updateSegmentCache(segments),
839
805
  segmentCache,
@@ -12,13 +12,11 @@
12
12
  */
13
13
 
14
14
  import type { SegmentInfo } from './segment-cache';
15
- import type { HeadElement } from './head';
16
15
  import type { RouterDeps } from './router';
17
- import { decodeHeadHeader, HEAD_HEADER } from '../shared/head-header.js';
18
16
 
19
17
  // ─── Types ───────────────────────────────────────────────────────
20
18
 
21
- /** Result of fetching an RSC payload — includes head elements and segment metadata. */
19
+ /** Result of fetching an RSC payload — includes segment metadata. */
22
20
  export interface FetchResult {
23
21
  payload: unknown;
24
22
  /**
@@ -29,7 +27,6 @@ export interface FetchResult {
29
27
  * become unhandled rejections.
30
28
  */
31
29
  decodePromise: Promise<void> | null;
32
- headElements: HeadElement[] | null;
33
30
  /** Segment metadata from X-Timber-Segments header for populating the segment cache. */
34
31
  segmentInfo: SegmentInfo[] | null;
35
32
  /** Route params from X-Timber-Params header for populating useSegmentParams(). */
@@ -149,20 +146,6 @@ function warnMalformedHeader(headerName: string, raw: string): void {
149
146
  }
150
147
  }
151
148
 
152
- /**
153
- * Extract head elements from the X-Timber-Head response header.
154
- * Returns null if the header is missing or malformed.
155
- */
156
- export function extractHeadElements(response: Response): HeadElement[] | null {
157
- const header = response.headers.get(HEAD_HEADER);
158
- if (!header) return null;
159
- const decoded = decodeHeadHeader(header);
160
- if (decoded === null) {
161
- warnMalformedHeader(HEAD_HEADER, header);
162
- }
163
- return decoded as HeadElement[] | null;
164
- }
165
-
166
149
  /**
167
150
  * Extract segment metadata from the X-Timber-Segments response header.
168
151
  * Returns null if the header is missing or malformed.
@@ -317,9 +300,6 @@ function trackStreamCompletion(body: ReadableStream<Uint8Array>): {
317
300
  * Fetch an RSC payload from the server. If a decodeRsc function is provided,
318
301
  * the response is decoded into a React element tree via createFromFetch.
319
302
  * Otherwise, the raw response text is returned (test mode).
320
- *
321
- * Also extracts head elements from the X-Timber-Head response header
322
- * so the client can update document.title and <meta> tags after navigation.
323
303
  */
324
304
  export async function fetchRscPayload(
325
305
  url: string,
@@ -335,10 +315,9 @@ export async function fetchRscPayload(
335
315
  // createFromFetch takes a Promise<Response> and progressively parses
336
316
  // the RSC Flight stream as chunks arrive.
337
317
  //
338
- // Intercept the response to read X-Timber-Head before createFromFetch
318
+ // Intercept the response to read segment metadata before createFromFetch
339
319
  // consumes the body. Reading headers does NOT consume the body stream.
340
320
  const fetchPromise = deps.fetch(rscUrl, { headers, redirect: 'manual', signal });
341
- let headElements: HeadElement[] | null = null;
342
321
  let segmentInfo: SegmentInfo[] | null = null;
343
322
  let params: Record<string, string | string[]> | null = null;
344
323
  let skippedSegments: string[] | null = null;
@@ -371,7 +350,8 @@ export async function fetchRscPayload(
371
350
  if (response.headers.get('X-Timber-Error') === '1') {
372
351
  throw new ServerErrorResponse(response.status, url);
373
352
  }
374
- headElements = extractHeadElements(response);
353
+ // Metadata (<title>/<meta>/<link>) now rides the RSC Flight payload
354
+ // as React elements — React 19 Float handles them. See TIM-1151.
375
355
  segmentInfo = extractSegmentInfo(response);
376
356
  params = extractParams(response);
377
357
  skippedSegments = extractSkippedSegments(response);
@@ -391,7 +371,7 @@ export async function fetchRscPayload(
391
371
  }
392
372
  return response;
393
373
  });
394
- // Await headers so headElements/segmentInfo/params are populated.
374
+ // Await headers so segmentInfo/params are populated.
395
375
  await wrappedPromise;
396
376
  // Start decoding but do NOT await — return the in-progress thenable.
397
377
  // React can render a Flight thenable directly: it suspends on unresolved
@@ -411,7 +391,6 @@ export async function fetchRscPayload(
411
391
  return {
412
392
  payload,
413
393
  decodePromise,
414
- headElements,
415
394
  segmentInfo,
416
395
  params,
417
396
  skippedSegments,
@@ -429,7 +408,6 @@ export async function fetchRscPayload(
429
408
  return {
430
409
  payload: await response.text(),
431
410
  decodePromise: null,
432
- headElements: extractHeadElements(response),
433
411
  segmentInfo: extractSegmentInfo(response),
434
412
  params: extractParams(response),
435
413
  skippedSegments: extractSkippedSegments(response),
@@ -1,14 +1,11 @@
1
1
  // Segment Cache — stores the mounted segment tree and prefetched payloads
2
2
  // See design/19-client-navigation.md for architecture details.
3
3
 
4
- import type { HeadElement } from './head';
5
-
6
4
  // ─── Types ───────────────────────────────────────────────────────
7
5
 
8
- /** A prefetched RSC result with optional head elements and segment metadata. */
6
+ /** A prefetched RSC result with optional segment metadata. */
9
7
  export interface PrefetchResult {
10
8
  payload: unknown;
11
- headElements: HeadElement[] | null;
12
9
  /** Segment metadata from X-Timber-Segments header for populating the segment cache. */
13
10
  segmentInfo?: SegmentInfo[] | null;
14
11
  /** Route params from X-Timber-Params header for populating useSegmentParams(). */
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { useSyncExternalStore } from 'react';
12
+ import { parseCookie, stringifySetCookie } from 'cookie';
12
13
  import { getSsrData } from './ssr-data.js';
13
14
  import { assertValidCookieName } from '../cookies/validation.js';
14
15
 
@@ -41,24 +42,29 @@ const listeners = new Map<string, Set<Listener>>();
41
42
  /** Parse a cookie name from document.cookie. */
42
43
  export function getCookieValue(name: string): string | undefined {
43
44
  if (typeof document === 'undefined') return undefined;
44
- const match = document.cookie.match(
45
- new RegExp('(?:^|;\\s*)' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\s*=\\s*([^;]*)')
46
- );
47
- return match ? decodeURIComponent(match[1]) : undefined;
45
+ const cookies = parseCookie(document.cookie);
46
+ return cookies[name];
48
47
  }
49
48
 
50
49
  /** Serialize options into a cookie string suffix. */
51
50
  export function serializeOptions(options?: ClientCookieOptions): string {
52
51
  if (!options) return '; Path=/; SameSite=Lax';
53
- const parts: string[] = [];
54
- parts.push(`Path=${options.path ?? '/'}`);
55
- if (options.domain) parts.push(`Domain=${options.domain}`);
56
- if (options.maxAge !== undefined) parts.push(`Max-Age=${options.maxAge}`);
57
- if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
58
52
  const sameSite = options.sameSite ?? 'lax';
59
- parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);
60
- if (options.secure) parts.push('Secure');
61
- return '; ' + parts.join('; ');
53
+ // Build a Set-Cookie string via the cookie package, then strip the
54
+ // `__placeholder__=` name=value prefix to get just the attributes.
55
+ const full = stringifySetCookie({
56
+ name: '__placeholder__',
57
+ value: '',
58
+ path: options.path ?? '/',
59
+ domain: options.domain,
60
+ maxAge: options.maxAge,
61
+ expires: options.expires,
62
+ sameSite,
63
+ secure: options.secure,
64
+ });
65
+ // Strip everything up to and including the first "; "
66
+ const idx = full.indexOf('; ');
67
+ return idx >= 0 ? full.slice(idx) : '';
62
68
  }
63
69
 
64
70
  /** Notify all subscribers for a given cookie name. */
@@ -47,6 +47,8 @@ export interface TimberUserConfig {
47
47
  clientJavascript?: boolean | ClientJavascriptConfig;
48
48
  adapter?: unknown;
49
49
  cacheHandler?: unknown;
50
+ /** CDN purge handler — called on revalidateTag/cache.invalidate to purge CDN-cached responses. */
51
+ cdnPurge?: unknown;
50
52
  allowedOrigins?: string[];
51
53
  csrf?: boolean;
52
54
  limits?: {
@@ -244,7 +246,7 @@ export interface TimberUserConfig {
244
246
  * Auto-generate sitemap.xml from the route tree.
245
247
  *
246
248
  * When enabled, timber walks the file-system route tree and produces a sitemap
247
- * for all discoverable pages. Dynamic routes with `generateStaticParams()` are
249
+ * for all discoverable pages. Dynamic routes with `generateStaticSegmentParams()` are
248
250
  * enumerated. Routes protected by `access.ts` are excluded by default.
249
251
  *
250
252
  * If a user-authored `sitemap.ts` or `sitemap.xml` exists at the app root,
@@ -31,6 +31,7 @@ const KNOWN_CONFIG_KEYS = {
31
31
  clientJavascript: true,
32
32
  adapter: true,
33
33
  cacheHandler: true,
34
+ cdnPurge: true,
34
35
  allowedOrigins: true,
35
36
  csrf: true,
36
37
  limits: true,