@ivogt/rsc-router 0.0.0-experimental.1

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 (123) hide show
  1. package/README.md +19 -0
  2. package/package.json +131 -0
  3. package/src/__mocks__/version.ts +6 -0
  4. package/src/__tests__/route-definition.test.ts +63 -0
  5. package/src/browser/event-controller.ts +876 -0
  6. package/src/browser/index.ts +18 -0
  7. package/src/browser/link-interceptor.ts +121 -0
  8. package/src/browser/lru-cache.ts +69 -0
  9. package/src/browser/merge-segment-loaders.ts +126 -0
  10. package/src/browser/navigation-bridge.ts +891 -0
  11. package/src/browser/navigation-client.ts +155 -0
  12. package/src/browser/navigation-store.ts +823 -0
  13. package/src/browser/partial-update.ts +545 -0
  14. package/src/browser/react/Link.tsx +248 -0
  15. package/src/browser/react/NavigationProvider.tsx +228 -0
  16. package/src/browser/react/ScrollRestoration.tsx +94 -0
  17. package/src/browser/react/context.ts +53 -0
  18. package/src/browser/react/index.ts +52 -0
  19. package/src/browser/react/location-state-shared.ts +120 -0
  20. package/src/browser/react/location-state.ts +62 -0
  21. package/src/browser/react/use-action.ts +240 -0
  22. package/src/browser/react/use-client-cache.ts +56 -0
  23. package/src/browser/react/use-handle.ts +178 -0
  24. package/src/browser/react/use-link-status.ts +134 -0
  25. package/src/browser/react/use-navigation.ts +150 -0
  26. package/src/browser/react/use-segments.ts +188 -0
  27. package/src/browser/request-controller.ts +149 -0
  28. package/src/browser/rsc-router.tsx +310 -0
  29. package/src/browser/scroll-restoration.ts +324 -0
  30. package/src/browser/server-action-bridge.ts +747 -0
  31. package/src/browser/shallow.ts +35 -0
  32. package/src/browser/types.ts +443 -0
  33. package/src/cache/__tests__/memory-segment-store.test.ts +487 -0
  34. package/src/cache/__tests__/memory-store.test.ts +484 -0
  35. package/src/cache/cache-scope.ts +565 -0
  36. package/src/cache/cf/__tests__/cf-cache-store.test.ts +361 -0
  37. package/src/cache/cf/cf-cache-store.ts +274 -0
  38. package/src/cache/cf/index.ts +19 -0
  39. package/src/cache/index.ts +52 -0
  40. package/src/cache/memory-segment-store.ts +150 -0
  41. package/src/cache/memory-store.ts +253 -0
  42. package/src/cache/types.ts +366 -0
  43. package/src/client.rsc.tsx +88 -0
  44. package/src/client.tsx +609 -0
  45. package/src/components/DefaultDocument.tsx +20 -0
  46. package/src/default-error-boundary.tsx +88 -0
  47. package/src/deps/browser.ts +8 -0
  48. package/src/deps/html-stream-client.ts +2 -0
  49. package/src/deps/html-stream-server.ts +2 -0
  50. package/src/deps/rsc.ts +10 -0
  51. package/src/deps/ssr.ts +2 -0
  52. package/src/errors.ts +259 -0
  53. package/src/handle.ts +120 -0
  54. package/src/handles/MetaTags.tsx +178 -0
  55. package/src/handles/index.ts +6 -0
  56. package/src/handles/meta.ts +247 -0
  57. package/src/href-client.ts +128 -0
  58. package/src/href.ts +139 -0
  59. package/src/index.rsc.ts +69 -0
  60. package/src/index.ts +84 -0
  61. package/src/loader.rsc.ts +204 -0
  62. package/src/loader.ts +47 -0
  63. package/src/network-error-thrower.tsx +21 -0
  64. package/src/outlet-context.ts +15 -0
  65. package/src/root-error-boundary.tsx +277 -0
  66. package/src/route-content-wrapper.tsx +198 -0
  67. package/src/route-definition.ts +1333 -0
  68. package/src/route-map-builder.ts +140 -0
  69. package/src/route-types.ts +148 -0
  70. package/src/route-utils.ts +89 -0
  71. package/src/router/__tests__/match-context.test.ts +104 -0
  72. package/src/router/__tests__/match-pipelines.test.ts +537 -0
  73. package/src/router/__tests__/match-result.test.ts +566 -0
  74. package/src/router/__tests__/on-error.test.ts +935 -0
  75. package/src/router/__tests__/pattern-matching.test.ts +577 -0
  76. package/src/router/error-handling.ts +287 -0
  77. package/src/router/handler-context.ts +60 -0
  78. package/src/router/loader-resolution.ts +326 -0
  79. package/src/router/manifest.ts +116 -0
  80. package/src/router/match-context.ts +261 -0
  81. package/src/router/match-middleware/background-revalidation.ts +236 -0
  82. package/src/router/match-middleware/cache-lookup.ts +261 -0
  83. package/src/router/match-middleware/cache-store.ts +250 -0
  84. package/src/router/match-middleware/index.ts +81 -0
  85. package/src/router/match-middleware/intercept-resolution.ts +268 -0
  86. package/src/router/match-middleware/segment-resolution.ts +174 -0
  87. package/src/router/match-pipelines.ts +214 -0
  88. package/src/router/match-result.ts +212 -0
  89. package/src/router/metrics.ts +62 -0
  90. package/src/router/middleware.test.ts +1355 -0
  91. package/src/router/middleware.ts +748 -0
  92. package/src/router/pattern-matching.ts +271 -0
  93. package/src/router/revalidation.ts +190 -0
  94. package/src/router/router-context.ts +299 -0
  95. package/src/router/types.ts +96 -0
  96. package/src/router.ts +3484 -0
  97. package/src/rsc/__tests__/helpers.test.ts +175 -0
  98. package/src/rsc/handler.ts +942 -0
  99. package/src/rsc/helpers.ts +64 -0
  100. package/src/rsc/index.ts +56 -0
  101. package/src/rsc/nonce.ts +18 -0
  102. package/src/rsc/types.ts +225 -0
  103. package/src/segment-system.tsx +405 -0
  104. package/src/server/__tests__/request-context.test.ts +171 -0
  105. package/src/server/context.ts +340 -0
  106. package/src/server/handle-store.ts +230 -0
  107. package/src/server/loader-registry.ts +174 -0
  108. package/src/server/request-context.ts +470 -0
  109. package/src/server/root-layout.tsx +10 -0
  110. package/src/server/tsconfig.json +14 -0
  111. package/src/server.ts +126 -0
  112. package/src/ssr/__tests__/ssr-handler.test.tsx +188 -0
  113. package/src/ssr/index.tsx +215 -0
  114. package/src/types.ts +1473 -0
  115. package/src/use-loader.tsx +346 -0
  116. package/src/vite/__tests__/expose-loader-id.test.ts +117 -0
  117. package/src/vite/expose-action-id.ts +344 -0
  118. package/src/vite/expose-handle-id.ts +209 -0
  119. package/src/vite/expose-loader-id.ts +357 -0
  120. package/src/vite/expose-location-state-id.ts +177 -0
  121. package/src/vite/index.ts +608 -0
  122. package/src/vite/version.d.ts +12 -0
  123. package/src/vite/virtual-entries.ts +109 -0
package/src/server.ts ADDED
@@ -0,0 +1,126 @@
1
+ /**
2
+ * rsc-router/server
3
+ *
4
+ * Server-only exports for route definition and building
5
+ * These should only be imported in server-side handler files
6
+ */
7
+
8
+ // Route definition helpers (server-only)
9
+ export {
10
+ route,
11
+ map,
12
+ createLoader,
13
+ redirect,
14
+ type RouteHelpers,
15
+ type InterceptSelectorContext,
16
+ type InterceptSegmentsState,
17
+ type InterceptWhenFn,
18
+ } from "./route-definition.js";
19
+
20
+ // Core router (server-only)
21
+ export {
22
+ createRSCRouter,
23
+ type RSCRouter,
24
+ type RSCRouterOptions,
25
+ type RootLayoutProps,
26
+ } from "./router.js";
27
+
28
+ // Type-safe href utilities
29
+ export {
30
+ createHref,
31
+ type HrefFunction,
32
+ type PrefixedRoutes,
33
+ type ParamsFor,
34
+ type SanitizePrefix,
35
+ type MergeRoutes,
36
+ } from "./href.js";
37
+
38
+ // Segment system (server-only)
39
+ export { renderSegments } from "./segment-system.js";
40
+
41
+ // Performance tracking (server-only)
42
+ export { track } from "./server/context.js";
43
+
44
+ // Handle API (works in both server and client contexts)
45
+ export { createHandle, isHandle, type Handle } from "./handle.js";
46
+
47
+ // Built-in handles
48
+ export { Meta } from "./handles/meta.js";
49
+
50
+ // Loader registry (for GET-based loader fetching)
51
+ export { registerLoaderById, setLoaderImports } from "./server/loader-registry.js";
52
+
53
+ // Request context (for accessing request data in server components/actions)
54
+ export {
55
+ getRequestContext,
56
+ requireRequestContext,
57
+ createRequestContext,
58
+ type RequestContext,
59
+ type CreateRequestContextOptions,
60
+ } from "./server/request-context.js";
61
+
62
+ // Meta types
63
+ export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
64
+
65
+ // Middleware types
66
+ export type {
67
+ MiddlewareFn,
68
+ MiddlewareFn as AppMiddlewareFn, // Alias for backwards compatibility
69
+ MiddlewareContext,
70
+ CookieOptions,
71
+ } from "./router/middleware.js";
72
+
73
+ // Error classes and utilities
74
+ export {
75
+ RouteNotFoundError,
76
+ DataNotFoundError,
77
+ notFound,
78
+ MiddlewareError,
79
+ HandlerError,
80
+ BuildError,
81
+ InvalidHandlerError,
82
+ sanitizeError,
83
+ } from "./errors.js";
84
+
85
+ // Types (re-exported for convenience)
86
+ export type {
87
+ RouterEnv,
88
+ DefaultEnv,
89
+ RouteDefinition,
90
+ RouteConfig,
91
+ RouteDefinitionOptions,
92
+ TrailingSlashMode,
93
+ ResolvedRouteMap,
94
+ Handler,
95
+ HandlerContext,
96
+ HandlersForRouteMap,
97
+ ResolvedSegment,
98
+ SegmentMetadata,
99
+ MatchResult,
100
+ SlotState,
101
+ ExtractParams,
102
+ GenericParams,
103
+ RevalidateParams,
104
+ ShouldRevalidateFn,
105
+ RouteKeys,
106
+ RouteHandler,
107
+ RouteRevalidateFn,
108
+ RouteMiddlewareFn,
109
+ LoaderDefinition,
110
+ LoaderFn,
111
+ LoaderContext,
112
+ GetRegisteredRoutes,
113
+ // Error boundary types
114
+ ErrorInfo,
115
+ ErrorBoundaryFallbackProps,
116
+ ErrorBoundaryHandler,
117
+ ClientErrorBoundaryFallbackProps,
118
+ // NotFound boundary types
119
+ NotFoundInfo,
120
+ NotFoundBoundaryFallbackProps,
121
+ NotFoundBoundaryHandler,
122
+ // Error handling callback types
123
+ ErrorPhase,
124
+ OnErrorContext,
125
+ OnErrorCallback,
126
+ } from "./types.js";
@@ -0,0 +1,188 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import React from "react";
3
+ import { createSSRHandler, type SSRDependencies } from "../index";
4
+
5
+ describe("createSSRHandler", () => {
6
+ // Mock dependencies
7
+ const createMockDeps = (
8
+ overrides: Partial<SSRDependencies> = {}
9
+ ): SSRDependencies => ({
10
+ createFromReadableStream: vi.fn().mockResolvedValue({
11
+ root: React.createElement("div", null, "Test"),
12
+ metadata: { matched: ["/"], pathname: "/" },
13
+ }),
14
+ renderToReadableStream: vi.fn().mockResolvedValue(
15
+ new ReadableStream({
16
+ start(controller) {
17
+ controller.enqueue(new TextEncoder().encode("<html><body>Test</body></html>"));
18
+ controller.close();
19
+ },
20
+ })
21
+ ),
22
+ injectRSCPayload: vi.fn().mockReturnValue(
23
+ new TransformStream({
24
+ transform(chunk, controller) {
25
+ controller.enqueue(chunk);
26
+ },
27
+ })
28
+ ),
29
+ loadBootstrapScriptContent: vi.fn().mockResolvedValue("console.log('bootstrap')"),
30
+ ...overrides,
31
+ });
32
+
33
+ const createMockRscStream = () =>
34
+ new ReadableStream({
35
+ start(controller) {
36
+ controller.enqueue(new TextEncoder().encode("mock rsc payload"));
37
+ controller.close();
38
+ },
39
+ });
40
+
41
+ describe("onError callback", () => {
42
+ it("should call onError when rendering fails", async () => {
43
+ const onError = vi.fn();
44
+ const renderError = new Error("Rendering failed");
45
+
46
+ const deps = createMockDeps({
47
+ renderToReadableStream: vi.fn().mockRejectedValue(renderError),
48
+ onError,
49
+ });
50
+
51
+ const renderHTML = createSSRHandler(deps);
52
+
53
+ await expect(renderHTML(createMockRscStream())).rejects.toThrow("Rendering failed");
54
+
55
+ expect(onError).toHaveBeenCalledTimes(1);
56
+ expect(onError).toHaveBeenCalledWith(renderError, { phase: "rendering" });
57
+ });
58
+
59
+ it("should call onError with phase: rendering", async () => {
60
+ const onError = vi.fn();
61
+
62
+ const deps = createMockDeps({
63
+ // Use loadBootstrapScriptContent error since it happens before React.use()
64
+ loadBootstrapScriptContent: vi.fn().mockRejectedValue(new Error("Bootstrap failed")),
65
+ onError,
66
+ });
67
+
68
+ const renderHTML = createSSRHandler(deps);
69
+
70
+ await expect(renderHTML(createMockRscStream())).rejects.toThrow("Bootstrap failed");
71
+
72
+ expect(onError).toHaveBeenCalledWith(
73
+ expect.any(Error),
74
+ { phase: "rendering" }
75
+ );
76
+ });
77
+
78
+ it("should convert non-Error objects to Error", async () => {
79
+ const onError = vi.fn();
80
+
81
+ const deps = createMockDeps({
82
+ renderToReadableStream: vi.fn().mockRejectedValue("string error"),
83
+ onError,
84
+ });
85
+
86
+ const renderHTML = createSSRHandler(deps);
87
+
88
+ await expect(renderHTML(createMockRscStream())).rejects.toThrow();
89
+
90
+ expect(onError).toHaveBeenCalledWith(
91
+ expect.objectContaining({
92
+ message: "string error",
93
+ }),
94
+ { phase: "rendering" }
95
+ );
96
+ });
97
+
98
+ it("should still throw original error after calling onError", async () => {
99
+ const onError = vi.fn();
100
+ const originalError = new Error("Original error");
101
+
102
+ const deps = createMockDeps({
103
+ loadBootstrapScriptContent: vi.fn().mockRejectedValue(originalError),
104
+ onError,
105
+ });
106
+
107
+ const renderHTML = createSSRHandler(deps);
108
+
109
+ await expect(renderHTML(createMockRscStream())).rejects.toThrow("Original error");
110
+
111
+ // Verify onError was called before error was thrown
112
+ expect(onError).toHaveBeenCalled();
113
+ });
114
+
115
+ it("should catch errors in onError callback and not break the flow", async () => {
116
+ const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
117
+ const callbackError = new Error("Callback exploded");
118
+ const onError = vi.fn().mockImplementation(() => {
119
+ throw callbackError;
120
+ });
121
+ const originalError = new Error("Original rendering error");
122
+
123
+ const deps = createMockDeps({
124
+ renderToReadableStream: vi.fn().mockRejectedValue(originalError),
125
+ onError,
126
+ });
127
+
128
+ const renderHTML = createSSRHandler(deps);
129
+
130
+ // Should throw original error, not callback error
131
+ await expect(renderHTML(createMockRscStream())).rejects.toThrow("Original rendering error");
132
+
133
+ expect(onError).toHaveBeenCalled();
134
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
135
+ "[SSRHandler.onError] Callback error:",
136
+ callbackError
137
+ );
138
+
139
+ consoleErrorSpy.mockRestore();
140
+ });
141
+
142
+ it("should not call onError when rendering succeeds", async () => {
143
+ const onError = vi.fn();
144
+
145
+ const deps = createMockDeps({ onError });
146
+ const renderHTML = createSSRHandler(deps);
147
+
148
+ const result = await renderHTML(createMockRscStream());
149
+
150
+ expect(result).toBeInstanceOf(ReadableStream);
151
+ expect(onError).not.toHaveBeenCalled();
152
+ });
153
+
154
+ it("should work without onError callback", async () => {
155
+ const deps = createMockDeps({
156
+ renderToReadableStream: vi.fn().mockRejectedValue(new Error("No callback test")),
157
+ onError: undefined,
158
+ });
159
+
160
+ const renderHTML = createSSRHandler(deps);
161
+
162
+ // Should not throw because of missing onError
163
+ await expect(renderHTML(createMockRscStream())).rejects.toThrow("No callback test");
164
+ });
165
+ });
166
+
167
+ describe("successful rendering", () => {
168
+ it("should return a ReadableStream on success", async () => {
169
+ const deps = createMockDeps();
170
+ const renderHTML = createSSRHandler(deps);
171
+
172
+ const result = await renderHTML(createMockRscStream());
173
+
174
+ expect(result).toBeInstanceOf(ReadableStream);
175
+ });
176
+
177
+ it("should call all dependencies in correct order", async () => {
178
+ const deps = createMockDeps();
179
+ const renderHTML = createSSRHandler(deps);
180
+
181
+ await renderHTML(createMockRscStream());
182
+
183
+ expect(deps.loadBootstrapScriptContent).toHaveBeenCalled();
184
+ expect(deps.renderToReadableStream).toHaveBeenCalled();
185
+ expect(deps.injectRSCPayload).toHaveBeenCalled();
186
+ });
187
+ });
188
+ });
@@ -0,0 +1,215 @@
1
+ import React from "react";
2
+ import { initHandleDataSync } from "../browser/react/use-handle.js";
3
+ import { initSegmentsSync } from "../browser/react/use-segments.js";
4
+ import type { HandleData } from "../browser/types.js";
5
+ import type { ErrorPhase } from "../types.js";
6
+
7
+ /**
8
+ * Options for injectRSCPayload
9
+ */
10
+ export interface InjectRSCPayloadOptions {
11
+ /**
12
+ * Nonce for Content Security Policy (CSP)
13
+ */
14
+ nonce?: string;
15
+ }
16
+
17
+ /**
18
+ * Options for renderToReadableStream from react-dom/server
19
+ */
20
+ interface RenderToReadableStreamOptions {
21
+ bootstrapScriptContent?: string;
22
+ nonce?: string;
23
+ formState?: unknown;
24
+ }
25
+
26
+ /**
27
+ * Options for the renderHTML function
28
+ */
29
+ export interface SSRRenderOptions {
30
+ /**
31
+ * Form state for useActionState progressive enhancement.
32
+ * This is the result of decodeFormState() and should be passed to
33
+ * react-dom's renderToReadableStream to enable useActionState to
34
+ * receive the action result during SSR.
35
+ */
36
+ formState?: unknown;
37
+
38
+ /**
39
+ * Nonce for Content Security Policy (CSP)
40
+ */
41
+ nonce?: string;
42
+ }
43
+
44
+ /**
45
+ * SSR dependencies from external packages
46
+ */
47
+ export interface SSRDependencies<TEnv = unknown> {
48
+ /**
49
+ * createFromReadableStream from @vitejs/plugin-rsc/ssr
50
+ */
51
+ createFromReadableStream: <T>(stream: ReadableStream<Uint8Array>) => Promise<T>;
52
+
53
+ /**
54
+ * renderToReadableStream from react-dom/server.edge
55
+ */
56
+ renderToReadableStream: (
57
+ element: React.ReactNode,
58
+ options?: RenderToReadableStreamOptions
59
+ ) => Promise<ReadableStream<Uint8Array>>;
60
+
61
+ /**
62
+ * injectRSCPayload from rsc-html-stream/server
63
+ */
64
+ injectRSCPayload: (
65
+ rscStream: ReadableStream<Uint8Array>,
66
+ options?: InjectRSCPayloadOptions
67
+ ) => TransformStream<Uint8Array, Uint8Array>;
68
+
69
+ /**
70
+ * Function to load bootstrap script content
71
+ * Typically: () => import.meta.viteRsc.loadBootstrapScriptContent("index")
72
+ */
73
+ loadBootstrapScriptContent: () => Promise<string>;
74
+
75
+ /**
76
+ * Optional callback invoked when an error occurs during SSR rendering.
77
+ *
78
+ * This callback is for notification/logging purposes.
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * export const renderHTML = createSSRHandler({
83
+ * // ... other deps
84
+ * onError: (error, context) => {
85
+ * console.error('[SSR] Rendering error:', error);
86
+ * Sentry.captureException(error);
87
+ * },
88
+ * });
89
+ * ```
90
+ */
91
+ onError?: (error: Error, context: { phase: ErrorPhase }) => void;
92
+ }
93
+
94
+ /**
95
+ * RSC payload type (minimal interface for SSR)
96
+ */
97
+ interface RscPayload {
98
+ root: React.ReactNode;
99
+ metadata?: {
100
+ handles?: AsyncGenerator<HandleData, void, unknown>;
101
+ matched?: string[];
102
+ pathname?: string;
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Consume an async generator and return a Promise that resolves with the final value.
108
+ * Used for SSR where we need to await all handle data before rendering.
109
+ */
110
+ async function consumeAsyncGenerator(
111
+ generator: AsyncGenerator<HandleData, void, unknown>
112
+ ): Promise<HandleData> {
113
+ let lastData: HandleData = {};
114
+ for await (const data of generator) {
115
+ lastData = data;
116
+ }
117
+ return lastData;
118
+ }
119
+
120
+ /**
121
+ * Create an SSR handler that converts RSC streams to HTML.
122
+ *
123
+ * @example
124
+ * ```tsx
125
+ * import { createSSRHandler } from "rsc-router/ssr";
126
+ * import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
127
+ * import { renderToReadableStream } from "react-dom/server.edge";
128
+ * import { injectRSCPayload } from "rsc-html-stream/server";
129
+ *
130
+ * export const renderHTML = createSSRHandler({
131
+ * createFromReadableStream,
132
+ * renderToReadableStream,
133
+ * injectRSCPayload,
134
+ * loadBootstrapScriptContent: () =>
135
+ * import.meta.viteRsc.loadBootstrapScriptContent("index"),
136
+ * });
137
+ * ```
138
+ */
139
+ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
140
+ const {
141
+ createFromReadableStream,
142
+ renderToReadableStream,
143
+ injectRSCPayload,
144
+ loadBootstrapScriptContent,
145
+ onError,
146
+ } = deps;
147
+
148
+ /**
149
+ * Render RSC stream to HTML stream
150
+ *
151
+ * @param rscStream - The RSC stream to render
152
+ * @param options - Optional render options including formState for useActionState and nonce for CSP
153
+ */
154
+ return async function renderHTML(
155
+ rscStream: ReadableStream<Uint8Array>,
156
+ options?: SSRRenderOptions
157
+ ): Promise<ReadableStream<Uint8Array>> {
158
+ const { nonce, formState } = options ?? {};
159
+
160
+ try {
161
+ // Tee the stream:
162
+ // - rscStream1: For SSR rendering (deserialize to React VDOM)
163
+ // - rscStream2: For browser hydration (inject as __FLIGHT_DATA__)
164
+ const [rscStream1, rscStream2] = rscStream.tee();
165
+
166
+ // Deserialize RSC stream to React tree
167
+ let payload: Promise<RscPayload> | undefined;
168
+ let handlesPromise: Promise<HandleData> | undefined;
169
+ function SsrRoot() {
170
+ payload ??= createFromReadableStream<RscPayload>(rscStream1);
171
+ const resolved = React.use(payload);
172
+
173
+ // Initialize segments state before children render (for useSegments hook)
174
+ initSegmentsSync(resolved.metadata?.matched, resolved.metadata?.pathname);
175
+
176
+ // Await handles and initialize state before children render
177
+ // The handles property is an async generator that yields on each push
178
+ // Memoize the promise since async generators can only be iterated once
179
+ if (resolved.metadata?.handles) {
180
+ handlesPromise ??= consumeAsyncGenerator(resolved.metadata.handles);
181
+ const handleData = React.use(handlesPromise);
182
+ initHandleDataSync(handleData, resolved.metadata.matched);
183
+ }
184
+
185
+ return resolved.root;
186
+ }
187
+
188
+ // Get bootstrap script content
189
+ const bootstrapScriptContent = await loadBootstrapScriptContent();
190
+
191
+ // Render React tree to HTML stream
192
+ // Pass formState for useActionState progressive enhancement if provided
193
+ // Pass nonce for CSP if provided
194
+ const htmlStream = await renderToReadableStream(<SsrRoot />, {
195
+ bootstrapScriptContent,
196
+ formState,
197
+ nonce,
198
+ });
199
+
200
+ // Inject RSC payload into HTML as <script nonce="...">__FLIGHT_DATA__</script>
201
+ return htmlStream.pipeThrough(injectRSCPayload(rscStream2, { nonce }));
202
+ } catch (error) {
203
+ // Invoke onError callback if provided
204
+ if (onError) {
205
+ const errorObj = error instanceof Error ? error : new Error(String(error));
206
+ try {
207
+ onError(errorObj, { phase: "rendering" });
208
+ } catch (callbackError) {
209
+ console.error("[SSRHandler.onError] Callback error:", callbackError);
210
+ }
211
+ }
212
+ throw error;
213
+ }
214
+ };
215
+ }