@rebask/core 0.2.1 → 0.4.0

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 (210) hide show
  1. package/dist/components/head.d.ts +2 -11
  2. package/dist/components/head.d.ts.map +1 -1
  3. package/dist/components/head.js +12 -24
  4. package/dist/components/image.d.ts +16 -3
  5. package/dist/components/image.d.ts.map +1 -1
  6. package/dist/components/image.js +50 -68
  7. package/dist/components/link.d.ts.map +1 -1
  8. package/dist/components/link.js +6 -2
  9. package/dist/entry.browser.js +2 -2
  10. package/dist/entry.ssr.d.ts.map +1 -1
  11. package/dist/entry.ssr.js +12 -7
  12. package/dist/handlers/rsc/background-render.js +2 -2
  13. package/dist/handlers/rsc/render-request.js +2 -2
  14. package/dist/handlers/rsc/render-to-response.js +2 -2
  15. package/dist/handlers/rsc/state.d.ts.map +1 -1
  16. package/dist/handlers/rsc/state.js +11 -5
  17. package/dist/lib/client-route-prediction.d.ts +1 -1
  18. package/dist/lib/client-route-prediction.d.ts.map +1 -1
  19. package/dist/lib/client-route-prediction.js +2 -7
  20. package/dist/lib/head/head-manager.d.ts +9 -0
  21. package/dist/lib/head/head-manager.d.ts.map +1 -0
  22. package/dist/lib/head/head-manager.js +98 -0
  23. package/dist/lib/head/head-script.js +8 -13
  24. package/dist/lib/image-optimization/fetch-remote-image.d.ts +17 -0
  25. package/dist/lib/image-optimization/fetch-remote-image.d.ts.map +1 -0
  26. package/dist/lib/image-optimization/fetch-remote-image.js +157 -0
  27. package/dist/lib/image-optimization/get-img-props.d.ts +3 -5
  28. package/dist/lib/image-optimization/get-img-props.d.ts.map +1 -1
  29. package/dist/lib/image-optimization/get-img-props.js +66 -40
  30. package/dist/lib/image-optimization/handler.d.ts +0 -3
  31. package/dist/lib/image-optimization/handler.d.ts.map +1 -1
  32. package/dist/lib/image-optimization/handler.js +339 -124
  33. package/dist/lib/image-optimization/image-config.d.ts +31 -4
  34. package/dist/lib/image-optimization/image-config.d.ts.map +1 -1
  35. package/dist/lib/image-optimization/image-config.js +6 -0
  36. package/dist/lib/image-optimization/image-error.d.ts +8 -0
  37. package/dist/lib/image-optimization/image-error.d.ts.map +1 -0
  38. package/dist/lib/image-optimization/image-error.js +10 -0
  39. package/dist/lib/image-optimization/image-plugin.d.ts +2 -0
  40. package/dist/lib/image-optimization/image-plugin.d.ts.map +1 -1
  41. package/dist/lib/image-optimization/image-plugin.js +36 -16
  42. package/dist/lib/image-optimization/is-private-ip.d.ts +2 -0
  43. package/dist/lib/image-optimization/is-private-ip.d.ts.map +1 -0
  44. package/dist/lib/image-optimization/is-private-ip.js +43 -0
  45. package/dist/lib/image-optimization/match-patterns.d.ts +4 -0
  46. package/dist/lib/image-optimization/match-patterns.d.ts.map +1 -0
  47. package/dist/lib/image-optimization/match-patterns.js +39 -0
  48. package/dist/lib/image-optimization/read-body.d.ts +2 -0
  49. package/dist/lib/image-optimization/read-body.d.ts.map +1 -0
  50. package/dist/lib/image-optimization/read-body.js +27 -0
  51. package/dist/lib/image-optimization/static-image-loader.d.ts +2 -0
  52. package/dist/lib/image-optimization/static-image-loader.d.ts.map +1 -0
  53. package/dist/lib/image-optimization/static-image-loader.js +60 -0
  54. package/dist/lib/plugin-names.d.ts +3 -0
  55. package/dist/lib/plugin-names.d.ts.map +1 -0
  56. package/dist/lib/plugin-names.js +2 -0
  57. package/dist/lib/proxy/build-proxy.d.ts +1 -1
  58. package/dist/lib/proxy/build-proxy.d.ts.map +1 -1
  59. package/dist/lib/proxy/build-proxy.js +4 -11
  60. package/dist/lib/proxy/pattern.d.ts +7 -0
  61. package/dist/lib/proxy/pattern.d.ts.map +1 -0
  62. package/dist/lib/proxy/pattern.js +18 -0
  63. package/dist/lib/proxy/types.d.ts +3 -1
  64. package/dist/lib/proxy/types.d.ts.map +1 -1
  65. package/dist/lib/route-prediction.d.ts.map +1 -1
  66. package/dist/lib/rsc-request.d.ts.map +1 -1
  67. package/dist/lib/rsc-request.js +6 -4
  68. package/dist/lib/server-router/build-entries.d.ts +76 -0
  69. package/dist/lib/server-router/build-entries.d.ts.map +1 -0
  70. package/dist/lib/server-router/build-entries.js +74 -0
  71. package/dist/lib/server-router/get-router.d.ts.map +1 -1
  72. package/dist/lib/server-router/get-router.js +55 -105
  73. package/dist/lib/server-router/rou3.d.ts +20 -0
  74. package/dist/lib/server-router/rou3.d.ts.map +1 -0
  75. package/dist/lib/server-router/rou3.js +55 -0
  76. package/dist/lib/server-router/route-entries.d.ts +16 -0
  77. package/dist/lib/server-router/route-entries.d.ts.map +1 -0
  78. package/dist/lib/server-router/route-entries.js +67 -0
  79. package/dist/lib/server-router/types.d.ts +24 -33
  80. package/dist/lib/server-router/types.d.ts.map +1 -1
  81. package/dist/lib/server-router/types.js +1 -6
  82. package/dist/lib/types.d.ts +2 -2
  83. package/dist/lib/types.d.ts.map +1 -1
  84. package/dist/plugins/prerender.d.ts +3 -4
  85. package/dist/plugins/prerender.d.ts.map +1 -1
  86. package/dist/plugins/prerender.js +31 -0
  87. package/dist/plugins/proxy.d.ts.map +1 -1
  88. package/dist/plugins/proxy.js +7 -1
  89. package/dist/plugins/router/rou3-compiled.d.ts +9 -0
  90. package/dist/plugins/router/rou3-compiled.d.ts.map +1 -0
  91. package/dist/plugins/router/rou3-compiled.js +14 -0
  92. package/dist/plugins/router/rou3-router.d.ts +3 -0
  93. package/dist/plugins/router/rou3-router.d.ts.map +1 -0
  94. package/dist/plugins/router/rou3-router.js +16 -0
  95. package/dist/plugins/router/router-plugin.d.ts +6 -16
  96. package/dist/plugins/router/router-plugin.d.ts.map +1 -1
  97. package/dist/plugins/router/router-plugin.js +43 -22
  98. package/dist/proxy.d.ts +1 -0
  99. package/dist/proxy.d.ts.map +1 -1
  100. package/dist/proxy.js +1 -0
  101. package/dist/request-context.d.ts +0 -2
  102. package/dist/request-context.d.ts.map +1 -1
  103. package/dist/request-context.js +0 -4
  104. package/dist/router.d.ts +1 -1
  105. package/dist/router.d.ts.map +1 -1
  106. package/dist/router.js +4 -4
  107. package/dist/server-router.d.ts +3 -3
  108. package/dist/server-router.d.ts.map +1 -1
  109. package/dist/server-router.js +2 -2
  110. package/dist/vite.d.ts +8 -3
  111. package/dist/vite.d.ts.map +1 -1
  112. package/dist/vite.js +72 -33
  113. package/package.json +6 -4
  114. package/dist/components/error-boundary-client.d.ts +0 -49
  115. package/dist/components/error-boundary-client.d.ts.map +0 -1
  116. package/dist/components/error-boundary-client.js +0 -71
  117. package/dist/components/error-boundary.browser.d.ts +0 -8
  118. package/dist/components/error-boundary.browser.d.ts.map +0 -1
  119. package/dist/components/error-boundary.browser.js +0 -6
  120. package/dist/components/error.d.ts +0 -2
  121. package/dist/components/error.d.ts.map +0 -1
  122. package/dist/components/error.js +0 -5
  123. package/dist/components/not-found-activator.d.ts +0 -11
  124. package/dist/components/not-found-activator.d.ts.map +0 -1
  125. package/dist/components/not-found-activator.js +0 -26
  126. package/dist/components/not-found-boundary-client.d.ts +0 -3
  127. package/dist/components/not-found-boundary-client.d.ts.map +0 -1
  128. package/dist/components/not-found-boundary-client.js +0 -20
  129. package/dist/components/not-found-boundary.browser.d.ts +0 -8
  130. package/dist/components/not-found-boundary.browser.d.ts.map +0 -1
  131. package/dist/components/not-found-boundary.browser.js +0 -4
  132. package/dist/components/not-found-boundary.d.ts +0 -8
  133. package/dist/components/not-found-boundary.d.ts.map +0 -1
  134. package/dist/components/not-found-boundary.js +0 -9
  135. package/dist/components/not-found-thrower.d.ts +0 -2
  136. package/dist/components/not-found-thrower.d.ts.map +0 -1
  137. package/dist/components/not-found-thrower.js +0 -7
  138. package/dist/components/not-found.d.ts +0 -2
  139. package/dist/components/not-found.d.ts.map +0 -1
  140. package/dist/components/not-found.js +0 -5
  141. package/dist/components/status-page.d.ts +0 -6
  142. package/dist/components/status-page.d.ts.map +0 -1
  143. package/dist/components/status-page.js +0 -61
  144. package/dist/handler.d.ts +0 -12
  145. package/dist/handler.d.ts.map +0 -1
  146. package/dist/handler.js +0 -21
  147. package/dist/handlers/rsc/constants.d.ts +0 -3
  148. package/dist/handlers/rsc/constants.d.ts.map +0 -1
  149. package/dist/handlers/rsc/constants.js +0 -2
  150. package/dist/handlers/rsc/create-response.d.ts +0 -3
  151. package/dist/handlers/rsc/create-response.d.ts.map +0 -1
  152. package/dist/handlers/rsc/create-response.js +0 -378
  153. package/dist/handlers/rsc/render-with-retries.d.ts +0 -4
  154. package/dist/handlers/rsc/render-with-retries.d.ts.map +0 -1
  155. package/dist/handlers/rsc/render-with-retries.js +0 -154
  156. package/dist/handlers/rsc/retry-loop.d.ts +0 -3
  157. package/dist/handlers/rsc/retry-loop.d.ts.map +0 -1
  158. package/dist/handlers/rsc/retry-loop.js +0 -145
  159. package/dist/handlers/rsc/stream-rsc-payload.d.ts +0 -9
  160. package/dist/handlers/rsc/stream-rsc-payload.d.ts.map +0 -1
  161. package/dist/handlers/rsc/stream-rsc-payload.js +0 -20
  162. package/dist/handlers/rsc/types.d.ts +0 -26
  163. package/dist/handlers/rsc/types.d.ts.map +0 -1
  164. package/dist/handlers/rsc/types.js +0 -7
  165. package/dist/handlers/rsc.d.ts +0 -20
  166. package/dist/handlers/rsc.d.ts.map +0 -1
  167. package/dist/handlers/rsc.js +0 -872
  168. package/dist/integration.d.ts +0 -14
  169. package/dist/integration.d.ts.map +0 -1
  170. package/dist/integration.js +0 -9
  171. package/dist/lib/dev-module-load-race.d.ts +0 -8
  172. package/dist/lib/dev-module-load-race.d.ts.map +0 -1
  173. package/dist/lib/dev-module-load-race.js +0 -31
  174. package/dist/lib/dev-page-exports.d.ts +0 -5
  175. package/dist/lib/dev-page-exports.d.ts.map +0 -1
  176. package/dist/lib/dev-page-exports.js +0 -14
  177. package/dist/lib/environment-names.d.ts +0 -11
  178. package/dist/lib/environment-names.d.ts.map +0 -1
  179. package/dist/lib/environment-names.js +0 -10
  180. package/dist/lib/initial-hash-scroll.d.ts.map +0 -1
  181. package/dist/lib/not-found.d.ts +0 -5
  182. package/dist/lib/not-found.d.ts.map +0 -1
  183. package/dist/lib/not-found.js +0 -7
  184. package/dist/lib/page-exports.d.ts +0 -9
  185. package/dist/lib/page-exports.d.ts.map +0 -1
  186. package/dist/lib/page-exports.js +0 -47
  187. package/dist/lib/pattern-segments.d.ts +0 -8
  188. package/dist/lib/pattern-segments.d.ts.map +0 -1
  189. package/dist/lib/pattern-segments.js +0 -22
  190. package/dist/lib/ppr/cache-status.d.ts +0 -3
  191. package/dist/lib/ppr/cache-status.d.ts.map +0 -1
  192. package/dist/lib/ppr/cache-status.js +0 -5
  193. package/dist/lib/server-router-contract.d.ts +0 -42
  194. package/dist/lib/server-router-contract.d.ts.map +0 -1
  195. package/dist/lib/server-router-contract.js +0 -6
  196. package/dist/lib/server-router-loader.d.ts +0 -10
  197. package/dist/lib/server-router-loader.d.ts.map +0 -1
  198. package/dist/lib/server-router-loader.js +0 -180
  199. package/dist/lib/server-router.d.ts +0 -8
  200. package/dist/lib/server-router.d.ts.map +0 -1
  201. package/dist/lib/server-router.js +0 -173
  202. package/dist/plugins/ppr-upgrade.d.ts +0 -36
  203. package/dist/plugins/ppr-upgrade.d.ts.map +0 -1
  204. package/dist/plugins/ppr-upgrade.js +0 -32
  205. package/dist/plugins/server-router-fallback.d.ts +0 -15
  206. package/dist/plugins/server-router-fallback.d.ts.map +0 -1
  207. package/dist/plugins/server-router-fallback.js +0 -30
  208. package/dist/server.d.ts +0 -10
  209. package/dist/server.d.ts.map +0 -1
  210. package/dist/server.js +0 -57
@@ -1,872 +0,0 @@
1
- import { createTemporaryReferenceSet, decodeAction, decodeFormState, decodeReply, loadServerAction, } from "@vitejs/plugin-rsc/rsc";
2
- import { injectRSCPayload } from "rsc-html-stream/server";
3
- import { pprUpgradeConfig } from "virtual:rebask/ppr-upgrade-config";
4
- import { proxyPatterns } from "virtual:rebask/proxy-router";
5
- import { errorModules } from "../lib/app-manifest.js";
6
- import { readActionBody } from "../lib/body-size-limit.js";
7
- import {} from "../lib/cache-context.js";
8
- import { CacheEntryType } from "../lib/cache-storage.js";
9
- import { getCacheHandler } from "../lib/config.js";
10
- import { getDeploymentId, getDeploymentScopedCacheKey, REBASK_DEPLOYMENT_ID_HEADER, } from "../lib/deployment-id.js";
11
- import { sendDevError, sendDevWarning, resetDevOverlayReady, warmupSsrModules, } from "../lib/dev-server.js";
12
- import { isNotFoundError } from "../lib/errors.js";
13
- import { noPrerenderNotFoundError, noPrerenderWarning } from "../lib/messages.js";
14
- import { getCacheControlHeader, REBASK_PRERENDER_HEADER, REBASK_RESUME_HEADER, } from "../lib/ppr/headers.js";
15
- import { parseResumeState, serializeResumeState } from "../lib/ppr/postponed-state.js";
16
- import { ResumeMode } from "../lib/ppr/types.js";
17
- import { prerenderPaths } from "../lib/prerender-paths.js";
18
- import { getActiveErrorBoundary, getNearestErrorBoundaryIndex, getNearestNotFoundBoundaryIndex, maybeAbort, RenderStorage, } from "../lib/render-context.js";
19
- import {} from "../lib/route-matcher.js";
20
- import { getRoutePrediction } from "../lib/route-prediction.js";
21
- import { renderPageSegment, renderRouteSegments } from "../lib/route-renderer.js";
22
- import { buildRouteTree } from "../lib/route-tree.js";
23
- import { cacheRscStream, createRscResponse, readCachedRscResponse, renderRsc, runRscRequest, } from "../lib/rsc-request.js";
24
- import { waitForMicrotaskDrain } from "../lib/scheduler-patch.js";
25
- import { chainStreams } from "../lib/stream-utils.js";
26
- import { ActionErrorCode } from "../lib/types.js";
27
- import { getBoundaryError, getCacheLife, hasRenderError, hasRequestIO, isNotFound, markNotFound, markNotFoundThrown, markRenderError, mergeResponseHeaders, notFoundWasThrown, OutgoingResponseHeaders, setBoundaryError, withRequestStorage, } from "../request-context.js";
28
- import { actionNotFoundResponse, applyActionResponseHeaders, validateActionAccept, validateActionContentType, validateActionOrigin, } from "./server-actions.js";
29
- const STATIC = 31536000; // 365 days
30
- const CRAWLER_USER_AGENT_RE = /Googlebot(?!-)|Googlebot$|Mediapartners-Google|AdsBot-Google|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview/i;
31
- class RetryWithBoundaryError extends Error {
32
- boundaryIndex;
33
- originalError;
34
- constructor(boundaryIndex, originalError) {
35
- super(`Retry render with error boundary ${boundaryIndex}`);
36
- this.boundaryIndex = boundaryIndex;
37
- this.originalError = originalError;
38
- }
39
- }
40
- class RetryWithNotFoundBoundaryError extends Error {
41
- boundaryIndex;
42
- constructor(boundaryIndex) {
43
- super(`Retry render with not-found boundary ${boundaryIndex}`);
44
- this.boundaryIndex = boundaryIndex;
45
- }
46
- }
47
- async function cacheHtml(key, html, cacheLife, postponed) {
48
- const revalidate = cacheLife?.revalidate ?? STATIC;
49
- try {
50
- return await getCacheHandler().set(key, {
51
- type: CacheEntryType.HTML,
52
- timestamp: Date.now(),
53
- expire: cacheLife?.expire ?? revalidate,
54
- revalidate,
55
- html,
56
- tags: cacheLife?.tags?.size ? [...cacheLife.tags] : undefined,
57
- ...(postponed && { postponed }),
58
- });
59
- }
60
- catch (error) {
61
- console.error("[rebask] Failed to cache HTML:", error);
62
- }
63
- }
64
- const pendingInlineRenders = new Map();
65
- function scheduleBackgroundInlineRender(key, render) {
66
- if (pendingInlineRenders.has(key))
67
- return;
68
- const promise = (async () => {
69
- const existing = await getCacheHandler().get(key);
70
- if (existing?.type === CacheEntryType.HTML && !existing.postponed)
71
- return;
72
- await render();
73
- })()
74
- .catch((err) => console.error(`[rebask] Background inline render failed for ${key}:`, err))
75
- .finally(() => pendingInlineRenders.delete(key));
76
- pendingInlineRenders.set(key, promise);
77
- }
78
- // Pre-load the SSR entry module in production
79
- const ssrEntryModulePromise = import.meta.env.PROD ? loadSSREntryModule() : null;
80
- function loadSSREntryModule() {
81
- return import.meta.viteRsc.loadModule("ssr", "index");
82
- }
83
- async function getSSREntryModule() {
84
- return ssrEntryModulePromise ?? loadSSREntryModule();
85
- }
86
- /**
87
- * Parse the POST request body based on the request type (action, resume, or form).
88
- * Returns early Response for invalid resume requests.
89
- */
90
- async function parsePostBody(request, isResumeRequest) {
91
- const csrfRejection = validateActionOrigin(request);
92
- if (csrfRejection)
93
- return csrfRejection;
94
- if (isResumeRequest) {
95
- const resumeStateString = await request.text();
96
- if (!resumeStateString) {
97
- return new Response("Missing resume state", {
98
- status: 400,
99
- headers: {
100
- "Content-Type": "text/plain",
101
- "cache-control": "no-store",
102
- },
103
- });
104
- }
105
- return { resumeState: parseResumeState(resumeStateString) };
106
- }
107
- const actionId = request.headers.get("x-rsc-action");
108
- const responseHeaders = new OutgoingResponseHeaders();
109
- if (actionId) {
110
- const [file, name] = actionId.split("#");
111
- if (!file || !name)
112
- return actionNotFoundResponse();
113
- const acceptRejection = validateActionAccept(request);
114
- if (acceptRejection)
115
- return acceptRejection;
116
- const contentType = validateActionContentType(request, true);
117
- if (typeof contentType !== "string")
118
- return contentType;
119
- const isFormAction = contentType.startsWith("multipart/form-data") ?? false;
120
- const body = await readActionBody(request, isFormAction);
121
- if (body instanceof Response)
122
- return body;
123
- let action;
124
- try {
125
- action = await loadServerAction(actionId);
126
- if (typeof action !== "function")
127
- return actionNotFoundResponse();
128
- }
129
- catch {
130
- return actionNotFoundResponse();
131
- }
132
- const temporaryReferences = createTemporaryReferenceSet();
133
- return await withRequestStorage({ input: { request }, response: { headers: responseHeaders }, isAction: true }, async () => {
134
- try {
135
- const args = await decodeReply(body, { temporaryReferences });
136
- const data = await action.apply(null, args);
137
- return {
138
- returnValue: { ok: true, data },
139
- temporaryReferences,
140
- responseHeaders,
141
- };
142
- }
143
- catch (error) {
144
- if (isNotFoundError(error)) {
145
- return { actionError: ActionErrorCode.NotFound, temporaryReferences, responseHeaders };
146
- }
147
- else {
148
- console.error(error);
149
- return {
150
- returnValue: {
151
- ok: false,
152
- data: import.meta.env.DEV
153
- ? error
154
- : new Error("An error occurred while running a Server Action. The specific message is omitted in production builds to avoid leaking sensitive details."),
155
- },
156
- temporaryReferences,
157
- responseHeaders,
158
- };
159
- }
160
- }
161
- });
162
- }
163
- // Form action (no-JS path: native browser form submission, no `x-rsc-action`)
164
- const contentType = validateActionContentType(request, false);
165
- if (typeof contentType !== "string")
166
- return contentType;
167
- return await withRequestStorage({ input: { request }, response: { headers: responseHeaders }, isAction: true }, async () => {
168
- try {
169
- const formDataResult = await readActionBody(request, true);
170
- if (formDataResult instanceof Response)
171
- return formDataResult;
172
- const formData = formDataResult;
173
- const decodedAction = await decodeAction(formData);
174
- const result = await decodedAction();
175
- const formState = await decodeFormState(result, formData);
176
- return { formState, responseHeaders };
177
- }
178
- catch (error) {
179
- if (isNotFoundError(error)) {
180
- return { actionError: ActionErrorCode.NotFound, responseHeaders };
181
- }
182
- else {
183
- console.error(error);
184
- return { actionThrownError: error, responseHeaders };
185
- }
186
- }
187
- });
188
- }
189
- function buildErrorRoute(errorPath) {
190
- return {
191
- type: "error",
192
- filePath: errorPath,
193
- segments: [{ type: "static", value: "" }],
194
- layouts: [],
195
- };
196
- }
197
- function buildDefaultErrorRoute() {
198
- return {
199
- type: "error",
200
- segments: [],
201
- layouts: [],
202
- };
203
- }
204
- function onNotFound() {
205
- if (import.meta.env.DEV && notFoundWasThrown()) {
206
- const msg = "A `<NotFoundBoundary>` fallback called `notFound()` during server rendering. A " +
207
- "fallback must render UI, not re-trigger `notFound()` — if you need it to escalate " +
208
- "to the parent boundary, make it a client component so React can handle in the client.";
209
- console.error("[rebask]", msg);
210
- sendDevError(msg);
211
- }
212
- markNotFoundThrown();
213
- }
214
- /**
215
- * Page route handler for RSC rendering.
216
- * Called by the router after a page route is matched, and during prerendering.
217
- */
218
- export async function handlePageRoute(request, route, params, handleOptions) {
219
- const { isBuildPrerender = false, onBuildPrerenderMeta } = handleOptions ?? {};
220
- const url = new URL(request.url);
221
- const isPost = request.method === "POST";
222
- const isResumeRequest = isPost && request.headers.get(REBASK_RESUME_HEADER) === "1";
223
- const isNotFoundRoute = route.type === "not-found";
224
- const isErrorRoute = route.type === "error";
225
- const isPageRoute = route.type === "page";
226
- // `handlePageRoute` does recursive calls with the original request, so we have check that the
227
- // page type hasn't changed.
228
- const isActionOrResumeRequest = isPost && !isNotFoundRoute && !isErrorRoute;
229
- let postBody = {};
230
- if (isActionOrResumeRequest) {
231
- // Parse POST body (actions, resume requests, or form submissions).
232
- const result = await parsePostBody(request, isResumeRequest);
233
- if (result instanceof Response)
234
- return result;
235
- postBody = result;
236
- }
237
- const { returnValue, formState, temporaryReferences, actionError, actionThrownError } = postBody;
238
- // resumeState can come from POST body or from cache lookup later
239
- let resumeState = postBody.resumeState;
240
- const match = { route, params };
241
- const isRscRequest = request.headers.get("accept")?.includes("text/x-component") ?? false;
242
- if (!isRscRequest && import.meta.env.DEV) {
243
- resetDevOverlayReady();
244
- }
245
- const isActionRscRequest = isActionOrResumeRequest && isRscRequest && !isResumeRequest;
246
- const isHtmlRequest = !isPost && !isRscRequest && !isErrorRoute;
247
- const routeTree = buildRouteTree(match);
248
- const routePrediction = getRoutePrediction(route);
249
- const isBotRequest = __REBASK_FULL_HTML_FOR_BOTS &&
250
- isHtmlRequest &&
251
- CRAWLER_USER_AGENT_RE.test(request.headers.get("user-agent") ?? "");
252
- // Handle route tree request (JSON, no async context needed)
253
- const isTreeRequest = request.headers.get("x-rebask-tree") === "1";
254
- if (isTreeRequest) {
255
- const routeJson = JSON.stringify(routePrediction ? [routeTree, routePrediction] : [routeTree]);
256
- return new Response(routeJson, {
257
- headers: {
258
- "content-type": "application/json",
259
- // TODO: not sure if this can be cached, to evaluate later.
260
- "cache-control": getCacheControlHeader({ revalidate: 0 }),
261
- vary: "accept, x-rebask-tree",
262
- ...(getDeploymentId() ? { [REBASK_DEPLOYMENT_ID_HEADER]: getDeploymentId() } : {}),
263
- },
264
- });
265
- }
266
- if (isRscRequest && !isPost && !resumeState && !isErrorRoute) {
267
- const cachedResponse = await readCachedRscResponse(url);
268
- if (cachedResponse)
269
- return cachedResponse;
270
- }
271
- // PPR: Should we prerender and detect postponed content
272
- const shouldPrerender = !__REBASK_SSR_STRATEGY && !isBotRequest && isHtmlRequest;
273
- // Static generation mode (for data caching - includes RSC requests)
274
- const isStaticGen = import.meta.env.PROD && !isPost;
275
- const isDynamicRoute = route.segments.some((seg) => seg.type !== "static");
276
- // Runtime prerender: render to completion (full HTML) instead of aborting on dynamic.
277
- let isPrerenderUpgrade = false;
278
- if (pprUpgradeConfig && !isResumeRequest) {
279
- isPrerenderUpgrade = request.headers.get(pprUpgradeConfig.forceFullRenderHeader) !== null;
280
- }
281
- // If the params are resolved, then `routeParams()` resolves synchronously during prerendering
282
- // and we don't abort the shell if it's used.
283
- //
284
- // Not founds are the exception: their params describe the unmatched path, which is per-request
285
- // data. Resolving them inline at build would bake one path's value into a shared shell and serve
286
- // it for every unmatched URL. Treating them as unresolved keeps `routeParams()` dynamic, so a
287
- // not-found fallback that reads the path is captured as a per-request resume (and, when it lives
288
- // inside <Suspense>, the `notFound()` surfaces during that resume and `NotFoundBoundary`
289
- // recovers it on the client).
290
- const paramsResolved = !isNotFoundRoute &&
291
- (isBuildPrerender
292
- ? route.segments.every((seg) => seg.type === "static" || seg.type === "group" || seg.name in params)
293
- : isPrerenderUpgrade); // The adapter can enable runtime prerendering.
294
- let htmlCacheKey = null;
295
- // URL-specific cache key preserved before the lookup may overwrite
296
- // htmlCacheKey to the shell key for dynamic routes. Used to cache the
297
- // full page result (shell + resume) after the first PPR resume.
298
- let fullPageCacheKey = null;
299
- let isNoShellRoute = false;
300
- let htmlCacheEntry = null;
301
- if (shouldPrerender) {
302
- if (isPageRoute) {
303
- isNoShellRoute = await prerenderPaths.hasNoShell(route.filePath);
304
- }
305
- if (isNotFoundRoute) {
306
- htmlCacheKey = getDeploymentScopedCacheKey("html:__default_not_found__");
307
- }
308
- else {
309
- htmlCacheKey = getDeploymentScopedCacheKey(`html:${url.pathname}`);
310
- fullPageCacheKey = htmlCacheKey;
311
- }
312
- }
313
- if (htmlCacheKey && !isResumeRequest && !isErrorRoute) {
314
- let cachedEntry = null;
315
- if (isDynamicRoute && isPageRoute) {
316
- const shellKey = getDeploymentScopedCacheKey(`html:${route.filePath}`);
317
- const [pathnameEntry, shellEntry] = await Promise.all([
318
- getCacheHandler().get(htmlCacheKey),
319
- getCacheHandler().get(shellKey),
320
- ]);
321
- if (pathnameEntry?.type === CacheEntryType.HTML) {
322
- cachedEntry = pathnameEntry;
323
- }
324
- else if (shellEntry?.type === CacheEntryType.HTML) {
325
- cachedEntry = shellEntry;
326
- htmlCacheKey = shellKey;
327
- }
328
- }
329
- else {
330
- const entry = await getCacheHandler().get(htmlCacheKey);
331
- if (entry?.type === CacheEntryType.HTML) {
332
- cachedEntry = entry;
333
- }
334
- }
335
- if (cachedEntry) {
336
- if (cachedEntry.postponed) {
337
- // The cached entry has postponed state for PPR resume
338
- const parsedState = parseResumeState(cachedEntry.postponed);
339
- // Only ResumeMode.HTML (Suspense holes) for cached entries are expected.
340
- // ResumeMode.RSC means dynamic access outside Suspense, which shouldn't
341
- // be cached.
342
- if (parsedState.type === ResumeMode.HTML) {
343
- htmlCacheEntry = cachedEntry;
344
- resumeState = parsedState;
345
- if (isBuildPrerender && onBuildPrerenderMeta) {
346
- onBuildPrerenderMeta({
347
- hasShell: true,
348
- postponedState: cachedEntry.postponed,
349
- cacheLife: {
350
- revalidate: cachedEntry.revalidate,
351
- expire: cachedEntry.expire,
352
- },
353
- });
354
- }
355
- }
356
- }
357
- else {
358
- // Fully static cached HTML - return it directly
359
- if (isBuildPrerender && onBuildPrerenderMeta) {
360
- onBuildPrerenderMeta({
361
- hasShell: true,
362
- cacheLife: {
363
- revalidate: cachedEntry.revalidate,
364
- expire: cachedEntry.expire,
365
- },
366
- });
367
- }
368
- return new Response(cachedEntry.html, {
369
- status: isNotFoundRoute ? 404 : 200,
370
- headers: {
371
- "Content-Type": "text/html; charset=utf-8",
372
- "Cache-Control": getCacheControlHeader({
373
- revalidate: cachedEntry.revalidate,
374
- expire: cachedEntry.expire,
375
- }),
376
- "x-rebask-cache": "HIT",
377
- vary: "accept",
378
- ...(isNotFound() && { "x-robots-tag": "noindex" }),
379
- },
380
- });
381
- }
382
- }
383
- }
384
- // Pre-warm SSR route modules before the RSC render registers inline server
385
- // references. Importing the same modules in the SSR environment afterwards
386
- // can invalidate plugin-rsc's dev server reference metadata.
387
- if (import.meta.env.DEV && !isRscRequest) {
388
- const warmupModules = new Set();
389
- if (isPageRoute)
390
- warmupModules.add(route.filePath);
391
- for (const layout of route.layouts)
392
- warmupModules.add(layout);
393
- await warmupSsrModules([...warmupModules]);
394
- }
395
- // Server action RSC requests only return their action result or form state.
396
- // Other RSC requests, including PPR resume requests, render route data.
397
- const shouldRenderRscSegment = isRscRequest && !isActionRscRequest;
398
- // For RSC segment requests, render only the page segment.
399
- // For HTML requests (SSR), render the full tree.
400
- let seedData = undefined;
401
- async function createResponse(rscStream) {
402
- function getResponseStatus() {
403
- if (isErrorRoute || getActiveErrorBoundary() !== undefined)
404
- return 500;
405
- if (isNotFoundRoute || isNotFound())
406
- return 404;
407
- if (isRscRequest)
408
- return 200;
409
- if (actionError === ActionErrorCode.NotFound)
410
- return 404;
411
- return 200;
412
- }
413
- if (isRscRequest) {
414
- // If the action threw an error, we consider the response a 500.
415
- const rscStatus = returnValue?.ok === false ? 500 : getResponseStatus();
416
- // See if the RSC can be cached, only when it's not a resume request.
417
- if (!isPost &&
418
- !isErrorRoute &&
419
- !resumeState &&
420
- !hasRequestIO() &&
421
- !hasRenderError() &&
422
- !isNotFound()) {
423
- const [responseStream, cacheStream] = rscStream.tee();
424
- cacheRscStream(url, cacheStream).catch((error) => console.error("[rebask] Failed to cache RSC:", error));
425
- return createRscResponse(responseStream, {}, rscStatus);
426
- }
427
- return createRscResponse(rscStream, isActionRscRequest ? { "cache-control": "no-store" } : {}, rscStatus);
428
- }
429
- const ssrEntryModule = await getSSREntryModule();
430
- function teeWithBackgroundInlineRender(combinedStream, cacheKey) {
431
- const [clientStream, observerStream] = combinedStream.tee();
432
- new Response(observerStream)
433
- .text()
434
- .then(() => {
435
- if (hasRequestIO() || hasRenderError())
436
- return;
437
- scheduleBackgroundInlineRender(cacheKey, async () => {
438
- await runRscRequest({
439
- input: { request, params },
440
- response: { headers: new OutgoingResponseHeaders() },
441
- }, { errorBoundary: {}, notFoundBoundary: {} }, async () => {
442
- if (!seedData) {
443
- throw new Error("Expected route seed data for background inline render.");
444
- }
445
- // Re-render RSC with the same payload
446
- const bgRscStream = renderRsc({
447
- seedData,
448
- routeTree,
449
- ...(routePrediction && { routePrediction }),
450
- ...(proxyPatterns && { proxyPatterns }),
451
- }, { temporaryReferences });
452
- const [bgRscStream1, bgRscStream2] = bgRscStream.tee();
453
- const routerCache = await ssrEntryModule.getRouterCache(bgRscStream1, routeTree);
454
- const bgResult = await ssrEntryModule.prerenderHTML(bgRscStream2, routerCache, {
455
- renderToCompletion: true, // forces React to render Suspense inline
456
- });
457
- // Pathname cache entries must be standalone full HTML documents.
458
- if (bgResult.postponed)
459
- return;
460
- const html = await new Response(bgResult.prelude).text();
461
- if (hasRequestIO() || hasRenderError())
462
- return;
463
- await cacheHtml(cacheKey, html, getCacheLife());
464
- });
465
- });
466
- })
467
- .catch((error) => {
468
- console.error("[rebask] Failed to read response html:", error);
469
- });
470
- return clientStream;
471
- }
472
- try {
473
- let responseStream;
474
- let isStatic = false;
475
- let isPrerenderedResume = false;
476
- let cacheLife;
477
- if (resumeState?.type === ResumeMode.HTML) {
478
- const [rscStream1, rscStream2] = rscStream.tee();
479
- const cache = await ssrEntryModule.getRouterCache(rscStream1, routeTree);
480
- const resumePromise = ssrEntryModule.resumeHTML(rscStream2, cache, resumeState.postponed, {
481
- routeTree,
482
- });
483
- // HTML resume - generate content to fill the "holes"
484
- if (htmlCacheEntry) {
485
- const shellStream = new Response(htmlCacheEntry.html).body;
486
- // Internal cache HIT:
487
- // 1. Start streaming cached shell immediately
488
- // 2. Resume prepares in the background and streams after the shell
489
- const combinedStream = chainStreams(shellStream, resumePromise);
490
- if (fullPageCacheKey) {
491
- responseStream = teeWithBackgroundInlineRender(combinedStream, fullPageCacheKey);
492
- }
493
- else {
494
- responseStream = combinedStream;
495
- }
496
- isPrerenderedResume = true;
497
- }
498
- else {
499
- // External resume request - just send resume content
500
- responseStream = await resumePromise;
501
- }
502
- }
503
- else if (resumeState?.type === ResumeMode.RSC) {
504
- // External PPR resume (RSC only)
505
- responseStream = streamRSCPayload(rscStream);
506
- // We skip prerendering if we know the route has no shell, but not at
507
- // build time.
508
- }
509
- else if (shouldPrerender && (!isNoShellRoute || isBuildPrerender)) {
510
- // PPR: Prerender to capture static shell, then resume for dynamic content
511
- const pprContext = RenderStorage.getStore()?.ppr;
512
- const [rscStream1, rscStream2] = rscStream.tee();
513
- const cache = await ssrEntryModule.getRouterCache(rscStream1, routeTree);
514
- // Prerender to capture the static shell. Any suspended components
515
- // (due to dynamic functions, async data) are captured as postponed state.
516
- const result = ssrEntryModule.prerenderHTML(rscStream2, cache, {
517
- signal: pprContext?.controller.signal,
518
- onShell: () => {
519
- maybeAbort("prerender");
520
- },
521
- // For upgrade renders we want the cached HTML to render the resolved Suspense content
522
- // inline so a refresh shows real content immediately instead of using the fallback.
523
- renderToCompletion: isPrerenderUpgrade,
524
- });
525
- // Wait for React's microtask queue to drain, indicating Fizz is idle
526
- // and waiting at our hanging promise. Then abort to capture the static
527
- // shell.
528
- waitForMicrotaskDrain().then(() => {
529
- maybeAbort("rsc");
530
- });
531
- const { prelude, postponed } = await result;
532
- const [preludeForClient, preludeForCache] = prelude.tee();
533
- const postponedState = postponed
534
- ? serializeResumeState({ type: ResumeMode.HTML, postponed })
535
- : undefined;
536
- // When replaySlots === -1, it means the ROOT was postponed (dynamic access
537
- // happened at the top level without any Suspense boundary). In this case,
538
- // there's no static shell worth caching.
539
- // See: React's getPostponedState in ReactFizzServer.js
540
- const rootPostponed = postponed
541
- ? postponed.replaySlots === -1
542
- : false;
543
- if (rootPostponed) {
544
- if (isPageRoute) {
545
- prerenderPaths.registerNoShell(route.filePath);
546
- }
547
- if (import.meta.env.DEV) {
548
- const devWarningMessage = noPrerenderWarning(url.pathname);
549
- console.warn(devWarningMessage);
550
- sendDevWarning(devWarningMessage);
551
- }
552
- }
553
- // notFound() can be thrown inside a <Suspense> boundary, where it's captured as postponed
554
- // content, so the prerender still produced a shell. When that happens, activate the next
555
- // boundary and retry.
556
- if (notFoundWasThrown()) {
557
- const nextNotFoundIndex = getNearestNotFoundBoundaryIndex();
558
- if (nextNotFoundIndex !== undefined) {
559
- prelude.cancel().catch(console.error);
560
- throw new RetryWithNotFoundBoundaryError(nextNotFoundIndex);
561
- }
562
- }
563
- if (postponed) {
564
- // A valid shell exists if the root wasn't postponed
565
- if (isBuildPrerender) {
566
- // Build-time prerendering only needs the shell. Skip resume.
567
- responseStream = preludeForClient;
568
- }
569
- else {
570
- // PPR: There's postponed content (Suspense or async component suspended)
571
- if (!seedData) {
572
- throw new Error("Expected route seed data for PPR resume response.");
573
- }
574
- // Resume runs in a fresh RenderStorage context (preserving activated boundary indices)
575
- // so stale registry state from the prerender doesn't leak into the resume.
576
- const currentRenderContext = RenderStorage.getStore();
577
- const [resumeRscStream1, resumeRscStream2] = RenderStorage.run({
578
- errorBoundary: { activeIndex: currentRenderContext.errorBoundary.activeIndex },
579
- notFoundBoundary: {
580
- activeIndex: currentRenderContext.notFoundBoundary.activeIndex,
581
- },
582
- ppr: { controller: new AbortController(), abortOnDynamic: false, paramsResolved },
583
- }, () => renderRsc({
584
- seedData,
585
- routeTree,
586
- ...(routePrediction && { routePrediction }),
587
- ...(proxyPatterns && { proxyPatterns }),
588
- }, { temporaryReferences }, onNotFound).tee());
589
- const cache = await ssrEntryModule.getRouterCache(resumeRscStream1, routeTree);
590
- const resumeStream = await ssrEntryModule.resumeHTML(resumeRscStream2, cache, postponed, { routeTree });
591
- if (rootPostponed) {
592
- // Root was postponed - prelude is nothing (just closing tags).
593
- // Use resume stream directly which contains complete HTML.
594
- // Cancel the broken prelude to avoid resource leaks.
595
- preludeForClient.cancel().catch(console.error);
596
- preludeForCache.cancel().catch(console.error);
597
- responseStream = resumeStream;
598
- }
599
- else {
600
- // Concatenate: static shell + dynamic content
601
- const combinedStream = chainStreams(preludeForClient, resumeStream);
602
- if (fullPageCacheKey) {
603
- responseStream = teeWithBackgroundInlineRender(combinedStream, fullPageCacheKey);
604
- }
605
- else {
606
- responseStream = combinedStream;
607
- }
608
- }
609
- }
610
- }
611
- else {
612
- // Fully static - no dynamic content
613
- isStatic = true;
614
- responseStream = preludeForClient;
615
- }
616
- if (isBuildPrerender && rootPostponed && isNotFoundRoute) {
617
- // Not-found pages must be prerenderable at build time
618
- throw new Error(noPrerenderNotFoundError("(default)"));
619
- }
620
- // Capture aggregated cache life values from async context for static
621
- // content. This is done after prerendering completes when all
622
- // cacheLife() calls have been processed.
623
- if (isStatic)
624
- cacheLife = getCacheLife();
625
- // A servable static shell does not exit when:
626
- // - `rootPostponed`: dynamic data was accessed at the root with no <Suspense> to
627
- // absorb it, so React produced no prelude.
628
- // - `isNotFound()`: the render resolved to notFound(), which is never a servable shell.
629
- // The router's catch-all route for unmatched URLs is the exception — a 404 is its
630
- // shell. This is what forces pages put `notFound()` behind dynamic IO inside <Suspense>.
631
- const hasServableShell = !rootPostponed && (isNotFoundRoute || !isNotFound());
632
- if (htmlCacheKey && hasServableShell && !getBoundaryError()) {
633
- // If the page has postponed content, we cache the static shell so future requests can
634
- // resume from it.
635
- const isSharedShell = !!postponedState && !paramsResolved && (isPageRoute || isNotFoundRoute);
636
- // If the page is fully static (no postponed content), `preludeForCache` is the
637
- // complete HTML and gets cached with no postponed state.
638
- if (!postponedState || isSharedShell) {
639
- const cacheKey = isSharedShell
640
- ? isPageRoute
641
- ? getDeploymentScopedCacheKey(`html:${route.filePath}`)
642
- : htmlCacheKey
643
- : htmlCacheKey;
644
- const cacheWrite = new Response(preludeForCache)
645
- .text()
646
- .then((html) => cacheHtml(cacheKey, html, cacheLife, postponedState));
647
- // Await the write only during the build, just so that `handlePageRoute` finishes
648
- // when the cache has been written.
649
- if (isBuildPrerender)
650
- await cacheWrite;
651
- }
652
- else {
653
- preludeForCache.cancel().catch(console.error);
654
- }
655
- }
656
- if (isBuildPrerender && onBuildPrerenderMeta) {
657
- onBuildPrerenderMeta({
658
- hasShell: hasServableShell,
659
- postponedState,
660
- ...(rootPostponed && {
661
- cacheLife: {
662
- revalidate: cacheLife?.revalidate ?? STATIC,
663
- expire: cacheLife?.expire ?? cacheLife?.revalidate ?? STATIC,
664
- },
665
- }),
666
- });
667
- }
668
- }
669
- else {
670
- // Non-prerendering path: dynamic content, streaming
671
- const [rscStream1, rscStream2] = rscStream.tee();
672
- const cache = await ssrEntryModule.getRouterCache(rscStream1, routeTree);
673
- if (isBotRequest) {
674
- const result = await ssrEntryModule.prerenderHTML(rscStream2, cache, {
675
- renderToCompletion: true,
676
- });
677
- responseStream = result.prelude;
678
- }
679
- else {
680
- responseStream = await ssrEntryModule.renderHTML(rscStream2, cache, {
681
- formState,
682
- routeTree,
683
- });
684
- }
685
- }
686
- // Build Cache-Control header:
687
- // - Static content: use aggregated cacheLife values (safe to cache at CDN)
688
- // - Dynamic/streaming content (including prerendered resume): no caching
689
- // because the dynamic parts change per-request
690
- const cacheControl = isStatic
691
- ? getCacheControlHeader({
692
- revalidate: cacheLife?.revalidate ?? STATIC,
693
- expire: cacheLife?.expire,
694
- })
695
- : getCacheControlHeader({ revalidate: 0 });
696
- const response = new Response(responseStream, {
697
- status: getResponseStatus(),
698
- headers: mergeResponseHeaders({
699
- "Content-Type": "text/html; charset=utf-8",
700
- vary: "accept",
701
- "Cache-Control": cacheControl,
702
- ...(isNotFoundRoute && { "x-robots-tag": "noindex" }),
703
- "x-rebask-cache": isErrorRoute ? "MISS" : isPrerenderedResume ? "HIT" : "MISS",
704
- ...(isPrerenderedResume && { [REBASK_PRERENDER_HEADER]: "1" }),
705
- ...(getDeploymentId() ? { [REBASK_DEPLOYMENT_ID_HEADER]: getDeploymentId() } : {}),
706
- }),
707
- });
708
- return response;
709
- }
710
- catch (error) {
711
- markRenderError();
712
- // We want to throw during prerendering, instead of rendering an error page.
713
- if (isBuildPrerender && !isNotFound()) {
714
- throw error;
715
- }
716
- // Handle 404 if called before a Suspense boundary.
717
- if (isNotFound()) {
718
- // Render the nearest `NotFoundBoundary` only if the current render has called `notFound()`
719
- if (notFoundWasThrown()) {
720
- // `isNotFound()` is the sticky 404 status (persists across retries).
721
- // `notFoundWasThrown()` is the per-render state.
722
- const nextNotFoundIndex = getNearestNotFoundBoundaryIndex();
723
- if (nextNotFoundIndex !== undefined) {
724
- throw new RetryWithNotFoundBoundaryError(nextNotFoundIndex);
725
- }
726
- }
727
- // Fall back to the built-in default 404
728
- return handlePageRoute(request, { type: "not-found", segments: [], layouts: [], errorPath: route.errorPath }, params, handleOptions);
729
- }
730
- const nextBoundaryIndex = getNearestErrorBoundaryIndex();
731
- if (!isErrorRoute && nextBoundaryIndex !== undefined) {
732
- throw new RetryWithBoundaryError(nextBoundaryIndex, error);
733
- }
734
- const rootErrorPath = route.errorPath;
735
- // Once a framework error route itself fails, fall back to the built-in default.
736
- if (isErrorRoute) {
737
- return handlePageRoute(request, buildDefaultErrorRoute(), params, handleOptions);
738
- }
739
- if (rootErrorPath && errorModules[rootErrorPath]) {
740
- const rootErrorRoute = buildErrorRoute(rootErrorPath);
741
- return handlePageRoute(request, rootErrorRoute, params, handleOptions);
742
- }
743
- return handlePageRoute(request, buildDefaultErrorRoute(), params, handleOptions);
744
- }
745
- }
746
- // We're in PPR mode if we should prerender, this is not a resume request, we don't have a
747
- // cached HTML entry already, and the route hasn't been flagged as having no shell
748
- // (except at build time).
749
- const isPpr = shouldPrerender && !isResumeRequest && !htmlCacheEntry && (!isNoShellRoute || isBuildPrerender);
750
- // In upgrade mode, we still want the PPR render path (so cacheLife/postponed handling stays
751
- // consistent) but we must NOT abort on dynamic access, as the goal is a fully-resolved render.
752
- const shouldAbortOnDynamic = isPpr && !isPrerenderUpgrade;
753
- let attempt = {};
754
- let response;
755
- while (true) {
756
- const abortOnDynamic = shouldAbortOnDynamic && attempt.activeErrorIndex === undefined;
757
- const renderContext = {
758
- errorBoundary: {
759
- isErrorPage: isErrorRoute,
760
- activeIndex: attempt.activeErrorIndex,
761
- },
762
- notFoundBoundary: {
763
- activeIndex: attempt.activeNotFoundIndex,
764
- pathnameMode: route.type === "not-found" ? "client" : "request",
765
- },
766
- ...(isPpr && {
767
- ppr: { controller: new AbortController(), abortOnDynamic, paramsResolved },
768
- }),
769
- };
770
- try {
771
- response = await runRscRequest({
772
- input: { request, params: match.params, isStaticGeneration: isStaticGen },
773
- response: { headers: new OutgoingResponseHeaders() },
774
- }, renderContext, async () => {
775
- if (attempt.boundaryError)
776
- setBoundaryError(attempt.boundaryError);
777
- if (attempt.notFound)
778
- markNotFound();
779
- if (!isBuildPrerender &&
780
- isDynamicRoute &&
781
- isPageRoute &&
782
- (await prerenderPaths.isStaticPathMiss(route.filePath, url.pathname))) {
783
- markNotFoundThrown();
784
- }
785
- // No-JS native forms cannot consume RSC streams, so they recurse to render the full HTML
786
- // page. We bridge the action's 404 state here to ensure the subsequent page render knows to
787
- // activate the not-found boundaries.
788
- if (actionError === ActionErrorCode.NotFound && !isRscRequest) {
789
- markNotFound();
790
- }
791
- // Build seedData inside the request scope so renderPage can read isNotFound().
792
- // For non-PPR routes this only needs to run once, but rebuilding is cheap because
793
- // module loaders are cached.
794
- seedData = shouldRenderRscSegment
795
- ? await renderPageSegment(match, attempt.boundaryError)
796
- : isRscRequest
797
- ? undefined
798
- : await renderRouteSegments(match, routeTree,
799
- // Native no-JS form submissions execute the action before the main render starts.
800
- // Re-inject unexpected action errors to activate the nearest error boundary
801
- !attempt.boundaryError ? actionThrownError : undefined);
802
- const boundaryError = getBoundaryError();
803
- let payload;
804
- if (isActionRscRequest) {
805
- if (returnValue !== undefined) {
806
- payload = { returnValue };
807
- }
808
- else if (formState !== undefined) {
809
- payload = { formState };
810
- }
811
- else if (actionError !== undefined) {
812
- payload = { actionError };
813
- }
814
- else {
815
- throw new Error("Expected an action result, form state, or action error for action RSC response.");
816
- }
817
- }
818
- else {
819
- if (!seedData) {
820
- throw new Error("Expected route seed data for route RSC response.");
821
- }
822
- payload = {
823
- seedData,
824
- routeTree,
825
- ...(routePrediction && { routePrediction }),
826
- ...(proxyPatterns && { proxyPatterns }),
827
- ...(formState !== undefined && { formState }),
828
- ...(boundaryError && { error: boundaryError }),
829
- };
830
- }
831
- const stream = isActionRscRequest
832
- ? renderRsc(payload, { temporaryReferences })
833
- : renderRsc(payload, { temporaryReferences }, onNotFound);
834
- return await createResponse(stream);
835
- });
836
- break;
837
- }
838
- catch (error) {
839
- if (error instanceof RetryWithBoundaryError) {
840
- attempt.activeErrorIndex = error.boundaryIndex;
841
- attempt.boundaryError = error.originalError;
842
- continue;
843
- }
844
- if (error instanceof RetryWithNotFoundBoundaryError) {
845
- attempt.activeNotFoundIndex = error.boundaryIndex;
846
- attempt.notFound = true;
847
- continue;
848
- }
849
- throw error;
850
- }
851
- }
852
- return applyActionResponseHeaders(response, postBody.responseHeaders);
853
- }
854
- const closingTagsBytes = new TextEncoder().encode("</body></html>");
855
- /**
856
- * Stream RSC payload for ResumeMode.RSC.
857
- *
858
- * The HTML shell is complete (served from cache), we just need to send fresh
859
- * RSC data for React to reconcile on the client.
860
- */
861
- function streamRSCPayload(rscStream) {
862
- // Create a minimal stream with just closing tags
863
- // injectRSCPayload will insert RSC script tags before them
864
- const closingStream = new ReadableStream({
865
- start(controller) {
866
- controller.enqueue(closingTagsBytes);
867
- controller.close();
868
- },
869
- });
870
- // Inject RSC payload as script tags before closing tags
871
- return closingStream.pipeThrough(injectRSCPayload(rscStream));
872
- }