@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.
- package/dist/components/head.d.ts +2 -11
- package/dist/components/head.d.ts.map +1 -1
- package/dist/components/head.js +12 -24
- package/dist/components/image.d.ts +16 -3
- package/dist/components/image.d.ts.map +1 -1
- package/dist/components/image.js +50 -68
- package/dist/components/link.d.ts.map +1 -1
- package/dist/components/link.js +6 -2
- package/dist/entry.browser.js +2 -2
- package/dist/entry.ssr.d.ts.map +1 -1
- package/dist/entry.ssr.js +12 -7
- package/dist/handlers/rsc/background-render.js +2 -2
- package/dist/handlers/rsc/render-request.js +2 -2
- package/dist/handlers/rsc/render-to-response.js +2 -2
- package/dist/handlers/rsc/state.d.ts.map +1 -1
- package/dist/handlers/rsc/state.js +11 -5
- package/dist/lib/client-route-prediction.d.ts +1 -1
- package/dist/lib/client-route-prediction.d.ts.map +1 -1
- package/dist/lib/client-route-prediction.js +2 -7
- package/dist/lib/head/head-manager.d.ts +9 -0
- package/dist/lib/head/head-manager.d.ts.map +1 -0
- package/dist/lib/head/head-manager.js +98 -0
- package/dist/lib/head/head-script.js +8 -13
- package/dist/lib/image-optimization/fetch-remote-image.d.ts +17 -0
- package/dist/lib/image-optimization/fetch-remote-image.d.ts.map +1 -0
- package/dist/lib/image-optimization/fetch-remote-image.js +157 -0
- package/dist/lib/image-optimization/get-img-props.d.ts +3 -5
- package/dist/lib/image-optimization/get-img-props.d.ts.map +1 -1
- package/dist/lib/image-optimization/get-img-props.js +66 -40
- package/dist/lib/image-optimization/handler.d.ts +0 -3
- package/dist/lib/image-optimization/handler.d.ts.map +1 -1
- package/dist/lib/image-optimization/handler.js +339 -124
- package/dist/lib/image-optimization/image-config.d.ts +31 -4
- package/dist/lib/image-optimization/image-config.d.ts.map +1 -1
- package/dist/lib/image-optimization/image-config.js +6 -0
- package/dist/lib/image-optimization/image-error.d.ts +8 -0
- package/dist/lib/image-optimization/image-error.d.ts.map +1 -0
- package/dist/lib/image-optimization/image-error.js +10 -0
- package/dist/lib/image-optimization/image-plugin.d.ts +2 -0
- package/dist/lib/image-optimization/image-plugin.d.ts.map +1 -1
- package/dist/lib/image-optimization/image-plugin.js +36 -16
- package/dist/lib/image-optimization/is-private-ip.d.ts +2 -0
- package/dist/lib/image-optimization/is-private-ip.d.ts.map +1 -0
- package/dist/lib/image-optimization/is-private-ip.js +43 -0
- package/dist/lib/image-optimization/match-patterns.d.ts +4 -0
- package/dist/lib/image-optimization/match-patterns.d.ts.map +1 -0
- package/dist/lib/image-optimization/match-patterns.js +39 -0
- package/dist/lib/image-optimization/read-body.d.ts +2 -0
- package/dist/lib/image-optimization/read-body.d.ts.map +1 -0
- package/dist/lib/image-optimization/read-body.js +27 -0
- package/dist/lib/image-optimization/static-image-loader.d.ts +2 -0
- package/dist/lib/image-optimization/static-image-loader.d.ts.map +1 -0
- package/dist/lib/image-optimization/static-image-loader.js +60 -0
- package/dist/lib/plugin-names.d.ts +3 -0
- package/dist/lib/plugin-names.d.ts.map +1 -0
- package/dist/lib/plugin-names.js +2 -0
- package/dist/lib/proxy/build-proxy.d.ts +1 -1
- package/dist/lib/proxy/build-proxy.d.ts.map +1 -1
- package/dist/lib/proxy/build-proxy.js +4 -11
- package/dist/lib/proxy/pattern.d.ts +7 -0
- package/dist/lib/proxy/pattern.d.ts.map +1 -0
- package/dist/lib/proxy/pattern.js +18 -0
- package/dist/lib/proxy/types.d.ts +3 -1
- package/dist/lib/proxy/types.d.ts.map +1 -1
- package/dist/lib/route-prediction.d.ts.map +1 -1
- package/dist/lib/rsc-request.d.ts.map +1 -1
- package/dist/lib/rsc-request.js +6 -4
- package/dist/lib/server-router/build-entries.d.ts +76 -0
- package/dist/lib/server-router/build-entries.d.ts.map +1 -0
- package/dist/lib/server-router/build-entries.js +74 -0
- package/dist/lib/server-router/get-router.d.ts.map +1 -1
- package/dist/lib/server-router/get-router.js +55 -105
- package/dist/lib/server-router/rou3.d.ts +20 -0
- package/dist/lib/server-router/rou3.d.ts.map +1 -0
- package/dist/lib/server-router/rou3.js +55 -0
- package/dist/lib/server-router/route-entries.d.ts +16 -0
- package/dist/lib/server-router/route-entries.d.ts.map +1 -0
- package/dist/lib/server-router/route-entries.js +67 -0
- package/dist/lib/server-router/types.d.ts +24 -33
- package/dist/lib/server-router/types.d.ts.map +1 -1
- package/dist/lib/server-router/types.js +1 -6
- package/dist/lib/types.d.ts +2 -2
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/plugins/prerender.d.ts +3 -4
- package/dist/plugins/prerender.d.ts.map +1 -1
- package/dist/plugins/prerender.js +31 -0
- package/dist/plugins/proxy.d.ts.map +1 -1
- package/dist/plugins/proxy.js +7 -1
- package/dist/plugins/router/rou3-compiled.d.ts +9 -0
- package/dist/plugins/router/rou3-compiled.d.ts.map +1 -0
- package/dist/plugins/router/rou3-compiled.js +14 -0
- package/dist/plugins/router/rou3-router.d.ts +3 -0
- package/dist/plugins/router/rou3-router.d.ts.map +1 -0
- package/dist/plugins/router/rou3-router.js +16 -0
- package/dist/plugins/router/router-plugin.d.ts +6 -16
- package/dist/plugins/router/router-plugin.d.ts.map +1 -1
- package/dist/plugins/router/router-plugin.js +43 -22
- package/dist/proxy.d.ts +1 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +1 -0
- package/dist/request-context.d.ts +0 -2
- package/dist/request-context.d.ts.map +1 -1
- package/dist/request-context.js +0 -4
- package/dist/router.d.ts +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +4 -4
- package/dist/server-router.d.ts +3 -3
- package/dist/server-router.d.ts.map +1 -1
- package/dist/server-router.js +2 -2
- package/dist/vite.d.ts +8 -3
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +72 -33
- package/package.json +6 -4
- package/dist/components/error-boundary-client.d.ts +0 -49
- package/dist/components/error-boundary-client.d.ts.map +0 -1
- package/dist/components/error-boundary-client.js +0 -71
- package/dist/components/error-boundary.browser.d.ts +0 -8
- package/dist/components/error-boundary.browser.d.ts.map +0 -1
- package/dist/components/error-boundary.browser.js +0 -6
- package/dist/components/error.d.ts +0 -2
- package/dist/components/error.d.ts.map +0 -1
- package/dist/components/error.js +0 -5
- package/dist/components/not-found-activator.d.ts +0 -11
- package/dist/components/not-found-activator.d.ts.map +0 -1
- package/dist/components/not-found-activator.js +0 -26
- package/dist/components/not-found-boundary-client.d.ts +0 -3
- package/dist/components/not-found-boundary-client.d.ts.map +0 -1
- package/dist/components/not-found-boundary-client.js +0 -20
- package/dist/components/not-found-boundary.browser.d.ts +0 -8
- package/dist/components/not-found-boundary.browser.d.ts.map +0 -1
- package/dist/components/not-found-boundary.browser.js +0 -4
- package/dist/components/not-found-boundary.d.ts +0 -8
- package/dist/components/not-found-boundary.d.ts.map +0 -1
- package/dist/components/not-found-boundary.js +0 -9
- package/dist/components/not-found-thrower.d.ts +0 -2
- package/dist/components/not-found-thrower.d.ts.map +0 -1
- package/dist/components/not-found-thrower.js +0 -7
- package/dist/components/not-found.d.ts +0 -2
- package/dist/components/not-found.d.ts.map +0 -1
- package/dist/components/not-found.js +0 -5
- package/dist/components/status-page.d.ts +0 -6
- package/dist/components/status-page.d.ts.map +0 -1
- package/dist/components/status-page.js +0 -61
- package/dist/handler.d.ts +0 -12
- package/dist/handler.d.ts.map +0 -1
- package/dist/handler.js +0 -21
- package/dist/handlers/rsc/constants.d.ts +0 -3
- package/dist/handlers/rsc/constants.d.ts.map +0 -1
- package/dist/handlers/rsc/constants.js +0 -2
- package/dist/handlers/rsc/create-response.d.ts +0 -3
- package/dist/handlers/rsc/create-response.d.ts.map +0 -1
- package/dist/handlers/rsc/create-response.js +0 -378
- package/dist/handlers/rsc/render-with-retries.d.ts +0 -4
- package/dist/handlers/rsc/render-with-retries.d.ts.map +0 -1
- package/dist/handlers/rsc/render-with-retries.js +0 -154
- package/dist/handlers/rsc/retry-loop.d.ts +0 -3
- package/dist/handlers/rsc/retry-loop.d.ts.map +0 -1
- package/dist/handlers/rsc/retry-loop.js +0 -145
- package/dist/handlers/rsc/stream-rsc-payload.d.ts +0 -9
- package/dist/handlers/rsc/stream-rsc-payload.d.ts.map +0 -1
- package/dist/handlers/rsc/stream-rsc-payload.js +0 -20
- package/dist/handlers/rsc/types.d.ts +0 -26
- package/dist/handlers/rsc/types.d.ts.map +0 -1
- package/dist/handlers/rsc/types.js +0 -7
- package/dist/handlers/rsc.d.ts +0 -20
- package/dist/handlers/rsc.d.ts.map +0 -1
- package/dist/handlers/rsc.js +0 -872
- package/dist/integration.d.ts +0 -14
- package/dist/integration.d.ts.map +0 -1
- package/dist/integration.js +0 -9
- package/dist/lib/dev-module-load-race.d.ts +0 -8
- package/dist/lib/dev-module-load-race.d.ts.map +0 -1
- package/dist/lib/dev-module-load-race.js +0 -31
- package/dist/lib/dev-page-exports.d.ts +0 -5
- package/dist/lib/dev-page-exports.d.ts.map +0 -1
- package/dist/lib/dev-page-exports.js +0 -14
- package/dist/lib/environment-names.d.ts +0 -11
- package/dist/lib/environment-names.d.ts.map +0 -1
- package/dist/lib/environment-names.js +0 -10
- package/dist/lib/initial-hash-scroll.d.ts.map +0 -1
- package/dist/lib/not-found.d.ts +0 -5
- package/dist/lib/not-found.d.ts.map +0 -1
- package/dist/lib/not-found.js +0 -7
- package/dist/lib/page-exports.d.ts +0 -9
- package/dist/lib/page-exports.d.ts.map +0 -1
- package/dist/lib/page-exports.js +0 -47
- package/dist/lib/pattern-segments.d.ts +0 -8
- package/dist/lib/pattern-segments.d.ts.map +0 -1
- package/dist/lib/pattern-segments.js +0 -22
- package/dist/lib/ppr/cache-status.d.ts +0 -3
- package/dist/lib/ppr/cache-status.d.ts.map +0 -1
- package/dist/lib/ppr/cache-status.js +0 -5
- package/dist/lib/server-router-contract.d.ts +0 -42
- package/dist/lib/server-router-contract.d.ts.map +0 -1
- package/dist/lib/server-router-contract.js +0 -6
- package/dist/lib/server-router-loader.d.ts +0 -10
- package/dist/lib/server-router-loader.d.ts.map +0 -1
- package/dist/lib/server-router-loader.js +0 -180
- package/dist/lib/server-router.d.ts +0 -8
- package/dist/lib/server-router.d.ts.map +0 -1
- package/dist/lib/server-router.js +0 -173
- package/dist/plugins/ppr-upgrade.d.ts +0 -36
- package/dist/plugins/ppr-upgrade.d.ts.map +0 -1
- package/dist/plugins/ppr-upgrade.js +0 -32
- package/dist/plugins/server-router-fallback.d.ts +0 -15
- package/dist/plugins/server-router-fallback.d.ts.map +0 -1
- package/dist/plugins/server-router-fallback.js +0 -30
- package/dist/server.d.ts +0 -10
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -57
package/dist/handlers/rsc.js
DELETED
|
@@ -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
|
-
}
|