@timber-js/app 0.2.0-alpha.4 → 0.2.0-alpha.41
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.
- package/LICENSE +8 -0
- package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-Ba7URUIn.js} +1 -1
- package/dist/_chunks/als-registry-Ba7URUIn.js.map +1 -0
- package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
- package/dist/_chunks/debug-ECi_61pb.js +108 -0
- package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
- package/dist/_chunks/define-cookie-BmKbSyp0.js +93 -0
- package/dist/_chunks/define-cookie-BmKbSyp0.js.map +1 -0
- package/dist/_chunks/error-boundary-BAN3751q.js +211 -0
- package/dist/_chunks/error-boundary-BAN3751q.js.map +1 -0
- package/dist/_chunks/{format-CwdaB0_2.js → format-cX7wzEp2.js} +2 -2
- package/dist/_chunks/{format-CwdaB0_2.js.map → format-cX7wzEp2.js.map} +1 -1
- package/dist/_chunks/{interception-BOoWmLUA.js → interception-D2djYaIm.js} +112 -77
- package/dist/_chunks/interception-D2djYaIm.js.map +1 -0
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-BU684ls2.js} +1 -1
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-BU684ls2.js.map} +1 -1
- package/dist/_chunks/{request-context-CZJi4CuK.js → request-context-BxYIJM24.js} +93 -69
- package/dist/_chunks/request-context-BxYIJM24.js.map +1 -0
- package/dist/_chunks/segment-context-C6byCyZU.js +69 -0
- package/dist/_chunks/segment-context-C6byCyZU.js.map +1 -0
- package/dist/_chunks/stale-reload-C0ValzG7.js +47 -0
- package/dist/_chunks/stale-reload-C0ValzG7.js.map +1 -0
- package/dist/_chunks/{tracing-Cwn7697K.js → tracing-CuXiCP5p.js} +17 -3
- package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-CuXiCP5p.js.map} +1 -1
- package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-BvW0TKDn.js} +1 -1
- package/dist/_chunks/{use-query-states-D5KaffOK.js.map → use-query-states-BvW0TKDn.js.map} +1 -1
- package/dist/_chunks/wrappers-C6J0nNji.js +331 -0
- package/dist/_chunks/wrappers-C6J0nNji.js.map +1 -0
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/nitro.d.ts +17 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +56 -13
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/fast-hash.d.ts +22 -0
- package/dist/cache/fast-hash.d.ts.map +1 -0
- package/dist/cache/index.d.ts +5 -2
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +88 -18
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/register-cached-function.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts +18 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +10 -1
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -125
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +213 -93
- package/dist/client/index.js.map +1 -1
- package/dist/client/link.d.ts +22 -8
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +2 -2
- package/dist/client/router.d.ts +25 -3
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +23 -2
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +1 -1
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-context.d.ts +1 -1
- package/dist/client/segment-context.d.ts.map +1 -1
- package/dist/client/segment-merger.d.ts.map +1 -1
- package/dist/client/stale-reload.d.ts +15 -0
- package/dist/client/stale-reload.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +1 -1
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/transition-root.d.ts +1 -1
- package/dist/client/transition-root.d.ts.map +1 -1
- package/dist/client/use-params.d.ts +2 -2
- package/dist/client/use-params.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts +1 -1
- package/dist/codec.d.ts +21 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/cookies/define-cookie.d.ts +33 -12
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.js +1 -83
- package/dist/fonts/css.d.ts +1 -0
- package/dist/fonts/css.d.ts.map +1 -1
- package/dist/fonts/local.d.ts +4 -2
- package/dist/fonts/local.d.ts.map +1 -1
- package/dist/index.d.ts +112 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +635 -233
- package/dist/index.js.map +1 -1
- package/dist/params/define.d.ts +76 -0
- package/dist/params/define.d.ts.map +1 -0
- package/dist/params/index.d.ts +8 -0
- package/dist/params/index.d.ts.map +1 -0
- package/dist/params/index.js +104 -0
- package/dist/params/index.js.map +1 -0
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/client-chunks.d.ts +32 -0
- package/dist/plugins/client-chunks.d.ts.map +1 -0
- package/dist/plugins/dev-error-overlay.d.ts +26 -1
- package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +7 -0
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +9 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/mdx.d.ts +6 -0
- package/dist/plugins/mdx.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen.d.ts +2 -2
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/index.js +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/status-file-lint.d.ts +2 -1
- package/dist/routing/status-file-lint.d.ts.map +1 -1
- package/dist/routing/types.d.ts +6 -4
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/rsc-runtime/rsc.d.ts +1 -1
- package/dist/rsc-runtime/rsc.d.ts.map +1 -1
- package/dist/rsc-runtime/ssr.d.ts +12 -0
- package/dist/rsc-runtime/ssr.d.ts.map +1 -1
- package/dist/search-params/codecs.d.ts +1 -1
- package/dist/search-params/define.d.ts +153 -0
- package/dist/search-params/define.d.ts.map +1 -0
- package/dist/search-params/index.d.ts +4 -5
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +3 -474
- package/dist/search-params/registry.d.ts +1 -1
- package/dist/search-params/wrappers.d.ts +53 -0
- package/dist/search-params/wrappers.d.ts.map +1 -0
- package/dist/server/access-gate.d.ts +4 -0
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/action-encryption.d.ts +76 -0
- package/dist/server/action-encryption.d.ts.map +1 -0
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +18 -4
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +2 -2
- package/dist/server/debug.d.ts +46 -15
- package/dist/server/debug.d.ts.map +1 -1
- package/dist/server/default-logger.d.ts +22 -0
- package/dist/server/default-logger.d.ts.map +1 -0
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/early-hints.d.ts +13 -5
- package/dist/server/early-hints.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +4 -0
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/flight-injection-state.d.ts +78 -0
- package/dist/server/flight-injection-state.d.ts.map +1 -0
- package/dist/server/flight-scripts.d.ts +39 -0
- package/dist/server/flight-scripts.d.ts.map +1 -0
- package/dist/server/flush.d.ts.map +1 -1
- package/dist/server/form-data.d.ts +29 -0
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +5 -11
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1975 -1649
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +24 -7
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +77 -0
- package/dist/server/node-stream-transforms.d.ts.map +1 -0
- package/dist/server/pipeline.d.ts +7 -4
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +30 -3
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/render-timeout.d.ts +51 -0
- package/dist/server/render-timeout.d.ts.map +1 -0
- package/dist/server/request-context.d.ts +65 -38
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +7 -0
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-handler.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +2 -2
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +46 -3
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +6 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +9 -0
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/slot-resolver.d.ts +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts +22 -0
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts +39 -21
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/dist/server/tracing.d.ts +10 -0
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +19 -12
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -3
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/version-skew.d.ts +61 -0
- package/dist/server/version-skew.d.ts.map +1 -0
- package/dist/server/waituntil-bridge.d.ts.map +1 -1
- package/dist/shared/merge-search-params.d.ts +22 -0
- package/dist/shared/merge-search-params.d.ts.map +1 -0
- package/dist/shims/navigation-client.d.ts +1 -1
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +1 -1
- package/dist/shims/navigation.d.ts.map +1 -1
- package/dist/utils/state-machine.d.ts +80 -0
- package/dist/utils/state-machine.d.ts.map +1 -0
- package/package.json +17 -14
- package/src/adapters/compress-module.ts +24 -4
- package/src/adapters/nitro.ts +58 -9
- package/src/cache/fast-hash.ts +34 -0
- package/src/cache/index.ts +5 -2
- package/src/cache/register-cached-function.ts +7 -3
- package/src/cache/singleflight.ts +62 -4
- package/src/cache/timber-cache.ts +34 -26
- package/src/cli.ts +0 -0
- package/src/client/browser-entry.ts +94 -90
- package/src/client/error-boundary.tsx +18 -1
- package/src/client/index.ts +10 -1
- package/src/client/link.tsx +78 -19
- package/src/client/navigation-context.ts +2 -2
- package/src/client/router.ts +105 -60
- package/src/client/rsc-fetch.ts +63 -2
- package/src/client/segment-cache.ts +1 -1
- package/src/client/segment-context.ts +6 -1
- package/src/client/segment-merger.ts +2 -8
- package/src/client/stale-reload.ts +32 -6
- package/src/client/top-loader.tsx +10 -9
- package/src/client/transition-root.tsx +7 -1
- package/src/client/use-params.ts +3 -3
- package/src/client/use-query-states.ts +1 -1
- package/src/codec.ts +21 -0
- package/src/cookies/define-cookie.ts +69 -18
- package/src/fonts/css.ts +2 -1
- package/src/fonts/local.ts +7 -3
- package/src/index.ts +280 -85
- package/src/params/define.ts +260 -0
- package/src/params/index.ts +28 -0
- package/src/plugins/adapter-build.ts +6 -0
- package/src/plugins/build-manifest.ts +11 -0
- package/src/plugins/client-chunks.ts +65 -0
- package/src/plugins/dev-error-overlay.ts +70 -1
- package/src/plugins/dev-server.ts +38 -4
- package/src/plugins/entries.ts +12 -11
- package/src/plugins/fonts.ts +171 -19
- package/src/plugins/mdx.ts +9 -5
- package/src/plugins/routing.ts +40 -14
- package/src/plugins/server-bundle.ts +32 -1
- package/src/plugins/shims.ts +1 -1
- package/src/plugins/static-build.ts +8 -4
- package/src/routing/codegen.ts +109 -88
- package/src/routing/scanner.ts +55 -6
- package/src/routing/status-file-lint.ts +2 -1
- package/src/routing/types.ts +7 -4
- package/src/rsc-runtime/rsc.ts +2 -0
- package/src/rsc-runtime/ssr.ts +50 -0
- package/src/rsc-runtime/vendor-types.d.ts +7 -0
- package/src/search-params/codecs.ts +1 -1
- package/src/search-params/define.ts +504 -0
- package/src/search-params/index.ts +12 -18
- package/src/search-params/registry.ts +1 -1
- package/src/search-params/wrappers.ts +85 -0
- package/src/server/access-gate.tsx +40 -9
- package/src/server/action-client.ts +14 -5
- package/src/server/action-encryption.ts +144 -0
- package/src/server/action-handler.ts +19 -2
- package/src/server/als-registry.ts +18 -4
- package/src/server/build-manifest.ts +4 -4
- package/src/server/compress.ts +25 -7
- package/src/server/debug.ts +55 -17
- package/src/server/default-logger.ts +98 -0
- package/src/server/deny-renderer.ts +2 -1
- package/src/server/early-hints.ts +36 -15
- package/src/server/error-boundary-wrapper.ts +57 -14
- package/src/server/flight-injection-state.ts +152 -0
- package/src/server/flight-scripts.ts +59 -0
- package/src/server/flush.ts +2 -1
- package/src/server/form-data.ts +76 -0
- package/src/server/html-injectors.ts +103 -66
- package/src/server/index.ts +9 -4
- package/src/server/logger.ts +38 -35
- package/src/server/node-stream-transforms.ts +381 -0
- package/src/server/pipeline.ts +131 -39
- package/src/server/primitives.ts +47 -5
- package/src/server/render-timeout.ts +108 -0
- package/src/server/request-context.ts +112 -119
- package/src/server/route-element-builder.ts +106 -114
- package/src/server/route-handler.ts +2 -1
- package/src/server/route-matcher.ts +2 -2
- package/src/server/rsc-entry/error-renderer.ts +5 -3
- package/src/server/rsc-entry/helpers.ts +122 -3
- package/src/server/rsc-entry/index.ts +125 -49
- package/src/server/rsc-entry/rsc-payload.ts +52 -12
- package/src/server/rsc-entry/rsc-stream.ts +33 -8
- package/src/server/rsc-entry/ssr-renderer.ts +40 -13
- package/src/server/slot-resolver.ts +199 -210
- package/src/server/ssr-entry.ts +168 -22
- package/src/server/ssr-render.ts +289 -67
- package/src/server/tracing.ts +23 -0
- package/src/server/tree-builder.ts +91 -57
- package/src/server/types.ts +1 -3
- package/src/server/version-skew.ts +104 -0
- package/src/server/waituntil-bridge.ts +4 -1
- package/src/shared/merge-search-params.ts +48 -0
- package/src/shims/navigation-client.ts +1 -1
- package/src/shims/navigation.ts +1 -1
- package/src/utils/state-machine.ts +111 -0
- package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
- package/dist/_chunks/debug-B4WUeqJ-.js +0 -75
- package/dist/_chunks/debug-B4WUeqJ-.js.map +0 -1
- package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
- package/dist/_chunks/request-context-CZJi4CuK.js.map +0 -1
- package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
- package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
- package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
- package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
- package/dist/client/error-boundary.js.map +0 -1
- package/dist/cookies/index.js.map +0 -1
- package/dist/plugins/dynamic-transform.d.ts +0 -72
- package/dist/plugins/dynamic-transform.d.ts.map +0 -1
- package/dist/search-params/analyze.d.ts +0 -54
- package/dist/search-params/analyze.d.ts.map +0 -1
- package/dist/search-params/builtin-codecs.d.ts +0 -105
- package/dist/search-params/builtin-codecs.d.ts.map +0 -1
- package/dist/search-params/create.d.ts +0 -106
- package/dist/search-params/create.d.ts.map +0 -1
- package/dist/search-params/index.js.map +0 -1
- package/dist/server/prerender.d.ts +0 -77
- package/dist/server/prerender.d.ts.map +0 -1
- package/dist/server/response-cache.d.ts +0 -53
- package/dist/server/response-cache.d.ts.map +0 -1
- package/src/plugins/dynamic-transform.ts +0 -161
- package/src/search-params/analyze.ts +0 -192
- package/src/search-params/builtin-codecs.ts +0 -228
- package/src/search-params/create.ts +0 -321
- package/src/server/prerender.ts +0 -139
- package/src/server/response-cache.ts +0 -277
|
@@ -47,7 +47,11 @@ import { buildClientScripts } from '#/server/html-injectors.js';
|
|
|
47
47
|
import type { InterceptionContext, PipelineConfig, RouteMatch } from '#/server/pipeline.js';
|
|
48
48
|
import { createPipeline } from '#/server/pipeline.js';
|
|
49
49
|
import { DenySignal, RedirectSignal } from '#/server/primitives.js';
|
|
50
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
buildRouteElement,
|
|
52
|
+
RouteSignalWithContext,
|
|
53
|
+
ParamCoercionError,
|
|
54
|
+
} from '#/server/route-element-builder.js';
|
|
51
55
|
import type { ManifestSegmentNode } from '#/server/route-matcher.js';
|
|
52
56
|
import { createMetadataRouteMatcher, createRouteMatcher } from '#/server/route-matcher.js';
|
|
53
57
|
import { initDevTracing } from '#/server/tracing.js';
|
|
@@ -61,33 +65,62 @@ import {
|
|
|
61
65
|
createDebugChannelSink,
|
|
62
66
|
escapeHtml,
|
|
63
67
|
isRscPayloadRequest,
|
|
68
|
+
type DebugComponentEntry,
|
|
64
69
|
} from './helpers.js';
|
|
65
70
|
import { parseClientStateTree } from '#/server/state-tree-diff.js';
|
|
66
|
-
import {
|
|
67
|
-
createResponseCache,
|
|
68
|
-
resolveResponseCacheConfig,
|
|
69
|
-
type ResponseCache,
|
|
70
|
-
} from '#/server/response-cache.js';
|
|
71
71
|
import { buildRscPayloadResponse } from './rsc-payload.js';
|
|
72
72
|
import { renderRscStream } from './rsc-stream.js';
|
|
73
73
|
import { renderSsrResponse } from './ssr-renderer.js';
|
|
74
74
|
import { callSsr } from './ssr-bridge.js';
|
|
75
|
-
import { isDebug, setDebugFromConfig } from '#/server/debug.js';
|
|
75
|
+
import { isDebug, isDevMode, setDebugFromConfig } from '#/server/debug.js';
|
|
76
|
+
import { recordTiming } from '#/server/server-timing.js';
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the Server-Timing mode from timber.config.ts.
|
|
80
|
+
*
|
|
81
|
+
* If the user set `serverTiming` explicitly, use that value.
|
|
82
|
+
* Otherwise: `'detailed'` in dev, `'total'` in production.
|
|
83
|
+
*/
|
|
84
|
+
function resolveServerTimingMode(
|
|
85
|
+
config: Record<string, unknown>,
|
|
86
|
+
isDev: boolean
|
|
87
|
+
): 'detailed' | 'total' | false {
|
|
88
|
+
const userValue = config.serverTiming as 'detailed' | 'total' | false | undefined;
|
|
89
|
+
if (userValue !== undefined) return userValue;
|
|
90
|
+
return isDev ? 'detailed' : 'total';
|
|
91
|
+
}
|
|
76
92
|
|
|
77
93
|
// Dev-only pipeline error handler, set by the dev server after import.
|
|
78
94
|
// In production this is always undefined — no overhead.
|
|
79
|
-
|
|
95
|
+
// The third argument provides RSC debug component data (from the Flight
|
|
96
|
+
// debug channel) when available — used by the error overlay to show the
|
|
97
|
+
// server component tree context for render errors.
|
|
98
|
+
let _devPipelineErrorHandler:
|
|
99
|
+
| ((error: Error, phase: string, debugComponents?: DebugComponentEntry[]) => void)
|
|
100
|
+
| undefined;
|
|
80
101
|
|
|
81
102
|
/**
|
|
82
103
|
* Set the dev pipeline error handler.
|
|
83
104
|
*
|
|
84
105
|
* Called by the dev server after importing this module to wire pipeline
|
|
85
106
|
* errors into the Vite browser error overlay. No-op in production.
|
|
107
|
+
*
|
|
108
|
+
* The handler receives an optional third argument with RSC debug component
|
|
109
|
+
* info — component names, environments, and stack frames from the Flight
|
|
110
|
+
* debug channel. This is only populated for render-phase errors.
|
|
86
111
|
*/
|
|
87
|
-
export function setDevPipelineErrorHandler(
|
|
112
|
+
export function setDevPipelineErrorHandler(
|
|
113
|
+
handler: (error: Error, phase: string, debugComponents?: DebugComponentEntry[]) => void
|
|
114
|
+
): void {
|
|
88
115
|
_devPipelineErrorHandler = handler;
|
|
89
116
|
}
|
|
90
117
|
|
|
118
|
+
// Dev-only: holds a getter for the current request's RSC debug components.
|
|
119
|
+
// Updated on each renderRscStream call so the onPipelineError callback can
|
|
120
|
+
// include component tree context for render-phase errors. This is request-
|
|
121
|
+
// scoped by convention — each renderRoute call sets it before returning.
|
|
122
|
+
let _lastDebugComponentsGetter: (() => DebugComponentEntry[]) | undefined;
|
|
123
|
+
|
|
91
124
|
/**
|
|
92
125
|
* Create the RSC request handler from the route manifest.
|
|
93
126
|
*
|
|
@@ -101,13 +134,15 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
101
134
|
// See design/17-logging.md §"register() — Server Startup"
|
|
102
135
|
await loadInstrumentation(loadUserInstrumentation);
|
|
103
136
|
|
|
104
|
-
// Initialize
|
|
105
|
-
|
|
106
|
-
|
|
137
|
+
// Initialize deployment ID for version skew detection (TIM-446).
|
|
138
|
+
// The manifest init module sets globalThis.__TIMBER_DEPLOYMENT_ID__ at startup.
|
|
139
|
+
// In dev mode this is undefined — skew checks are skipped.
|
|
140
|
+
const deploymentId = (globalThis as Record<string, unknown>).__TIMBER_DEPLOYMENT_ID__ as
|
|
141
|
+
| string
|
|
107
142
|
| undefined;
|
|
108
|
-
if (
|
|
109
|
-
const {
|
|
110
|
-
|
|
143
|
+
if (deploymentId) {
|
|
144
|
+
const { setDeploymentId } = await import('#/server/version-skew.js');
|
|
145
|
+
setDeploymentId(deploymentId);
|
|
111
146
|
}
|
|
112
147
|
|
|
113
148
|
const matchRoute = createRouteMatcher(manifest);
|
|
@@ -127,9 +162,6 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
127
162
|
buildManifest: buildManifest as BuildManifest,
|
|
128
163
|
});
|
|
129
164
|
|
|
130
|
-
// Dev logging — initialize OTEL-based dev tracing once at handler creation.
|
|
131
|
-
// In production, isDev is false — no tracing, no overhead.
|
|
132
|
-
// The DevSpanProcessor handles all formatting and stderr output.
|
|
133
165
|
// Initialize debug flag from config before anything else.
|
|
134
166
|
// This allows timber.config.ts `debug: true` to enable debug logging
|
|
135
167
|
// in production without the TIMBER_DEBUG env var.
|
|
@@ -137,10 +169,24 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
137
169
|
setDebugFromConfig(true);
|
|
138
170
|
}
|
|
139
171
|
|
|
140
|
-
|
|
172
|
+
// Two separate flags for two different security levels:
|
|
173
|
+
//
|
|
174
|
+
// isDev (isDevMode) — gates client-visible behavior: dev error pages with
|
|
175
|
+
// stack traces, detailed Server-Timing headers, error messages in action
|
|
176
|
+
// payloads. Statically replaced in production → tree-shaken to zero.
|
|
177
|
+
// TIMBER_DEBUG cannot enable this.
|
|
178
|
+
//
|
|
179
|
+
// debugEnabled (isDebug) — gates server-side logging only: stderr warnings,
|
|
180
|
+
// OTEL dev tracing, console.error fallbacks. TIMBER_DEBUG enables this.
|
|
181
|
+
// Never affects what clients see.
|
|
182
|
+
const isDev = isDevMode();
|
|
183
|
+
const debugEnabled = isDebug();
|
|
141
184
|
const slowPhaseMs = (runtimeConfig as Record<string, unknown>).slowPhaseMs as number | undefined;
|
|
142
185
|
|
|
143
|
-
|
|
186
|
+
// Dev logging — initialize OTEL-based dev tracing once at handler creation.
|
|
187
|
+
// In production with TIMBER_DEBUG, this enables server-side tracing output
|
|
188
|
+
// without exposing anything to clients.
|
|
189
|
+
if (debugEnabled) {
|
|
144
190
|
const devLogMode = resolveLogMode();
|
|
145
191
|
if (devLogMode !== 'quiet') {
|
|
146
192
|
await initDevTracing({ mode: devLogMode, slowPhaseMs });
|
|
@@ -153,17 +199,6 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
153
199
|
|
|
154
200
|
const typedBuildManifest = buildManifest as BuildManifest;
|
|
155
201
|
|
|
156
|
-
// Initialize response-level caching and singleflight deduplication.
|
|
157
|
-
// See design/31-benchmarking.md for performance motivation.
|
|
158
|
-
const responseCacheRaw = (runtimeConfig as Record<string, unknown>).responseCache as
|
|
159
|
-
| { maxSize?: number; ttlMs?: number; publicOnly?: boolean }
|
|
160
|
-
| false
|
|
161
|
-
| undefined;
|
|
162
|
-
const responseCacheConfig = resolveResponseCacheConfig(responseCacheRaw);
|
|
163
|
-
const responseCache: ResponseCache | null = responseCacheConfig
|
|
164
|
-
? createResponseCache(responseCacheConfig)
|
|
165
|
-
: null;
|
|
166
|
-
|
|
167
202
|
const pipelineConfig: PipelineConfig = {
|
|
168
203
|
proxyLoader: manifest.proxy?.load,
|
|
169
204
|
matchRoute,
|
|
@@ -194,17 +229,15 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
194
229
|
_requestHeaderOverlay: Headers,
|
|
195
230
|
interception?: InterceptionContext
|
|
196
231
|
) => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
return doRender();
|
|
232
|
+
return renderRoute(
|
|
233
|
+
req,
|
|
234
|
+
match,
|
|
235
|
+
responseHeaders,
|
|
236
|
+
clientBootstrap,
|
|
237
|
+
clientJsDisabled,
|
|
238
|
+
interception,
|
|
239
|
+
manifest.root
|
|
240
|
+
);
|
|
208
241
|
},
|
|
209
242
|
renderNoMatch: async (req: Request, responseHeaders: Headers) => {
|
|
210
243
|
return renderNoMatchPage(req, manifest.root, responseHeaders, clientBootstrap);
|
|
@@ -213,10 +246,18 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
213
246
|
// Slow request threshold from timber.config.ts. Default 3000ms, 0 to disable.
|
|
214
247
|
// See design/17-logging.md §"slowRequestMs"
|
|
215
248
|
slowRequestMs: (runtimeConfig as Record<string, unknown>).slowRequestMs as number | undefined,
|
|
216
|
-
|
|
249
|
+
serverTiming: resolveServerTimingMode(runtimeConfig, isDev),
|
|
217
250
|
onPipelineError: isDev
|
|
218
251
|
? (error: Error, phase: string) => {
|
|
219
|
-
if (_devPipelineErrorHandler)
|
|
252
|
+
if (_devPipelineErrorHandler) {
|
|
253
|
+
// For render-phase errors, include RSC debug component data
|
|
254
|
+
// from the Flight debug channel (if available from the current request).
|
|
255
|
+
const debugComponents =
|
|
256
|
+
phase === 'render' && _lastDebugComponentsGetter
|
|
257
|
+
? _lastDebugComponentsGetter()
|
|
258
|
+
: undefined;
|
|
259
|
+
_devPipelineErrorHandler(error, phase, debugComponents);
|
|
260
|
+
}
|
|
220
261
|
}
|
|
221
262
|
: undefined,
|
|
222
263
|
renderFallbackError: (error, req, responseHeaders) =>
|
|
@@ -296,7 +337,8 @@ async function renderRoute(
|
|
|
296
337
|
responseHeaders: Headers,
|
|
297
338
|
clientBootstrap: ClientBootstrapConfig,
|
|
298
339
|
clientJsDisabled: boolean,
|
|
299
|
-
interception?: InterceptionContext
|
|
340
|
+
interception?: InterceptionContext,
|
|
341
|
+
rootSegment?: ManifestSegmentNode
|
|
300
342
|
): Promise<Response> {
|
|
301
343
|
const segments = match.segments as unknown as ManifestSegmentNode[];
|
|
302
344
|
const leaf = segments[segments.length - 1];
|
|
@@ -318,6 +360,7 @@ async function renderRoute(
|
|
|
318
360
|
// Build the React element tree — loads modules, runs access checks,
|
|
319
361
|
// resolves metadata. DenySignal/RedirectSignal propagate for HTTP handling.
|
|
320
362
|
let routeResult;
|
|
363
|
+
const _buildStart = performance.now();
|
|
321
364
|
try {
|
|
322
365
|
routeResult = await buildRouteElement(_req, match, interception, clientStateTree);
|
|
323
366
|
} catch (error) {
|
|
@@ -350,14 +393,29 @@ async function renderRoute(
|
|
|
350
393
|
return buildRedirectResponse(_req, signal, responseHeaders);
|
|
351
394
|
}
|
|
352
395
|
}
|
|
353
|
-
//
|
|
396
|
+
// Param coercion failed — render the custom 404 page (status files / not-found).
|
|
397
|
+
// Previously returned a bare Response(null, { status: 404 }) which bypassed
|
|
398
|
+
// custom not-found pages. Now routes through renderNoMatchPage so apps with
|
|
399
|
+
// 404.tsx / not-found status files render their custom page.
|
|
400
|
+
if (error instanceof ParamCoercionError) {
|
|
401
|
+
return renderNoMatchPage(_req, rootSegment!, responseHeaders, clientBootstrap);
|
|
402
|
+
}
|
|
403
|
+
// No PageComponent found — same treatment as param coercion: render custom 404.
|
|
354
404
|
if (error instanceof Error && error.message.startsWith('No page component')) {
|
|
355
|
-
return
|
|
405
|
+
return renderNoMatchPage(_req, rootSegment!, responseHeaders, clientBootstrap);
|
|
356
406
|
}
|
|
357
407
|
throw error;
|
|
358
408
|
}
|
|
359
409
|
|
|
360
|
-
const
|
|
410
|
+
const _buildEnd = performance.now();
|
|
411
|
+
recordTiming({
|
|
412
|
+
name: 'build',
|
|
413
|
+
dur: Math.round(_buildEnd - _buildStart),
|
|
414
|
+
desc: 'build element tree',
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const { element, headElements, layoutComponents, deferSuspenseFor, skippedSegments } =
|
|
418
|
+
routeResult;
|
|
361
419
|
|
|
362
420
|
// Build head HTML for injection into the SSR output.
|
|
363
421
|
// Collects CSS, fonts, and modulepreload from the build manifest for matched segments.
|
|
@@ -374,6 +432,14 @@ async function renderRoute(
|
|
|
374
432
|
headHtml += buildCssLinkTags(cssUrls);
|
|
375
433
|
}
|
|
376
434
|
|
|
435
|
+
// Inline font CSS as a <style> tag — @font-face rules and scoped classes.
|
|
436
|
+
// The font CSS is set on globalThis by the transformed font file's
|
|
437
|
+
// side-effect import of virtual:timber-font-css-register.
|
|
438
|
+
const fontCss = (globalThis as Record<string, unknown>).__timber_font_css as string | undefined;
|
|
439
|
+
if (fontCss) {
|
|
440
|
+
headHtml += `<style data-timber-fonts>${fontCss}</style>`;
|
|
441
|
+
}
|
|
442
|
+
|
|
377
443
|
const fontEntries = collectRouteFonts(segments, typedManifest);
|
|
378
444
|
if (fontEntries.length > 0) {
|
|
379
445
|
headHtml += buildFontPreloadTags(fontEntries);
|
|
@@ -400,7 +466,17 @@ async function renderRoute(
|
|
|
400
466
|
}
|
|
401
467
|
|
|
402
468
|
// Render to RSC Flight stream with signal tracking.
|
|
403
|
-
const
|
|
469
|
+
const _rscStart = performance.now();
|
|
470
|
+
const { rscStream, signals, getDebugComponents } = renderRscStream(element, _req);
|
|
471
|
+
|
|
472
|
+
// Store the debug components getter so onPipelineError can include
|
|
473
|
+
// component tree context for render-phase errors (dev mode only).
|
|
474
|
+
_lastDebugComponentsGetter = getDebugComponents;
|
|
475
|
+
recordTiming({
|
|
476
|
+
name: 'rsc-init',
|
|
477
|
+
dur: Math.round(performance.now() - _rscStart),
|
|
478
|
+
desc: 'RSC stream init',
|
|
479
|
+
});
|
|
404
480
|
|
|
405
481
|
// Synchronous redirect — redirect() in access.ts or a non-async component
|
|
406
482
|
// throws during renderToReadableStream creation. Return HTTP redirect.
|
|
@@ -45,18 +45,45 @@ export async function buildRscPayloadResponse(
|
|
|
45
45
|
skippedSegments?: string[]
|
|
46
46
|
): Promise<Response> {
|
|
47
47
|
// Read the first chunk from the RSC stream before committing headers.
|
|
48
|
+
// Race the first read against signal detection — if an async component
|
|
49
|
+
// throws a RedirectSignal or DenySignal, the onError callback fires
|
|
50
|
+
// signals.onSignal() and we can react immediately without waiting for
|
|
51
|
+
// the full macrotask queue.
|
|
52
|
+
//
|
|
53
|
+
// The rejection chain for an async-wrapped page component:
|
|
54
|
+
// 1. PageComponent throws RedirectSignal
|
|
55
|
+
// 2. withSpan catches and re-throws (microtask 1)
|
|
56
|
+
// 3. TracedPage promise rejects (microtask 2)
|
|
57
|
+
// 4. React Flight rejection handler → onError (microtask 3+)
|
|
58
|
+
//
|
|
59
|
+
// Promise.race reacts the instant onError fires, eliminating the
|
|
60
|
+
// per-request setTimeout(0) macrotask delay for the common case
|
|
61
|
+
// (no signal). A 50ms ceiling timeout guards against edge cases
|
|
62
|
+
// where onError never fires.
|
|
48
63
|
const reader = rscStream.getReader();
|
|
49
|
-
const
|
|
64
|
+
const signalDetected = new Promise<void>((resolve) => {
|
|
65
|
+
signals.onSignal = resolve;
|
|
66
|
+
});
|
|
50
67
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
type RaceResult =
|
|
69
|
+
| { type: 'data'; chunk: ReadableStreamReadResult<Uint8Array> }
|
|
70
|
+
| { type: 'signal' };
|
|
71
|
+
|
|
72
|
+
const first: RaceResult = await Promise.race([
|
|
73
|
+
reader.read().then((chunk) => ({ type: 'data' as const, chunk })),
|
|
74
|
+
signalDetected.then(() => ({ type: 'signal' as const })),
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
// If data arrived first, still check signals — they may have fired
|
|
78
|
+
// concurrently. Also do a final ceiling timeout check for edge cases
|
|
79
|
+
// where the signal fires just after the first read resolves.
|
|
80
|
+
if (first.type === 'data' && !signals.redirectSignal && !signals.denySignal) {
|
|
81
|
+
// Brief yield to let any in-flight microtask rejections complete.
|
|
82
|
+
await new Promise<void>((r) => setTimeout(r, 0));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Detach the callback — no longer needed after this point.
|
|
86
|
+
signals.onSignal = undefined;
|
|
60
87
|
|
|
61
88
|
// Check for redirect/deny signals detected during initial rendering
|
|
62
89
|
const trackedRedirect = signals.redirectSignal as RedirectSignal | null;
|
|
@@ -75,6 +102,19 @@ export async function buildRscPayloadResponse(
|
|
|
75
102
|
);
|
|
76
103
|
}
|
|
77
104
|
|
|
105
|
+
// Extract the first chunk from the race result.
|
|
106
|
+
// If the signal won the race but neither redirect nor deny was detected
|
|
107
|
+
// (edge case), cancel the reader immediately rather than issuing a bare
|
|
108
|
+
// read() that could hang forever if the RSC stream has stalled.
|
|
109
|
+
// See TIM-519.
|
|
110
|
+
let firstRead: ReadableStreamReadResult<Uint8Array>;
|
|
111
|
+
if (first.type === 'data') {
|
|
112
|
+
firstRead = first.chunk;
|
|
113
|
+
} else {
|
|
114
|
+
await reader.cancel();
|
|
115
|
+
firstRead = { done: true, value: undefined };
|
|
116
|
+
}
|
|
117
|
+
|
|
78
118
|
// Reconstruct the stream: prepend the buffered first chunk,
|
|
79
119
|
// then continue piping from the original reader.
|
|
80
120
|
const patchedStream = new ReadableStream<Uint8Array>({
|
|
@@ -123,8 +163,8 @@ export async function buildRscPayloadResponse(
|
|
|
123
163
|
responseHeaders.set('X-Timber-Skipped-Segments', JSON.stringify(skippedSegments));
|
|
124
164
|
}
|
|
125
165
|
|
|
126
|
-
// Send route params so the client can populate
|
|
127
|
-
// SPA navigation. Without this,
|
|
166
|
+
// Send route params so the client can populate useSegmentParams() after
|
|
167
|
+
// SPA navigation. Without this, useSegmentParams() returns {}.
|
|
128
168
|
if (Object.keys(match.params).length > 0) {
|
|
129
169
|
responseHeaders.set('X-Timber-Params', JSON.stringify(match.params));
|
|
130
170
|
}
|
|
@@ -16,24 +16,38 @@ import { logRenderError } from '#/server/logger.js';
|
|
|
16
16
|
import { DenySignal, RedirectSignal, RenderError } from '#/server/primitives.js';
|
|
17
17
|
import { checkAndWarnRscPropError } from '#/server/rsc-prop-warnings.js';
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
createDebugChannelSink,
|
|
21
|
+
createDebugChannelCollector,
|
|
22
|
+
isAbortError,
|
|
23
|
+
type DebugComponentEntry,
|
|
24
|
+
} from './helpers.js';
|
|
20
25
|
import { isDebug } from '#/server/debug.js';
|
|
26
|
+
import { isDevMode } from '#/server/debug.js';
|
|
21
27
|
|
|
22
28
|
/**
|
|
23
29
|
* Mutable signal state captured during RSC rendering.
|
|
24
30
|
*
|
|
25
31
|
* Signals fire asynchronously via `onError` during stream consumption.
|
|
26
32
|
* The first signal of each type wins — subsequent signals are ignored.
|
|
33
|
+
*
|
|
34
|
+
* `onSignal` is an optional callback fired when a DenySignal or
|
|
35
|
+
* RedirectSignal is captured. Consumers use it with Promise.race to
|
|
36
|
+
* react immediately instead of polling with setTimeout/queueMicrotask.
|
|
27
37
|
*/
|
|
28
38
|
export interface RenderSignals {
|
|
29
39
|
denySignal: DenySignal | null;
|
|
30
40
|
redirectSignal: RedirectSignal | null;
|
|
31
41
|
renderError: { error: unknown; status: number } | null;
|
|
42
|
+
/** Callback fired when a redirect or deny signal is captured in onError. */
|
|
43
|
+
onSignal?: () => void;
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
export interface RscStreamResult {
|
|
35
47
|
rscStream: ReadableStream<Uint8Array> | undefined;
|
|
36
48
|
signals: RenderSignals;
|
|
49
|
+
/** Dev-only: server component debug info from the Flight debug channel. */
|
|
50
|
+
getDebugComponents?: () => DebugComponentEntry[];
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
/**
|
|
@@ -56,6 +70,10 @@ export function renderRscStream(element: React.ReactElement, req: Request): RscS
|
|
|
56
70
|
|
|
57
71
|
let rscStream: ReadableStream<Uint8Array> | undefined;
|
|
58
72
|
|
|
73
|
+
// In dev mode, collect debug channel data for the error overlay.
|
|
74
|
+
// In production, use the discard sink (no overhead).
|
|
75
|
+
const debugChannel = isDevMode() ? createDebugChannelCollector() : createDebugChannelSink();
|
|
76
|
+
|
|
59
77
|
try {
|
|
60
78
|
rscStream = renderToReadableStream(
|
|
61
79
|
element,
|
|
@@ -67,11 +85,13 @@ export function renderRscStream(element: React.ReactElement, req: Request): RscS
|
|
|
67
85
|
if (isAbortError(error) || req.signal?.aborted) return;
|
|
68
86
|
if (error instanceof DenySignal) {
|
|
69
87
|
signals.denySignal = error;
|
|
88
|
+
signals.onSignal?.();
|
|
70
89
|
// Return structured digest for client-side error boundaries
|
|
71
90
|
return JSON.stringify({ type: 'deny', status: error.status, data: error.data });
|
|
72
91
|
}
|
|
73
92
|
if (error instanceof RedirectSignal) {
|
|
74
93
|
signals.redirectSignal = error;
|
|
94
|
+
signals.onSignal?.();
|
|
75
95
|
return JSON.stringify({
|
|
76
96
|
type: 'redirect',
|
|
77
97
|
location: error.location,
|
|
@@ -98,11 +118,7 @@ export function renderRscStream(element: React.ReactElement, req: Request): RscS
|
|
|
98
118
|
// directive isn't at the very top of the file, or the component is
|
|
99
119
|
// re-exported through a barrel file without 'use client'.
|
|
100
120
|
// See LOCAL-297.
|
|
101
|
-
if (
|
|
102
|
-
isDebug() &&
|
|
103
|
-
error instanceof Error &&
|
|
104
|
-
error.message.includes('Invalid hook call')
|
|
105
|
-
) {
|
|
121
|
+
if (isDebug() && error instanceof Error && error.message.includes('Invalid hook call')) {
|
|
106
122
|
console.error(
|
|
107
123
|
'[timber] A React hook was called during RSC rendering. This usually means a ' +
|
|
108
124
|
"'use client' component is being executed as a server component instead of " +
|
|
@@ -128,7 +144,7 @@ export function renderRscStream(element: React.ReactElement, req: Request): RscS
|
|
|
128
144
|
}
|
|
129
145
|
logRenderError({ method: req.method, path: new URL(req.url).pathname, error });
|
|
130
146
|
},
|
|
131
|
-
debugChannel
|
|
147
|
+
debugChannel,
|
|
132
148
|
},
|
|
133
149
|
{
|
|
134
150
|
onClientReference(info: { id: string; name: string; deps: unknown }) {
|
|
@@ -156,5 +172,14 @@ export function renderRscStream(element: React.ReactElement, req: Request): RscS
|
|
|
156
172
|
}
|
|
157
173
|
}
|
|
158
174
|
|
|
159
|
-
return {
|
|
175
|
+
return {
|
|
176
|
+
rscStream,
|
|
177
|
+
signals,
|
|
178
|
+
// Expose the debug channel collector's getComponents in dev mode.
|
|
179
|
+
// The caller can retrieve component tree info when handling errors.
|
|
180
|
+
getDebugComponents:
|
|
181
|
+
'getComponents' in debugChannel
|
|
182
|
+
? (debugChannel as { getComponents: () => DebugComponentEntry[] }).getComponents
|
|
183
|
+
: undefined,
|
|
184
|
+
};
|
|
160
185
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { ClientBootstrapConfig } from '#/server/html-injectors.js';
|
|
16
|
+
import { flightInitScript } from '#/server/flight-scripts.js';
|
|
16
17
|
import type { LayoutEntry } from '#/server/deny-renderer.js';
|
|
17
18
|
import { renderDenyPage } from '#/server/deny-renderer.js';
|
|
18
19
|
import type { RouteMatch } from '#/server/pipeline.js';
|
|
@@ -26,11 +27,12 @@ import {
|
|
|
26
27
|
buildSegmentInfo,
|
|
27
28
|
createDebugChannelSink,
|
|
28
29
|
isAbortError,
|
|
29
|
-
parseCookiesFromHeader,
|
|
30
30
|
} from './helpers.js';
|
|
31
|
+
import { getCookiesForSsr } from '#/server/request-context.js';
|
|
31
32
|
import { renderErrorPage } from './error-renderer.js';
|
|
32
33
|
import { callSsr } from './ssr-bridge.js';
|
|
33
34
|
import type { RenderSignals } from './rsc-stream.js';
|
|
35
|
+
import { recordTiming } from '#/server/server-timing.js';
|
|
34
36
|
|
|
35
37
|
interface SsrRenderOptions {
|
|
36
38
|
req: Request;
|
|
@@ -91,8 +93,8 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
91
93
|
? ''
|
|
92
94
|
: `<script>self.__timber_segments=${JSON.stringify(buildSegmentInfo(segments, layoutComponents))}</script>`;
|
|
93
95
|
|
|
94
|
-
// Embed route params in HTML so
|
|
95
|
-
// Without this,
|
|
96
|
+
// Embed route params in HTML so useSegmentParams() works on initial hydration.
|
|
97
|
+
// Without this, useSegmentParams() returns {} until the first client navigation.
|
|
96
98
|
const paramsScript =
|
|
97
99
|
clientJsDisabled || Object.keys(match.params).length === 0
|
|
98
100
|
? ''
|
|
@@ -104,13 +106,20 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
104
106
|
searchParams: Object.fromEntries(new URL(req.url).searchParams),
|
|
105
107
|
statusCode: 200,
|
|
106
108
|
responseHeaders,
|
|
107
|
-
headHtml:
|
|
109
|
+
headHtml:
|
|
110
|
+
headHtml +
|
|
111
|
+
clientBootstrap.preloadLinks +
|
|
112
|
+
segmentScript +
|
|
113
|
+
paramsScript +
|
|
114
|
+
// Initialize __timber_f in <head> so it exists before any streaming
|
|
115
|
+
// chunk scripts arrive in <body>. See flight-scripts.ts, LOCAL-415.
|
|
116
|
+
(clientJsDisabled ? '' : flightInitScript()),
|
|
108
117
|
bootstrapScriptContent: clientBootstrap.bootstrapScriptContent,
|
|
109
118
|
// Skip RSC inline stream when client JS is disabled — no client to hydrate.
|
|
110
119
|
rscStream: clientJsDisabled ? undefined : inlineStream,
|
|
111
120
|
deferSuspenseFor: deferSuspenseFor > 0 ? deferSuspenseFor : undefined,
|
|
112
121
|
signal: req.signal,
|
|
113
|
-
cookies:
|
|
122
|
+
cookies: getCookiesForSsr(),
|
|
114
123
|
};
|
|
115
124
|
|
|
116
125
|
// Helper: check if render-phase signals were captured and return the
|
|
@@ -156,16 +165,34 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
156
165
|
try {
|
|
157
166
|
const ssrResponse = await callSsr(ssrStream, navContext);
|
|
158
167
|
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
168
|
+
// Record SSR sub-phase timings for Server-Timing header (detailed mode).
|
|
169
|
+
// These are populated by handleSsr() in the SSR environment and passed
|
|
170
|
+
// back via navContext._ssrTimings across the RSC→SSR boundary.
|
|
171
|
+
if (navContext._ssrTimings) {
|
|
172
|
+
const t = navContext._ssrTimings;
|
|
173
|
+
recordTiming({ name: 'ssr-decode', dur: t.decodeMs, desc: 'RSC Flight decode' });
|
|
174
|
+
recordTiming({ name: 'ssr-shell', dur: t.shellMs, desc: 'Fizz onShellReady' });
|
|
175
|
+
recordTiming({ name: 'ssr-pipeline', dur: t.pipelineMs, desc: 'stream transforms' });
|
|
176
|
+
recordTiming({
|
|
177
|
+
name: 'ssr-total',
|
|
178
|
+
dur: t.totalMs,
|
|
179
|
+
desc: t.nodeStreams ? 'SSR (Node streams)' : 'SSR (Web Streams)',
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Signal promotion: check if any signals were captured during rendering
|
|
184
|
+
// inside Suspense boundaries. If no signals are present yet, yield one
|
|
185
|
+
// microtask so async component rejections propagate to the RSC onError
|
|
186
|
+
// callback before we commit the response.
|
|
163
187
|
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
188
|
+
// When signals are already captured (onSignal already fired), skip the
|
|
189
|
+
// yield entirely — react immediately. Uses queueMicrotask instead of
|
|
190
|
+
// setTimeout(0) for the fallback to avoid yielding to the full event
|
|
191
|
+
// loop (timers phase).
|
|
167
192
|
// See design/05-streaming.md §"deferSuspenseFor and the Hold Window"
|
|
168
|
-
|
|
193
|
+
if (!signals.redirectSignal && !signals.denySignal && !signals.renderError) {
|
|
194
|
+
await new Promise<void>((r) => queueMicrotask(r));
|
|
195
|
+
}
|
|
169
196
|
|
|
170
197
|
const promoted = checkCapturedSignals(/* skipHandledDeny */ true);
|
|
171
198
|
if (promoted) {
|