@timber-js/app 0.2.0-alpha.32 → 0.2.0-alpha.33
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/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.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.map +1 -1
- package/package.json +1 -1
- package/src/server/rsc-entry/index.ts +15 -0
- package/src/server/rsc-entry/ssr-renderer.ts +16 -0
- package/src/server/ssr-entry.ts +49 -28
- package/src/server/ssr-render.ts +0 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA2FA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAE/F;AAkaD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;8BApR3C,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AAsRhD,wBAAiE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/ssr-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAGxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAYrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"ssr-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/ssr-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAGxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAYrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,UAAU,gBAAgB;IACxB,GAAG,EAAE,OAAO,CAAC;IACb,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACtC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqLjF"}
|
|
@@ -53,6 +53,28 @@ export interface NavContext {
|
|
|
53
53
|
* to a page-level deny. See LOCAL-298.
|
|
54
54
|
*/
|
|
55
55
|
_denyHandledByBoundary?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Mutable: SSR timing data populated by handleSsr().
|
|
58
|
+
* Read by the RSC entry to record sub-phase Server-Timing entries
|
|
59
|
+
* when `serverTiming: 'detailed'` is configured.
|
|
60
|
+
*
|
|
61
|
+
* This bridges the RSC→SSR environment boundary: the SSR entry populates
|
|
62
|
+
* these fields, the RSC entry reads them after callSsr() returns.
|
|
63
|
+
*/
|
|
64
|
+
_ssrTimings?: {
|
|
65
|
+
/** Time to decode RSC stream (createFromReadableStream/createFromNodeStream) */
|
|
66
|
+
decodeMs: number;
|
|
67
|
+
/** Time for Fizz to render the shell (onShellReady) */
|
|
68
|
+
shellMs: number;
|
|
69
|
+
/** Time for pipe() to flush shell bytes to the output stream */
|
|
70
|
+
pipeMs: number;
|
|
71
|
+
/** Time to set up Node.js Transform pipeline / Web Stream transforms */
|
|
72
|
+
pipelineMs: number;
|
|
73
|
+
/** Total SSR time (decode → response ready) */
|
|
74
|
+
totalMs: number;
|
|
75
|
+
/** Whether the Node.js native stream path was used */
|
|
76
|
+
nodeStreams: boolean;
|
|
77
|
+
};
|
|
56
78
|
}
|
|
57
79
|
/**
|
|
58
80
|
* Handle SSR: decode an RSC stream and render it to hydration-ready HTML.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-entry.d.ts","sourceRoot":"","sources":["../../src/server/ssr-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA0EH;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,iCAAiC;IACjC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,eAAe,EAAE,OAAO,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qEAAqE;IACrE,SAAS,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACvC;;;0DAGsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;iFAE6E;IAC7E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;4DACwD;IACxD,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ssr-entry.d.ts","sourceRoot":"","sources":["../../src/server/ssr-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA0EH;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,iCAAiC;IACjC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,eAAe,EAAE,OAAO,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qEAAqE;IACrE,SAAS,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACvC;;;0DAGsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;iFAE6E;IAC7E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;4DACwD;IACxD,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE;QACZ,gFAAgF;QAChF,QAAQ,EAAE,MAAM,CAAC;QACjB,uDAAuD;QACvD,OAAO,EAAE,MAAM,CAAC;QAChB,gEAAgE;QAChE,MAAM,EAAE,MAAM,CAAC;QACf,wEAAwE;QACxE,UAAU,EAAE,MAAM,CAAC;QACnB,+CAA+C;QAC/C,OAAO,EAAE,MAAM,CAAC;QAChB,sDAAsD;QACtD,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,SAAS,CAC7B,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EACrC,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,QAAQ,CAAC,CAuJnB;AAED,eAAe,SAAS,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-render.d.ts","sourceRoot":"","sources":["../../src/server/ssr-render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAqEvC;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE;IAAE,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAC7F,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAErC;AAED,wEAAwE;AACxE,eAAO,MAAM,cAAc,SAAkB,CAAC;AAU9C;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE;IAAE,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAC7F,OAAO,CAAC,OAAO,aAAa,EAAE,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"ssr-render.d.ts","sourceRoot":"","sources":["../../src/server/ssr-render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAqEvC;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE;IAAE,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAC7F,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAErC;AAED,wEAAwE;AACxE,eAAO,MAAM,cAAc,SAAkB,CAAC;AAU9C;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE;IAAE,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAC7F,OAAO,CAAC,OAAO,aAAa,EAAE,QAAQ,CAAC,CAkDzC;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,aAAa,EAAE,QAAQ,GACvC,cAAc,CAAC,UAAU,CAAC,CAE5B;AA0CD;;;;;;;;;;;;GAYG;AACH,2CAA2C;AAC3C,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,UAAU,CAAC,CA2B5B;AAWD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,cAAc,CAAC,UAAU,CAAC,EACtC,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,OAAO,GACvB,QAAQ,CASV"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timber-js/app",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.33",
|
|
4
4
|
"description": "Vite-native React framework for Cloudflare Workers — correct HTTP semantics, real status codes, pages that work without JavaScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|
|
@@ -68,6 +68,7 @@ import { renderRscStream } from './rsc-stream.js';
|
|
|
68
68
|
import { renderSsrResponse } from './ssr-renderer.js';
|
|
69
69
|
import { callSsr } from './ssr-bridge.js';
|
|
70
70
|
import { isDebug, isDevMode, setDebugFromConfig } from '#/server/debug.js';
|
|
71
|
+
import { recordTiming } from '#/server/server-timing.js';
|
|
71
72
|
|
|
72
73
|
/**
|
|
73
74
|
* Resolve the Server-Timing mode from timber.config.ts.
|
|
@@ -325,6 +326,7 @@ async function renderRoute(
|
|
|
325
326
|
// Build the React element tree — loads modules, runs access checks,
|
|
326
327
|
// resolves metadata. DenySignal/RedirectSignal propagate for HTTP handling.
|
|
327
328
|
let routeResult;
|
|
329
|
+
const _buildStart = performance.now();
|
|
328
330
|
try {
|
|
329
331
|
routeResult = await buildRouteElement(_req, match, interception, clientStateTree);
|
|
330
332
|
} catch (error) {
|
|
@@ -364,6 +366,13 @@ async function renderRoute(
|
|
|
364
366
|
throw error;
|
|
365
367
|
}
|
|
366
368
|
|
|
369
|
+
const _buildEnd = performance.now();
|
|
370
|
+
recordTiming({
|
|
371
|
+
name: 'build',
|
|
372
|
+
dur: Math.round(_buildEnd - _buildStart),
|
|
373
|
+
desc: 'build element tree',
|
|
374
|
+
});
|
|
375
|
+
|
|
367
376
|
const { element, headElements, layoutComponents, deferSuspenseFor, skippedSegments } =
|
|
368
377
|
routeResult;
|
|
369
378
|
|
|
@@ -416,7 +425,13 @@ async function renderRoute(
|
|
|
416
425
|
}
|
|
417
426
|
|
|
418
427
|
// Render to RSC Flight stream with signal tracking.
|
|
428
|
+
const _rscStart = performance.now();
|
|
419
429
|
const { rscStream, signals } = renderRscStream(element, _req);
|
|
430
|
+
recordTiming({
|
|
431
|
+
name: 'rsc-init',
|
|
432
|
+
dur: Math.round(performance.now() - _rscStart),
|
|
433
|
+
desc: 'RSC stream init',
|
|
434
|
+
});
|
|
420
435
|
|
|
421
436
|
// Synchronous redirect — redirect() in access.ts or a non-async component
|
|
422
437
|
// throws during renderToReadableStream creation. Return HTTP redirect.
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
import { renderErrorPage } from './error-renderer.js';
|
|
32
32
|
import { callSsr } from './ssr-bridge.js';
|
|
33
33
|
import type { RenderSignals } from './rsc-stream.js';
|
|
34
|
+
import { recordTiming } from '#/server/server-timing.js';
|
|
34
35
|
|
|
35
36
|
interface SsrRenderOptions {
|
|
36
37
|
req: Request;
|
|
@@ -156,6 +157,21 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
156
157
|
try {
|
|
157
158
|
const ssrResponse = await callSsr(ssrStream, navContext);
|
|
158
159
|
|
|
160
|
+
// Record SSR sub-phase timings for Server-Timing header (detailed mode).
|
|
161
|
+
// These are populated by handleSsr() in the SSR environment and passed
|
|
162
|
+
// back via navContext._ssrTimings across the RSC→SSR boundary.
|
|
163
|
+
if (navContext._ssrTimings) {
|
|
164
|
+
const t = navContext._ssrTimings;
|
|
165
|
+
recordTiming({ name: 'ssr-decode', dur: t.decodeMs, desc: 'RSC Flight decode' });
|
|
166
|
+
recordTiming({ name: 'ssr-shell', dur: t.shellMs, desc: 'Fizz onShellReady' });
|
|
167
|
+
recordTiming({ name: 'ssr-pipeline', dur: t.pipelineMs, desc: 'stream transforms' });
|
|
168
|
+
recordTiming({
|
|
169
|
+
name: 'ssr-total',
|
|
170
|
+
dur: t.totalMs,
|
|
171
|
+
desc: t.nodeStreams ? 'SSR (Node streams)' : 'SSR (Web Streams)',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
159
175
|
// Signal promotion: check if any signals were captured during rendering
|
|
160
176
|
// inside Suspense boundaries. If no signals are present yet, yield one
|
|
161
177
|
// microtask so async component rejections propagate to the RSC onError
|
package/src/server/ssr-entry.ts
CHANGED
|
@@ -126,6 +126,29 @@ export interface NavContext {
|
|
|
126
126
|
* to a page-level deny. See LOCAL-298.
|
|
127
127
|
*/
|
|
128
128
|
_denyHandledByBoundary?: boolean;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Mutable: SSR timing data populated by handleSsr().
|
|
132
|
+
* Read by the RSC entry to record sub-phase Server-Timing entries
|
|
133
|
+
* when `serverTiming: 'detailed'` is configured.
|
|
134
|
+
*
|
|
135
|
+
* This bridges the RSC→SSR environment boundary: the SSR entry populates
|
|
136
|
+
* these fields, the RSC entry reads them after callSsr() returns.
|
|
137
|
+
*/
|
|
138
|
+
_ssrTimings?: {
|
|
139
|
+
/** Time to decode RSC stream (createFromReadableStream/createFromNodeStream) */
|
|
140
|
+
decodeMs: number;
|
|
141
|
+
/** Time for Fizz to render the shell (onShellReady) */
|
|
142
|
+
shellMs: number;
|
|
143
|
+
/** Time for pipe() to flush shell bytes to the output stream */
|
|
144
|
+
pipeMs: number;
|
|
145
|
+
/** Time to set up Node.js Transform pipeline / Web Stream transforms */
|
|
146
|
+
pipelineMs: number;
|
|
147
|
+
/** Total SSR time (decode → response ready) */
|
|
148
|
+
totalMs: number;
|
|
149
|
+
/** Whether the Node.js native stream path was used */
|
|
150
|
+
nodeStreams: boolean;
|
|
151
|
+
};
|
|
129
152
|
}
|
|
130
153
|
|
|
131
154
|
/**
|
|
@@ -182,11 +205,8 @@ export async function handleSsr(
|
|
|
182
205
|
// createFromReadableStream resolves client component references
|
|
183
206
|
// (from "use client" modules) using the SSR environment's module
|
|
184
207
|
// map, importing the actual components for server-side rendering.
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
console.log(
|
|
188
|
-
`[diag] nodeImports=${!!_nodeStreamImports} nodeStreamDecode=${hasNodeStreamDecode} rscStream=${rscStream?.constructor?.name}`
|
|
189
|
-
);
|
|
208
|
+
const _ssrStart = performance.now();
|
|
209
|
+
|
|
190
210
|
// Decode the RSC stream into a React element tree.
|
|
191
211
|
// On Node.js: convert Web ReadableStream → Node Readable → createFromNodeStream
|
|
192
212
|
// (eliminates Promise-per-chunk overhead from Web Streams reader)
|
|
@@ -200,21 +220,13 @@ export async function handleSsr(
|
|
|
200
220
|
} else {
|
|
201
221
|
element = createFromReadableStream(rscStream) as React.ReactNode;
|
|
202
222
|
}
|
|
203
|
-
const
|
|
223
|
+
const _decodeEnd = performance.now();
|
|
204
224
|
|
|
205
225
|
// Wrap with a server-safe nuqs adapter so that 'use client' components
|
|
206
226
|
// that call nuqs hooks (useQueryStates, useQueryState) can SSR correctly.
|
|
207
|
-
// The client-side TimberNuqsAdapter (injected by browser-entry.ts) takes
|
|
208
|
-
// over after hydration. This provider supplies the request's search params
|
|
209
|
-
// as a static snapshot so nuqs renders the right initial values on the server.
|
|
210
227
|
const wrappedElement = withNuqsSsrAdapter(navContext.searchParams, element);
|
|
211
|
-
const _s2 = performance.now();
|
|
212
228
|
|
|
213
229
|
// Render to HTML stream (waits for onShellReady).
|
|
214
|
-
// Pass bootstrapScriptContent so React injects a non-deferred <script>
|
|
215
|
-
// in the shell HTML. This executes immediately during parsing — even
|
|
216
|
-
// while Suspense boundaries are still streaming — triggering module
|
|
217
|
-
// loading via dynamic import() so hydration can start early.
|
|
218
230
|
//
|
|
219
231
|
// Two paths based on platform:
|
|
220
232
|
// - Node.js: renderToPipeableStream → Node Transform pipeline → Readable.toWeb() → Response
|
|
@@ -231,7 +243,6 @@ export async function handleSsr(
|
|
|
231
243
|
PassThrough,
|
|
232
244
|
} = _nodeStreamImports;
|
|
233
245
|
|
|
234
|
-
const _s3 = performance.now();
|
|
235
246
|
let nodeHtmlStream: import('node:stream').Readable;
|
|
236
247
|
try {
|
|
237
248
|
nodeHtmlStream = await renderSsrNodeStream(wrappedElement, {
|
|
@@ -249,29 +260,27 @@ export async function handleSsr(
|
|
|
249
260
|
renderError
|
|
250
261
|
);
|
|
251
262
|
}
|
|
263
|
+
const _renderEnd = performance.now();
|
|
252
264
|
|
|
253
|
-
// Build Node.js Transform pipeline: errorHandler → headInjector → flightInjector → gzip
|
|
254
265
|
const errorHandler = createNodeErrorHandler(navContext.signal);
|
|
255
266
|
const headInjector = createNodeHeadInjector(navContext.headHtml);
|
|
256
267
|
const flightInjector = createNodeFlightInjector(navContext.rscStream);
|
|
257
268
|
|
|
258
|
-
// Pipe through the chain. pipeline() handles backpressure and error propagation.
|
|
259
|
-
// The last stream in the chain is the output — convert to Web ReadableStream
|
|
260
|
-
// only at the Response boundary.
|
|
261
|
-
// Note: gzip compression is still handled by compressResponse() in the Nitro
|
|
262
|
-
// entry via Web Streams CompressionStream. Moving it into this Node.js pipeline
|
|
263
|
-
// requires the request headers (Accept-Encoding) which NavContext doesn't carry.
|
|
264
|
-
// TODO: pass request headers through NavContext to enable inline Node.js gzip.
|
|
265
269
|
const output = new PassThrough();
|
|
266
270
|
pipeline(nodeHtmlStream, errorHandler, headInjector, flightInjector, output).catch(() => {
|
|
267
271
|
// Pipeline errors are handled by errorHandler transform
|
|
268
272
|
});
|
|
273
|
+
const _pipelineEnd = performance.now();
|
|
269
274
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
+
// Record SSR sub-timings for Server-Timing header (detailed mode).
|
|
276
|
+
navContext._ssrTimings = {
|
|
277
|
+
decodeMs: Math.round(_decodeEnd - _ssrStart),
|
|
278
|
+
shellMs: Math.round(_renderEnd - _decodeEnd),
|
|
279
|
+
pipeMs: 0, // pipe() timing is inside renderSsrNodeStream
|
|
280
|
+
pipelineMs: Math.round(_pipelineEnd - _renderEnd),
|
|
281
|
+
totalMs: Math.round(_pipelineEnd - _ssrStart),
|
|
282
|
+
nodeStreams: true,
|
|
283
|
+
};
|
|
275
284
|
|
|
276
285
|
const webStream = nodeReadableToWeb(output);
|
|
277
286
|
return buildSsrResponse(webStream, navContext.statusCode, navContext.responseHeaders);
|
|
@@ -296,10 +305,22 @@ export async function handleSsr(
|
|
|
296
305
|
);
|
|
297
306
|
}
|
|
298
307
|
|
|
308
|
+
const _renderEnd = performance.now();
|
|
309
|
+
|
|
299
310
|
// Inject metadata into <head>, then interleave RSC payload chunks
|
|
300
311
|
// into the body as they arrive from the tee'd RSC stream.
|
|
301
312
|
let outputStream = injectHead(htmlStream, navContext.headHtml);
|
|
302
313
|
outputStream = injectRscPayload(outputStream, navContext.rscStream);
|
|
314
|
+
const _pipelineEnd = performance.now();
|
|
315
|
+
|
|
316
|
+
navContext._ssrTimings = {
|
|
317
|
+
decodeMs: Math.round(_decodeEnd - _ssrStart),
|
|
318
|
+
shellMs: Math.round(_renderEnd - _decodeEnd),
|
|
319
|
+
pipeMs: 0,
|
|
320
|
+
pipelineMs: Math.round(_pipelineEnd - _renderEnd),
|
|
321
|
+
totalMs: Math.round(_pipelineEnd - _ssrStart),
|
|
322
|
+
nodeStreams: false,
|
|
323
|
+
};
|
|
303
324
|
|
|
304
325
|
// Build and return the Response.
|
|
305
326
|
return buildSsrResponse(outputStream, navContext.statusCode, navContext.responseHeaders);
|
package/src/server/ssr-render.ts
CHANGED
|
@@ -136,7 +136,6 @@ export async function renderSsrNodeStream(
|
|
|
136
136
|
const deferMs = options?.deferSuspenseFor;
|
|
137
137
|
|
|
138
138
|
return new Promise<import('node:stream').Readable>((resolve, reject) => {
|
|
139
|
-
const _startTime = performance.now();
|
|
140
139
|
const passthrough = new _PassThrough!();
|
|
141
140
|
|
|
142
141
|
let allReadyResolve: (() => void) | null = null;
|
|
@@ -149,20 +148,14 @@ export async function renderSsrNodeStream(
|
|
|
149
148
|
bootstrapScriptContent: options?.bootstrapScriptContent || undefined,
|
|
150
149
|
|
|
151
150
|
onShellReady() {
|
|
152
|
-
const _shellReady = performance.now();
|
|
153
151
|
if (deferMs && deferMs > 0) {
|
|
154
152
|
Promise.race([allReady, new Promise<void>((r) => setTimeout(r, deferMs))]).then(() => {
|
|
155
153
|
pipe(passthrough);
|
|
156
154
|
resolve(passthrough);
|
|
157
155
|
});
|
|
158
156
|
} else {
|
|
159
|
-
const _beforePipe = performance.now();
|
|
160
157
|
pipe(passthrough);
|
|
161
|
-
const _afterPipe = performance.now();
|
|
162
158
|
resolve(passthrough);
|
|
163
|
-
const _afterResolve = performance.now();
|
|
164
|
-
// eslint-disable-next-line no-console
|
|
165
|
-
console.log(`[ssr-perf] onShellReady=${(_shellReady - _startTime).toFixed(1)}ms pipe=${(_afterPipe - _beforePipe).toFixed(1)}ms resolve=${(_afterResolve - _afterPipe).toFixed(1)}ms`);
|
|
166
159
|
}
|
|
167
160
|
},
|
|
168
161
|
|