@timber-js/app 0.2.0-alpha.167 → 0.2.0-alpha.168

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 (152) hide show
  1. package/dist/_chunks/{actions-TSxpXLHJ.js → actions-O_LsyCE4.js} +3 -3
  2. package/dist/_chunks/{actions-TSxpXLHJ.js.map → actions-O_LsyCE4.js.map} +1 -1
  3. package/dist/_chunks/{cache-api-DzpQQOEx.js → cache-api-B-lhk9p4.js} +56 -11
  4. package/dist/_chunks/cache-api-B-lhk9p4.js.map +1 -0
  5. package/dist/_chunks/{cli-schema-sync-wX-i90Og.js → cli-schema-sync-B73L6pMq.js} +2 -2
  6. package/dist/_chunks/{cli-schema-sync-wX-i90Og.js.map → cli-schema-sync-B73L6pMq.js.map} +1 -1
  7. package/dist/_chunks/json-lossy-check-ClNvBM_3.js +63 -0
  8. package/dist/_chunks/json-lossy-check-ClNvBM_3.js.map +1 -0
  9. package/dist/_chunks/{logger-t3uxAmbX.js → logger-AWfuX-KJ.js} +2 -19
  10. package/dist/_chunks/logger-AWfuX-KJ.js.map +1 -0
  11. package/dist/_chunks/{walkers-Cfwvl-UC.js → walkers-CoOC8Hga.js} +2 -2
  12. package/dist/_chunks/{walkers-Cfwvl-UC.js.map → walkers-CoOC8Hga.js.map} +1 -1
  13. package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -1
  14. package/dist/adapters/cloudflare-kv-cache.js +9 -2
  15. package/dist/adapters/cloudflare-kv-cache.js.map +1 -1
  16. package/dist/cache/cache-api.d.ts.map +1 -1
  17. package/dist/cache/index.d.ts +1 -1
  18. package/dist/cache/index.d.ts.map +1 -1
  19. package/dist/cache/index.js +1 -1
  20. package/dist/cache/json-lossy-check.d.ts +11 -0
  21. package/dist/cache/json-lossy-check.d.ts.map +1 -0
  22. package/dist/cache/redis-handler.d.ts +42 -0
  23. package/dist/cache/redis-handler.d.ts.map +1 -1
  24. package/dist/cache/singleflight.d.ts.map +1 -1
  25. package/dist/cache/stores/cloudflare-kv.d.ts +1 -1
  26. package/dist/cache/stores/cloudflare-kv.d.ts.map +1 -1
  27. package/dist/cache/stores/memory.d.ts +1 -1
  28. package/dist/cache/stores/memory.d.ts.map +1 -1
  29. package/dist/cache/stores/redis.d.ts +1 -1
  30. package/dist/cache/stores/redis.d.ts.map +1 -1
  31. package/dist/cache/stores/vercel.d.ts +1 -1
  32. package/dist/cache/stores/vercel.d.ts.map +1 -1
  33. package/dist/cache/tag-aware-handler.d.ts.map +1 -1
  34. package/dist/cache/timber-cache.d.ts.map +1 -1
  35. package/dist/cdn/cloudflare-purge.d.ts.map +1 -1
  36. package/dist/cdn/fastly-purge.d.ts.map +1 -1
  37. package/dist/cdn/workers-cache-purge.d.ts.map +1 -1
  38. package/dist/cli.d.ts +1 -1
  39. package/dist/cli.d.ts.map +1 -1
  40. package/dist/cli.js +3 -2
  41. package/dist/cli.js.map +1 -1
  42. package/dist/client/browser-entry/router-init.d.ts +1 -0
  43. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  44. package/dist/client/child-segment-context.d.ts +2 -2
  45. package/dist/client/child-segment-context.d.ts.map +1 -1
  46. package/dist/client/error-boundary.d.ts.map +1 -1
  47. package/dist/client/history.d.ts.map +1 -1
  48. package/dist/client/internal.js +4 -3
  49. package/dist/client/internal.js.map +1 -1
  50. package/dist/client/router.d.ts +6 -0
  51. package/dist/client/router.d.ts.map +1 -1
  52. package/dist/client/rsc-fetch.d.ts.map +1 -1
  53. package/dist/client/segment-cache.d.ts.map +1 -1
  54. package/dist/client/segment-update-context.d.ts +3 -3
  55. package/dist/client/segment-update-context.d.ts.map +1 -1
  56. package/dist/client/slot-outlet.d.ts +1 -1
  57. package/dist/codec.d.ts.map +1 -1
  58. package/dist/codec.js +2 -1
  59. package/dist/codec.js.map +1 -1
  60. package/dist/config-types.d.ts +16 -37
  61. package/dist/config-types.d.ts.map +1 -1
  62. package/dist/config-validation.d.ts.map +1 -1
  63. package/dist/dev-tools/instrumentation.d.ts.map +1 -1
  64. package/dist/fonts/pipeline.d.ts +19 -0
  65. package/dist/fonts/pipeline.d.ts.map +1 -1
  66. package/dist/fonts/transform.d.ts.map +1 -1
  67. package/dist/fonts/virtual-modules.d.ts.map +1 -1
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +344 -84
  70. package/dist/index.js.map +1 -1
  71. package/dist/plugin-context.d.ts.map +1 -1
  72. package/dist/plugins/adapter-build.d.ts +1 -0
  73. package/dist/plugins/adapter-build.d.ts.map +1 -1
  74. package/dist/plugins/entries.d.ts +27 -3
  75. package/dist/plugins/entries.d.ts.map +1 -1
  76. package/dist/plugins/fonts.d.ts.map +1 -1
  77. package/dist/plugins/server-bundle.d.ts.map +1 -1
  78. package/dist/routing/index.js +2 -2
  79. package/dist/server/action-client.d.ts +1 -1
  80. package/dist/server/action-client.d.ts.map +1 -1
  81. package/dist/server/body-limits.d.ts.map +1 -1
  82. package/dist/server/form-data.d.ts.map +1 -1
  83. package/dist/server/html-injector-core.d.ts.map +1 -1
  84. package/dist/server/index.js +20 -3
  85. package/dist/server/index.js.map +1 -1
  86. package/dist/server/internal.js +24 -40
  87. package/dist/server/internal.js.map +1 -1
  88. package/dist/server/middleware-runner.d.ts +0 -17
  89. package/dist/server/middleware-runner.d.ts.map +1 -1
  90. package/dist/server/param-coercion.d.ts.map +1 -1
  91. package/dist/server/pipeline-phases.d.ts +6 -3
  92. package/dist/server/pipeline-phases.d.ts.map +1 -1
  93. package/dist/server/pipeline.d.ts +18 -0
  94. package/dist/server/pipeline.d.ts.map +1 -1
  95. package/dist/server/prebuilt/capture-state.d.ts.map +1 -1
  96. package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -1
  97. package/dist/server/primitives.d.ts.map +1 -1
  98. package/dist/server/render-timeout.d.ts.map +1 -1
  99. package/dist/server/route-element-builder.d.ts.map +1 -1
  100. package/dist/server/rsc-entry/action-middleware-runner.d.ts +14 -14
  101. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  102. package/dist/server/rsc-entry/render-route.d.ts +1 -0
  103. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  104. package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
  105. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +44 -74
  106. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
  107. package/dist/server/safe-load.d.ts.map +1 -1
  108. package/dist/server/ssr-entry.d.ts.map +1 -1
  109. package/dist/shared/redirect-type.d.ts +2 -2
  110. package/dist/shared/redirect-type.d.ts.map +1 -1
  111. package/dist/shims/font-google.d.ts.map +1 -1
  112. package/dist/shims/image.d.ts +120 -120
  113. package/dist/shims/image.d.ts.map +1 -1
  114. package/docs/api/32-api-cache.mdx +112 -0
  115. package/docs/api/34-api-config.mdx +0 -26
  116. package/docs/learn/09-caching.mdx +121 -5
  117. package/docs/learn/12-client-navigation.mdx +9 -1
  118. package/docs/learn/13-configuration.mdx +6 -8
  119. package/docs/learn/14-deploying.mdx +18 -18
  120. package/docs/more/04-metadata-and-fonts.mdx +1 -1
  121. package/package.json +6 -6
  122. package/src/adapters/cloudflare-kv-cache.ts +27 -6
  123. package/src/cache/index.ts +1 -1
  124. package/src/cache/json-lossy-check.ts +75 -0
  125. package/src/cache/redis-handler.ts +65 -14
  126. package/src/cache/timber-cache.ts +10 -2
  127. package/src/client/browser-entry/index.ts +2 -0
  128. package/src/client/browser-entry/router-init.ts +2 -0
  129. package/src/client/router.ts +13 -3
  130. package/src/codec.ts +3 -1
  131. package/src/config-types.ts +16 -37
  132. package/src/config-validation.ts +7 -5
  133. package/src/fonts/pipeline.ts +32 -0
  134. package/src/fonts/transform.ts +30 -24
  135. package/src/plugins/adapter-build.ts +131 -13
  136. package/src/plugins/entries.ts +40 -43
  137. package/src/plugins/fonts.ts +30 -0
  138. package/src/plugins/server-bundle.ts +7 -15
  139. package/src/server/action-client.ts +1 -1
  140. package/src/server/middleware-runner.ts +0 -44
  141. package/src/server/param-coercion.ts +1 -0
  142. package/src/server/pipeline-phases.ts +11 -13
  143. package/src/server/pipeline.ts +51 -3
  144. package/src/server/route-element-builder.ts +10 -1
  145. package/src/server/rsc-entry/action-middleware-runner.ts +14 -14
  146. package/src/server/rsc-entry/index.ts +30 -40
  147. package/src/server/rsc-entry/render-route.ts +3 -1
  148. package/src/server/rsc-entry/wrap-action-dispatch.ts +109 -372
  149. package/dist/_chunks/cache-api-DzpQQOEx.js.map +0 -1
  150. package/dist/_chunks/logger-t3uxAmbX.js.map +0 -1
  151. package/dist/_chunks/tree-match-D2l830j2.js +0 -102
  152. package/dist/_chunks/tree-match-D2l830j2.js.map +0 -1
@@ -238,6 +238,29 @@ export interface PipelineConfig {
238
238
  */
239
239
  match?: RouteMatch
240
240
  ) => Response | Promise<Response>;
241
+
242
+ /**
243
+ * Server action dispatch — called after proxy.ts runs for POST requests
244
+ * that look like server actions. Returns a Response if the request was
245
+ * handled as an action, or null to fall through to normal routing.
246
+ *
247
+ * `canonicalPath` is the already-decoded canonical pathname — the same
248
+ * value that `handleRequest` and `matchRoute` use. Do NOT re-derive it
249
+ * from `req.url` (which re-encodes).
250
+ *
251
+ * The `reenter` parameter is the full pipeline function (including proxy,
252
+ * ALS, tracing) for the no-JS validation rerender path — the synthetic
253
+ * GET flows through the complete pipeline.
254
+ *
255
+ * Moved into the pipeline (TIM-1213) so proxy.ts runs on action POSTs,
256
+ * matching the design doc contract: "proxy.ts runs on every request,
257
+ * no exclusions."
258
+ */
259
+ dispatchAction?: (
260
+ req: Request,
261
+ canonicalPath: string,
262
+ reenter: (req: Request) => Promise<Response>
263
+ ) => Promise<Response | null>;
241
264
  }
242
265
 
243
266
  // ─── Pipeline ──────────────────────────────────────────────────────────────
@@ -265,7 +288,11 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
265
288
  // Logged with each request for diagnosing resource contention.
266
289
  let activeRequests = 0;
267
290
 
268
- return async (req: Request): Promise<Response> => {
291
+ // Self-reference for re-entry — the no-JS validation rerender path
292
+ // passes a synthetic GET back through the full pipeline (including
293
+ // proxy, ALS, tracing). The inner closure captures `pipelineFn` by
294
+ // name; by the time it's invoked, the const binding is initialized.
295
+ const pipelineFn = async (req: Request): Promise<Response> => {
269
296
  const url = new URL(req.url);
270
297
  const method = req.method;
271
298
  const path = url.pathname;
@@ -325,6 +352,24 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
325
352
  canonicalReq = new Request(canonicalUrl.toString(), req);
326
353
  }
327
354
 
355
+ // Build the inner handler — runs after proxy, before route
356
+ // matching. Checks for server action POSTs and dispatches them
357
+ // without entering the route match → middleware → render chain.
358
+ // Falls through to handleRequest for normal route handling.
359
+ // TIM-1213: action dispatch is now INSIDE the pipeline so
360
+ // proxy.ts runs on every request, including action POSTs.
361
+ const innerHandler = async (): Promise<Response> => {
362
+ if (config.dispatchAction) {
363
+ const actionResult = await config.dispatchAction(
364
+ canonicalReq,
365
+ canonicalPath,
366
+ pipelineFn
367
+ );
368
+ if (actionResult) return actionResult;
369
+ }
370
+ return handleRequest(config, canonicalReq, method, canonicalPath, true);
371
+ };
372
+
328
373
  let result: Response;
329
374
  if (proxyResolver) {
330
375
  const outcome = await runProxyPhase(
@@ -332,7 +377,8 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
332
377
  proxyResolver,
333
378
  canonicalReq,
334
379
  method,
335
- canonicalPath
380
+ canonicalPath,
381
+ innerHandler
336
382
  );
337
383
  result = await outcomeToResponse(config, outcome, {
338
384
  req: canonicalReq,
@@ -340,7 +386,7 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
340
386
  path: canonicalPath,
341
387
  });
342
388
  } else {
343
- result = await handleRequest(config, canonicalReq, method, canonicalPath, true);
389
+ result = await innerHandler();
344
390
  }
345
391
 
346
392
  // Set response status on the root span before it ends —
@@ -427,4 +473,6 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
427
473
  });
428
474
  });
429
475
  };
476
+
477
+ return pipelineFn;
430
478
  }
@@ -408,6 +408,7 @@ export async function buildRouteElement(
408
408
  // Skip OG auto-linking when the user already declared images in metadata.
409
409
  // See design/16-metadata.md §"Auto-Linking"
410
410
  const hasUserOgImage = Boolean(resolvedMetadata.openGraph?.images);
411
+ const hasUserTwitterImage = Boolean(resolvedMetadata.twitter?.images);
411
412
  const requestUrl = new URL(req.url);
412
413
  const requestPathname = requestUrl.pathname;
413
414
  // In dev mode, use the request origin so OG URLs resolve to localhost.
@@ -427,7 +428,7 @@ export async function buildRouteElement(
427
428
  if (!convention) continue;
428
429
  // Non-nestable routes only auto-link from root
429
430
  if (!convention.nestable && segment.urlPath !== '/') continue;
430
- // Skip auto-linking if user already declared the image in metadata
431
+ // Skip auto-linking entirely if user declared openGraph.images (covers both og + twitter)
431
432
  if (convention.type === 'opengraph-image' && hasUserOgImage) continue;
432
433
  // Build the href using the actual request path (not the pattern with [param]).
433
434
  // For nestable routes, the metadata route sits under the same resolved path
@@ -450,6 +451,14 @@ export async function buildRouteElement(
450
451
  href = new URL(href, ogBase).toString();
451
452
  }
452
453
  for (const autoLink of getMetadataRouteAutoLink(convention.type, href)) {
454
+ // Skip auto-linked twitter:image when user declared twitter.images
455
+ if (
456
+ hasUserTwitterImage &&
457
+ autoLink.tag === 'meta' &&
458
+ 'name' in autoLink &&
459
+ autoLink.name === 'twitter:image'
460
+ )
461
+ continue;
453
462
  if (autoLink.tag === 'link') {
454
463
  const attrs: Record<string, string> = { rel: autoLink.rel, href: autoLink.href };
455
464
  if (autoLink.type) attrs.type = autoLink.type;
@@ -1,21 +1,21 @@
1
1
  /**
2
- * Middleware-on-actions runner — runs the matched route's `middleware.ts`
3
- * chain for an action POST inside its own `runWithRequestContext` scope
4
- * and returns a `MiddlewareOutcome` value the dispatcher can translate.
2
+ * Middleware runner for revalidation — runs a route's `middleware.ts`
3
+ * chain inside its own `runWithRequestContext` scope and returns a
4
+ * `MiddlewareOutcome` value the caller can translate.
5
5
  *
6
- * Lifted out of `wrap-action-dispatch.ts` (TIM-853) so the dispatcher
7
- * can be read top-to-bottom without scrolling past the chain runner. The
8
- * function is intentionally a free function (not a closure over deps)
9
- * so it can be unit-tested in isolation. It does not import virtual
10
- * modules and does not call into the action handler — it owns exactly
11
- * the "run middleware once" step and nothing else.
6
+ * Used by the revalidation renderer (TIM-996) to run the TARGET route's
7
+ * middleware before building the revalidated element tree, ensuring that
8
+ * `revalidatePath('/admin')` from a public action honors admin's auth.
12
9
  *
13
- * Failure modes are encoded as outcome variants rather than thrown so the
14
- * caller never has to wrap this in try/catch. The only thrown errors are
15
- * truly unexpected (programmer bugs) and propagate naturally.
10
+ * NOT used for action dispatch itself — per-segment middleware does not
11
+ * run on action POSTs (TIM-1134). Actions are dispatched by action ID,
12
+ * not route segment, so `proxy.ts` and `createActionClient({ middleware })`
13
+ * are the correct auth mechanisms.
16
14
  *
17
- * See TIM-871 for the full design rationale (closing the
18
- * CVE-2025-29927-class bug where actions silently bypass middleware).
15
+ * The function is intentionally a free function (not a closure over deps)
16
+ * so it can be unit-tested in isolation. Failure modes are encoded as
17
+ * outcome variants rather than thrown so the caller never has to wrap
18
+ * this in try/catch.
19
19
  */
20
20
 
21
21
  import { getCookiesForSsr, getSetCookieHeaders } from '../cookie-context.js';
@@ -43,7 +43,7 @@ import { loadCdnPurgeHandler } from 'virtual:timber-cache-handler';
43
43
  // @ts-expect-error — virtual module provided by timber-routing plugin
44
44
  import globalSchemaCodecs from 'virtual:timber-schema';
45
45
 
46
- import { wrapPipelineWithActionDispatch } from './wrap-action-dispatch.js';
46
+ import { wrapPipelineWithActionDispatch, buildActionDispatcher } from './wrap-action-dispatch.js';
47
47
  import type { BodyLimitsConfig } from '../body-limits.js';
48
48
  import type { BuildManifest } from '../build-manifest.js';
49
49
  import { resolveLogMode } from '../../dev-tools/logger.js';
@@ -199,6 +199,8 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
199
199
  | { disabled: boolean; enableHMRInDev: boolean }
200
200
  | undefined;
201
201
  const clientJsDisabled = clientJsConfig?.disabled ?? false;
202
+ const clientSegmentCache =
203
+ ((runtimeConfig as Record<string, unknown>).clientSegmentCache as boolean) ?? false;
202
204
  const clientBootstrap = buildClientScripts({
203
205
  ...runtimeConfig,
204
206
  clientJavascript: clientJsConfig ?? { disabled: false, enableHMRInDev: false },
@@ -295,6 +297,7 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
295
297
  {
296
298
  clientBootstrap,
297
299
  clientJsDisabled,
300
+ clientSegmentCache,
298
301
  rootSegment: manifest.root,
299
302
  buildManifest: typedBuildManifest,
300
303
  globalError: manifest.globalError,
@@ -386,19 +389,36 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
386
389
  // and no user-authored sitemap exists at the app root.
387
390
  // See design/16-metadata.md §"Auto-generated Sitemap"
388
391
  autoSitemapHandler: createAutoSitemapHandler(manifest.root, runtimeConfig),
392
+
393
+ // Server action dispatch — runs after proxy.ts, before route matching
394
+ // (TIM-1213). Moved into the pipeline so proxy.ts runs on action POSTs,
395
+ // matching the design doc: "proxy.ts runs on every request, no exclusions."
396
+ // Per-segment `middleware.ts` does NOT run on action POSTs (TIM-1134).
397
+ dispatchAction: buildActionDispatcher({
398
+ csrfConfig: {
399
+ csrf: runtimeConfig.csrf,
400
+ allowedOrigins: (runtimeConfig as Record<string, unknown>).allowedOrigins as
401
+ | string[]
402
+ | undefined,
403
+ },
404
+ bodyLimits: (runtimeConfig as Record<string, unknown>).limits as BodyLimitsConfig['limits'],
405
+ sensitiveFields: formsConfig?.stripSensitiveFields,
406
+ matchRoute,
407
+ buildRevalidateRenderer: createRevalidateRendererFactory({
408
+ matchRoute,
409
+ coerceSegmentParams,
410
+ buildManifest: buildManifest as BuildManifest,
411
+ }),
412
+ }),
389
413
  };
390
414
 
391
415
  const pipeline = createPipeline(pipelineConfig);
392
416
 
393
- // Wrap the pipeline to enforce CSRF at the request boundary and intercept
394
- // server action requests. By default, the wrapper also runs the matched
395
- // route's `middleware.ts` chain on action POSTs, so authentication, rate
396
- // limiting, tenant isolation, and request-header injection apply to
397
- // actions just like they do to page renders. See TIM-871 and
398
- // design/08-forms-and-actions.md §"Middleware for Server Actions".
399
- //
400
- // CSRF validation lives in the wrapper (not inside the action handler) so
401
- // it covers route.ts API handlers as well as server actions. See LOCAL-773.
417
+ // Wrap the pipeline to enforce CSRF at the request boundary.
418
+ // CSRF validation lives in the wrapper (not inside the pipeline) so it
419
+ // runs before proxy.ts and covers every unsafe-method request. Server
420
+ // action dispatch has moved into the pipeline (TIM-1213) so proxy.ts
421
+ // runs on action POSTs. See LOCAL-773.
402
422
  const csrfConfig = {
403
423
  csrf: runtimeConfig.csrf,
404
424
  allowedOrigins: (runtimeConfig as Record<string, unknown>).allowedOrigins as
@@ -406,38 +426,8 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
406
426
  | undefined,
407
427
  };
408
428
 
409
- const actionsConfig = (runtimeConfig as Record<string, unknown>).actions as
410
- | { runMiddleware?: boolean }
411
- | undefined;
412
-
413
429
  const handler = wrapPipelineWithActionDispatch(pipeline, {
414
430
  csrfConfig,
415
- bodyLimits: (runtimeConfig as Record<string, unknown>).limits as BodyLimitsConfig['limits'],
416
- sensitiveFields: formsConfig?.stripSensitiveFields,
417
- matchRoute,
418
- coerceSegmentParams,
419
- renderDenyFallback: pipelineConfig.renderDenyFallback,
420
- runMiddleware: actionsConfig?.runMiddleware,
421
- // Thread the pipeline's `stripTrailingSlash` setting into the wrapper
422
- // so its canonicalization step (applied before `matchRoute` for
423
- // action POSTs) matches the page pipeline exactly. Defaults to `true`
424
- // both here and inside the wrapper.
425
- stripTrailingSlash: pipelineConfig.stripTrailingSlash,
426
- // Revalidation renderer — runs the target route's `middleware.ts`
427
- // chain BEFORE building the element tree so a server action that
428
- // revalidates a protected path honors that path's auth, tenant
429
- // isolation, and request-header injection. Closes the silent-bypass
430
- // bug where `revalidatePath('/admin')` from a public action would
431
- // serialize the admin RSC tree without ever consulting admin's
432
- // middleware. Also validates the user-supplied `path` as a local
433
- // root-relative URL to reject `revalidatePath('//evil.com/admin')`
434
- // and similar scheme/authority injections. See TIM-996 and
435
- // design/08-forms-and-actions.md §"revalidatePath()".
436
- buildRevalidateRenderer: createRevalidateRendererFactory({
437
- matchRoute,
438
- coerceSegmentParams,
439
- buildManifest: buildManifest as BuildManifest,
440
- }),
441
431
  });
442
432
 
443
433
  return { handler, pipelineConfig };
@@ -57,6 +57,7 @@ import { callSsr } from './ssr-bridge.js';
57
57
  export interface RenderRouteDeps {
58
58
  clientBootstrap: ClientBootstrapConfig;
59
59
  clientJsDisabled: boolean;
60
+ clientSegmentCache: boolean;
60
61
  rootSegment: ManifestSegmentNode;
61
62
  buildManifest: BuildManifest;
62
63
  globalError?: { load: () => Promise<unknown>; filePath: string };
@@ -95,7 +96,8 @@ export async function renderRoute(
95
96
  // skips re-rendering those layouts for a smaller, faster RSC payload.
96
97
  // Only used for RSC requests — HTML requests always get a full render.
97
98
  // See design/19-client-navigation.md §"X-Timber-State-Tree Header"
98
- const clientStateTree = isRscPayloadRequest(req) ? parseClientStateTree(req) : null;
99
+ const clientStateTree =
100
+ deps.clientSegmentCache && isRscPayloadRequest(req) ? parseClientStateTree(req) : null;
99
101
 
100
102
  // Build the React element tree — runs access checks eagerly to gate
101
103
  // metadata resolution (TIM-1027), then loads modules and resolves