You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
860 lines
42 KiB
JavaScript
860 lines
42 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
Object.defineProperty(exports, "renderToHTMLOrFlight", {
|
|
enumerable: true,
|
|
get: function() {
|
|
return renderToHTMLOrFlight;
|
|
}
|
|
});
|
|
const _jsxruntime = require("react/jsx-runtime");
|
|
const _react = /*#__PURE__*/ _interop_require_default(require("react"));
|
|
const _createservercomponentsrenderer = require("./create-server-components-renderer");
|
|
const _renderresult = /*#__PURE__*/ _interop_require_default(require("../render-result"));
|
|
const _nodewebstreamshelper = require("../stream-utils/node-web-streams-helper");
|
|
const _matchsegments = require("../../client/components/match-segments");
|
|
const _internalutils = require("../internal-utils");
|
|
const _approuterheaders = require("../../client/components/app-router-headers");
|
|
const _metadata = require("../../lib/metadata/metadata");
|
|
const _requestasyncstoragewrapper = require("../async-storage/request-async-storage-wrapper");
|
|
const _staticgenerationasyncstoragewrapper = require("../async-storage/static-generation-async-storage-wrapper");
|
|
const _notfound = require("../../client/components/not-found");
|
|
const _redirect = require("../../client/components/redirect");
|
|
const _patchfetch = require("../lib/patch-fetch");
|
|
const _constants = require("../lib/trace/constants");
|
|
const _tracer = require("../lib/trace/tracer");
|
|
const _flightrenderresult = require("./flight-render-result");
|
|
const _createerrorhandler = require("./create-error-handler");
|
|
const _getshortdynamicparamtype = require("./get-short-dynamic-param-type");
|
|
const _getsegmentparam = require("./get-segment-param");
|
|
const _getscriptnoncefromheader = require("./get-script-nonce-from-header");
|
|
const _parseandvalidateflightrouterstate = require("./parse-and-validate-flight-router-state");
|
|
const _validateurl = require("./validate-url");
|
|
const _createflightrouterstatefromloadertree = require("./create-flight-router-state-from-loader-tree");
|
|
const _actionhandler = require("./action-handler");
|
|
const _bailouttocsr = require("../../shared/lib/lazy-dynamic/bailout-to-csr");
|
|
const _log = require("../../build/output/log");
|
|
const _requestcookies = require("../web/spec-extension/adapters/request-cookies");
|
|
const _serverinsertedhtml = require("./server-inserted-html");
|
|
const _requiredscripts = require("./required-scripts");
|
|
const _addpathprefix = require("../../shared/lib/router/utils/add-path-prefix");
|
|
const _makegetserverinsertedhtml = require("./make-get-server-inserted-html");
|
|
const _walktreewithflightrouterstate = require("./walk-tree-with-flight-router-state");
|
|
const _createcomponenttree = require("./create-component-tree");
|
|
const _getassetquerystring = require("./get-asset-query-string");
|
|
const _actionencryptionutils = require("./action-encryption-utils");
|
|
const _staticrenderer = require("./static/static-renderer");
|
|
const _ismissingpostponeerror = require("./is-missing-postpone-error");
|
|
const _detachedpromise = require("../../lib/detached-promise");
|
|
const _hooksservercontext = require("../../client/components/hooks-server-context");
|
|
const _useflightresponse = require("./use-flight-response");
|
|
const _staticgenerationbailout = require("../../client/components/static-generation-bailout");
|
|
const _actionutils = require("./action-utils");
|
|
const _formatservererror = require("../../lib/format-server-error");
|
|
function _interop_require_default(obj) {
|
|
return obj && obj.__esModule ? obj : {
|
|
default: obj
|
|
};
|
|
}
|
|
function createNotFoundLoaderTree(loaderTree) {
|
|
// Align the segment with parallel-route-default in next-app-loader
|
|
return [
|
|
"",
|
|
{},
|
|
loaderTree[2]
|
|
];
|
|
}
|
|
/* This method is important for intercepted routes to function:
|
|
* when a route is intercepted, e.g. /blog/[slug], it will be rendered
|
|
* with the layout of the previous page, e.g. /profile/[id]. The problem is
|
|
* that the loader tree needs to know the dynamic param in order to render (id and slug in the example).
|
|
* Normally they are read from the path but since we are intercepting the route, the path would not contain id,
|
|
* so we need to read it from the router state.
|
|
*/ function findDynamicParamFromRouterState(providedFlightRouterState, segment) {
|
|
if (!providedFlightRouterState) {
|
|
return null;
|
|
}
|
|
const treeSegment = providedFlightRouterState[0];
|
|
if ((0, _matchsegments.canSegmentBeOverridden)(segment, treeSegment)) {
|
|
if (!Array.isArray(treeSegment) || Array.isArray(segment)) {
|
|
return null;
|
|
}
|
|
return {
|
|
param: treeSegment[0],
|
|
value: treeSegment[1],
|
|
treeSegment: treeSegment,
|
|
type: treeSegment[2]
|
|
};
|
|
}
|
|
for (const parallelRouterState of Object.values(providedFlightRouterState[1])){
|
|
const maybeDynamicParam = findDynamicParamFromRouterState(parallelRouterState, segment);
|
|
if (maybeDynamicParam) {
|
|
return maybeDynamicParam;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* Returns a function that parses the dynamic segment and return the associated value.
|
|
*/ function makeGetDynamicParamFromSegment(params, providedFlightRouterState) {
|
|
return function getDynamicParamFromSegment(// [slug] / [[slug]] / [...slug]
|
|
segment) {
|
|
const segmentParam = (0, _getsegmentparam.getSegmentParam)(segment);
|
|
if (!segmentParam) {
|
|
return null;
|
|
}
|
|
const key = segmentParam.param;
|
|
let value = params[key];
|
|
// this is a special marker that will be present for interception routes
|
|
if (value === "__NEXT_EMPTY_PARAM__") {
|
|
value = undefined;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
value = value.map((i)=>encodeURIComponent(i));
|
|
} else if (typeof value === "string") {
|
|
value = encodeURIComponent(value);
|
|
}
|
|
if (!value) {
|
|
// Handle case where optional catchall does not have a value, e.g. `/dashboard/[...slug]` when requesting `/dashboard`
|
|
if (segmentParam.type === "optional-catchall") {
|
|
const type = _getshortdynamicparamtype.dynamicParamTypes[segmentParam.type];
|
|
return {
|
|
param: key,
|
|
value: null,
|
|
type: type,
|
|
// This value always has to be a string.
|
|
treeSegment: [
|
|
key,
|
|
"",
|
|
type
|
|
]
|
|
};
|
|
}
|
|
return findDynamicParamFromRouterState(providedFlightRouterState, segment);
|
|
}
|
|
const type = (0, _getshortdynamicparamtype.getShortDynamicParamType)(segmentParam.type);
|
|
return {
|
|
param: key,
|
|
// The value that is passed to user code.
|
|
value: value,
|
|
// The value that is rendered in the router tree.
|
|
treeSegment: [
|
|
key,
|
|
Array.isArray(value) ? value.join("/") : value,
|
|
type
|
|
],
|
|
type: type
|
|
};
|
|
};
|
|
}
|
|
// Handle Flight render request. This is only used when client-side navigating. E.g. when you `router.push('/dashboard')` or `router.reload()`.
|
|
async function generateFlight(ctx, options) {
|
|
// Flight data that is going to be passed to the browser.
|
|
// Currently a single item array but in the future multiple patches might be combined in a single request.
|
|
let flightData = null;
|
|
const { componentMod: { tree: loaderTree, renderToReadableStream }, getDynamicParamFromSegment, appUsingSizeAdjustment, staticGenerationStore: { urlPathname }, providedSearchParams, requestId, providedFlightRouterState } = ctx;
|
|
if (!(options == null ? void 0 : options.skipFlight)) {
|
|
const [MetadataTree, MetadataOutlet] = (0, _metadata.createMetadataComponents)({
|
|
tree: loaderTree,
|
|
pathname: urlPathname,
|
|
searchParams: providedSearchParams,
|
|
trailingSlash: ctx.renderOpts.trailingSlash,
|
|
getDynamicParamFromSegment,
|
|
appUsingSizeAdjustment
|
|
});
|
|
flightData = (await (0, _walktreewithflightrouterstate.walkTreeWithFlightRouterState)({
|
|
ctx,
|
|
createSegmentPath: (child)=>child,
|
|
loaderTreeToFilter: loaderTree,
|
|
parentParams: {},
|
|
flightRouterState: providedFlightRouterState,
|
|
isFirst: true,
|
|
// For flight, render metadata inside leaf page
|
|
rscPayloadHead: // Adding requestId as react key to make metadata remount for each render
|
|
/*#__PURE__*/ (0, _jsxruntime.jsx)(MetadataTree, {}, requestId),
|
|
injectedCSS: new Set(),
|
|
injectedJS: new Set(),
|
|
injectedFontPreloadTags: new Set(),
|
|
rootLayoutIncluded: false,
|
|
asNotFound: ctx.isNotFoundPath || (options == null ? void 0 : options.asNotFound),
|
|
metadataOutlet: /*#__PURE__*/ (0, _jsxruntime.jsx)(MetadataOutlet, {})
|
|
})).map((path)=>path.slice(1)) // remove the '' (root) segment
|
|
;
|
|
}
|
|
const buildIdFlightDataPair = [
|
|
ctx.renderOpts.buildId,
|
|
flightData
|
|
];
|
|
// For app dir, use the bundled version of Flight server renderer (renderToReadableStream)
|
|
// which contains the subset React.
|
|
const flightReadableStream = renderToReadableStream(options ? [
|
|
options.actionResult,
|
|
buildIdFlightDataPair
|
|
] : buildIdFlightDataPair, ctx.clientReferenceManifest.clientModules, {
|
|
onError: ctx.flightDataRendererErrorHandler
|
|
});
|
|
return new _flightrenderresult.FlightRenderResult(flightReadableStream);
|
|
}
|
|
/**
|
|
* Creates a resolver that eagerly generates a flight payload that is then
|
|
* resolved when the resolver is called.
|
|
*/ function createFlightDataResolver(ctx) {
|
|
// Generate the flight data and as soon as it can, convert it into a string.
|
|
const promise = generateFlight(ctx).then(async (result)=>({
|
|
flightData: await result.toUnchunkedString(true)
|
|
}))// Otherwise if it errored, return the error.
|
|
.catch((err)=>({
|
|
err
|
|
}));
|
|
return async ()=>{
|
|
// Resolve the promise to get the flight data or error.
|
|
const result = await promise;
|
|
// If the flight data failed to render due to an error, re-throw the error
|
|
// here.
|
|
if ("err" in result) {
|
|
throw result.err;
|
|
}
|
|
// Otherwise, return the flight data.
|
|
return result.flightData;
|
|
};
|
|
}
|
|
// This is the root component that runs in the RSC context
|
|
async function ReactServerApp({ tree, ctx, preinitScripts, asNotFound }) {
|
|
preinitScripts();
|
|
// Create full component tree from root to leaf.
|
|
const injectedCSS = new Set();
|
|
const injectedJS = new Set();
|
|
const injectedFontPreloadTags = new Set();
|
|
const missingSlots = new Set();
|
|
const { getDynamicParamFromSegment, query, providedSearchParams, appUsingSizeAdjustment, componentMod: { AppRouter, GlobalError }, staticGenerationStore: { urlPathname } } = ctx;
|
|
const initialTree = (0, _createflightrouterstatefromloadertree.createFlightRouterStateFromLoaderTree)(tree, getDynamicParamFromSegment, query);
|
|
const [MetadataTree, MetadataOutlet] = (0, _metadata.createMetadataComponents)({
|
|
tree,
|
|
errorType: asNotFound ? "not-found" : undefined,
|
|
pathname: urlPathname,
|
|
searchParams: providedSearchParams,
|
|
trailingSlash: ctx.renderOpts.trailingSlash,
|
|
getDynamicParamFromSegment: getDynamicParamFromSegment,
|
|
appUsingSizeAdjustment: appUsingSizeAdjustment
|
|
});
|
|
const { seedData, styles } = await (0, _createcomponenttree.createComponentTree)({
|
|
ctx,
|
|
createSegmentPath: (child)=>child,
|
|
loaderTree: tree,
|
|
parentParams: {},
|
|
firstItem: true,
|
|
injectedCSS,
|
|
injectedJS,
|
|
injectedFontPreloadTags,
|
|
rootLayoutIncluded: false,
|
|
asNotFound: asNotFound,
|
|
metadataOutlet: /*#__PURE__*/ (0, _jsxruntime.jsx)(MetadataOutlet, {}),
|
|
missingSlots
|
|
});
|
|
return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_jsxruntime.Fragment, {
|
|
children: [
|
|
styles,
|
|
/*#__PURE__*/ (0, _jsxruntime.jsx)(AppRouter, {
|
|
buildId: ctx.renderOpts.buildId,
|
|
assetPrefix: ctx.assetPrefix,
|
|
initialCanonicalUrl: urlPathname,
|
|
// This is the router state tree.
|
|
initialTree: initialTree,
|
|
// This is the tree of React nodes that are seeded into the cache
|
|
initialSeedData: seedData,
|
|
initialHead: /*#__PURE__*/ (0, _jsxruntime.jsxs)(_jsxruntime.Fragment, {
|
|
children: [
|
|
ctx.res.statusCode > 400 && /*#__PURE__*/ (0, _jsxruntime.jsx)("meta", {
|
|
name: "robots",
|
|
content: "noindex"
|
|
}),
|
|
/*#__PURE__*/ (0, _jsxruntime.jsx)(MetadataTree, {}, ctx.requestId)
|
|
]
|
|
}),
|
|
globalErrorComponent: GlobalError,
|
|
// This is used to provide debug information (when in development mode)
|
|
// about which slots were not filled by page components while creating the component tree.
|
|
missingSlots: missingSlots
|
|
})
|
|
]
|
|
});
|
|
}
|
|
// This is the root component that runs in the RSC context
|
|
async function ReactServerError({ tree, ctx, preinitScripts, errorType }) {
|
|
const { getDynamicParamFromSegment, query, providedSearchParams, appUsingSizeAdjustment, componentMod: { AppRouter, GlobalError }, staticGenerationStore: { urlPathname }, requestId, res } = ctx;
|
|
preinitScripts();
|
|
const [MetadataTree] = (0, _metadata.createMetadataComponents)({
|
|
tree,
|
|
pathname: urlPathname,
|
|
trailingSlash: ctx.renderOpts.trailingSlash,
|
|
errorType,
|
|
searchParams: providedSearchParams,
|
|
getDynamicParamFromSegment,
|
|
appUsingSizeAdjustment
|
|
});
|
|
const head = /*#__PURE__*/ (0, _jsxruntime.jsxs)(_jsxruntime.Fragment, {
|
|
children: [
|
|
/*#__PURE__*/ (0, _jsxruntime.jsx)(MetadataTree, {}, requestId),
|
|
res.statusCode >= 400 && /*#__PURE__*/ (0, _jsxruntime.jsx)("meta", {
|
|
name: "robots",
|
|
content: "noindex"
|
|
}),
|
|
process.env.NODE_ENV === "development" && /*#__PURE__*/ (0, _jsxruntime.jsx)("meta", {
|
|
name: "next-error",
|
|
content: "not-found"
|
|
})
|
|
]
|
|
});
|
|
const initialTree = (0, _createflightrouterstatefromloadertree.createFlightRouterStateFromLoaderTree)(tree, getDynamicParamFromSegment, query);
|
|
// For metadata notFound error there's no global not found boundary on top
|
|
// so we create a not found page with AppRouter
|
|
const initialSeedData = [
|
|
initialTree[0],
|
|
{},
|
|
/*#__PURE__*/ (0, _jsxruntime.jsxs)("html", {
|
|
id: "__next_error__",
|
|
children: [
|
|
/*#__PURE__*/ (0, _jsxruntime.jsx)("head", {}),
|
|
/*#__PURE__*/ (0, _jsxruntime.jsx)("body", {})
|
|
]
|
|
})
|
|
];
|
|
return /*#__PURE__*/ (0, _jsxruntime.jsx)(AppRouter, {
|
|
buildId: ctx.renderOpts.buildId,
|
|
assetPrefix: ctx.assetPrefix,
|
|
initialCanonicalUrl: urlPathname,
|
|
initialTree: initialTree,
|
|
initialHead: head,
|
|
globalErrorComponent: GlobalError,
|
|
initialSeedData: initialSeedData,
|
|
missingSlots: new Set()
|
|
});
|
|
}
|
|
// This component must run in an SSR context. It will render the RSC root component
|
|
function ReactServerEntrypoint({ renderReactServer, inlinedDataTransformStream, clientReferenceManifest, formState, nonce }) {
|
|
const writable = inlinedDataTransformStream.writable;
|
|
const reactServerRequestStream = renderReactServer();
|
|
const reactServerResponse = (0, _useflightresponse.useFlightResponse)(writable, reactServerRequestStream, clientReferenceManifest, formState, nonce);
|
|
return _react.default.use(reactServerResponse);
|
|
}
|
|
async function renderToHTMLOrFlightImpl(req, res, pagePath, query, renderOpts, baseCtx) {
|
|
var _getTracer_getRootSpanAttributes;
|
|
const isNotFoundPath = pagePath === "/404";
|
|
// A unique request timestamp used by development to ensure that it's
|
|
// consistent and won't change during this request. This is important to
|
|
// avoid that resources can be deduped by React Float if the same resource is
|
|
// rendered or preloaded multiple times: `<link href="a.css?v={Date.now()}"/>`.
|
|
const requestTimestamp = Date.now();
|
|
const { buildManifest, subresourceIntegrityManifest, serverActionsManifest, ComponentMod, dev, nextFontManifest, supportsDynamicHTML, serverActions, appDirDevErrorLogger, assetPrefix = "", enableTainting } = renderOpts;
|
|
// We need to expose the bundled `require` API globally for
|
|
// react-server-dom-webpack. This is a hack until we find a better way.
|
|
if (ComponentMod.__next_app__) {
|
|
// @ts-ignore
|
|
globalThis.__next_require__ = ComponentMod.__next_app__.require;
|
|
// @ts-ignore
|
|
globalThis.__next_chunk_load__ = ComponentMod.__next_app__.loadChunk;
|
|
}
|
|
const metadata = {};
|
|
const appUsingSizeAdjustment = !!(nextFontManifest == null ? void 0 : nextFontManifest.appUsingSizeAdjust);
|
|
// TODO: fix this typescript
|
|
const clientReferenceManifest = renderOpts.clientReferenceManifest;
|
|
const serverModuleMap = (0, _actionutils.createServerModuleMap)({
|
|
serverActionsManifest,
|
|
pageName: renderOpts.page
|
|
});
|
|
(0, _actionencryptionutils.setReferenceManifestsSingleton)({
|
|
clientReferenceManifest,
|
|
serverActionsManifest,
|
|
serverModuleMap
|
|
});
|
|
const capturedErrors = [];
|
|
const allCapturedErrors = [];
|
|
const isNextExport = !!renderOpts.nextExport;
|
|
const { staticGenerationStore, requestStore } = baseCtx;
|
|
const { isStaticGeneration } = staticGenerationStore;
|
|
// when static generation fails during PPR, we log the errors separately. We intentionally
|
|
// silence the error logger in this case to avoid double logging.
|
|
const silenceStaticGenerationErrors = renderOpts.experimental.ppr && isStaticGeneration;
|
|
const serverComponentsErrorHandler = (0, _createerrorhandler.createErrorHandler)({
|
|
_source: "serverComponentsRenderer",
|
|
dev,
|
|
isNextExport,
|
|
errorLogger: appDirDevErrorLogger,
|
|
capturedErrors,
|
|
silenceLogger: silenceStaticGenerationErrors
|
|
});
|
|
const flightDataRendererErrorHandler = (0, _createerrorhandler.createErrorHandler)({
|
|
_source: "flightDataRenderer",
|
|
dev,
|
|
isNextExport,
|
|
errorLogger: appDirDevErrorLogger,
|
|
capturedErrors,
|
|
silenceLogger: silenceStaticGenerationErrors
|
|
});
|
|
const htmlRendererErrorHandler = (0, _createerrorhandler.createErrorHandler)({
|
|
_source: "htmlRenderer",
|
|
dev,
|
|
isNextExport,
|
|
errorLogger: appDirDevErrorLogger,
|
|
capturedErrors,
|
|
allCapturedErrors,
|
|
silenceLogger: silenceStaticGenerationErrors
|
|
});
|
|
/**
|
|
* This postpone handler will be used to help us discriminate between a set of cases
|
|
* 1. SSR or RSC postpone that was caught and not rethrown
|
|
* 2. SSR postpone handled by React
|
|
* 3. RSC postpone handled by React
|
|
*
|
|
* The previous technique for tracking postpones could not tell between cases 1 and 3
|
|
* however we only want to warn on the first case
|
|
*/ // eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
let serverComponentsDidPostpone = false;
|
|
const serverComponentsPostponeHandler = (_reason)=>{
|
|
serverComponentsDidPostpone = true;
|
|
};
|
|
ComponentMod.patchFetch();
|
|
/**
|
|
* Rules of Static & Dynamic HTML:
|
|
*
|
|
* 1.) We must generate static HTML unless the caller explicitly opts
|
|
* in to dynamic HTML support.
|
|
*
|
|
* 2.) If dynamic HTML support is requested, we must honor that request
|
|
* or throw an error. It is the sole responsibility of the caller to
|
|
* ensure they aren't e.g. requesting dynamic HTML for an AMP page.
|
|
*
|
|
* These rules help ensure that other existing features like request caching,
|
|
* coalescing, and ISR continue working as intended.
|
|
*/ const generateStaticHTML = supportsDynamicHTML !== true;
|
|
// Pull out the hooks/references from the component.
|
|
const { createSearchParamsBailoutProxy, tree: loaderTree, taintObjectReference } = ComponentMod;
|
|
if (enableTainting) {
|
|
taintObjectReference("Do not pass process.env to client components since it will leak sensitive data", process.env);
|
|
}
|
|
const { urlPathname } = staticGenerationStore;
|
|
staticGenerationStore.fetchMetrics = [];
|
|
metadata.fetchMetrics = staticGenerationStore.fetchMetrics;
|
|
// don't modify original query object
|
|
query = {
|
|
...query
|
|
};
|
|
(0, _internalutils.stripInternalQueries)(query);
|
|
const isRSCRequest = req.headers[_approuterheaders.RSC_HEADER.toLowerCase()] !== undefined;
|
|
const isPrefetchRSCRequest = isRSCRequest && req.headers[_approuterheaders.NEXT_ROUTER_PREFETCH_HEADER.toLowerCase()] !== undefined;
|
|
/**
|
|
* Router state provided from the client-side router. Used to handle rendering from the common layout down.
|
|
*/ let providedFlightRouterState = isRSCRequest && (!isPrefetchRSCRequest || !renderOpts.experimental.ppr) ? (0, _parseandvalidateflightrouterstate.parseAndValidateFlightRouterState)(req.headers[_approuterheaders.NEXT_ROUTER_STATE_TREE.toLowerCase()]) : undefined;
|
|
/**
|
|
* The metadata items array created in next-app-loader with all relevant information
|
|
* that we need to resolve the final metadata.
|
|
*/ let requestId;
|
|
if (process.env.NEXT_RUNTIME === "edge") {
|
|
requestId = crypto.randomUUID();
|
|
} else {
|
|
requestId = require("next/dist/compiled/nanoid").nanoid();
|
|
}
|
|
// During static generation we need to call the static generation bailout when reading searchParams
|
|
const providedSearchParams = isStaticGeneration ? createSearchParamsBailoutProxy() : query;
|
|
const searchParamsProps = {
|
|
searchParams: providedSearchParams
|
|
};
|
|
/**
|
|
* Dynamic parameters. E.g. when you visit `/dashboard/vercel` which is rendered by `/dashboard/[slug]` the value will be {"slug": "vercel"}.
|
|
*/ const params = renderOpts.params ?? {};
|
|
const getDynamicParamFromSegment = makeGetDynamicParamFromSegment(params, providedFlightRouterState);
|
|
const ctx = {
|
|
...baseCtx,
|
|
getDynamicParamFromSegment,
|
|
query,
|
|
isPrefetch: isPrefetchRSCRequest,
|
|
providedSearchParams,
|
|
requestTimestamp,
|
|
searchParamsProps,
|
|
appUsingSizeAdjustment,
|
|
providedFlightRouterState,
|
|
requestId,
|
|
defaultRevalidate: false,
|
|
pagePath,
|
|
clientReferenceManifest,
|
|
assetPrefix,
|
|
flightDataRendererErrorHandler,
|
|
serverComponentsErrorHandler,
|
|
isNotFoundPath,
|
|
res
|
|
};
|
|
if (isRSCRequest && !isStaticGeneration) {
|
|
return generateFlight(ctx);
|
|
}
|
|
const hasPostponed = typeof renderOpts.postponed === "string";
|
|
// Create the resolver that can get the flight payload when it's ready or
|
|
// throw the error if it occurred. If we are not generating static HTML, we
|
|
// don't need to generate the flight payload because it's a dynamic request
|
|
// which means we're either getting the flight payload only or just the
|
|
// regular HTML.
|
|
const flightDataResolver = isStaticGeneration ? createFlightDataResolver(ctx) : null;
|
|
// Get the nonce from the incoming request if it has one.
|
|
const csp = req.headers["content-security-policy"] || req.headers["content-security-policy-report-only"];
|
|
let nonce;
|
|
if (csp && typeof csp === "string") {
|
|
nonce = (0, _getscriptnoncefromheader.getScriptNonceFromHeader)(csp);
|
|
}
|
|
const validateRootLayout = dev ? {
|
|
assetPrefix: renderOpts.assetPrefix,
|
|
getTree: ()=>(0, _createflightrouterstatefromloadertree.createFlightRouterStateFromLoaderTree)(loaderTree, getDynamicParamFromSegment, query)
|
|
} : undefined;
|
|
const { HeadManagerContext } = require("../../shared/lib/head-manager-context.shared-runtime");
|
|
// On each render, create a new `ServerInsertedHTML` context to capture
|
|
// injected nodes from user code (`useServerInsertedHTML`).
|
|
const { ServerInsertedHTMLProvider, renderServerInsertedHTML } = (0, _serverinsertedhtml.createServerInsertedHTML)();
|
|
(_getTracer_getRootSpanAttributes = (0, _tracer.getTracer)().getRootSpanAttributes()) == null ? void 0 : _getTracer_getRootSpanAttributes.set("next.route", pagePath);
|
|
// Create a promise that will help us signal when the headers have been
|
|
// written to the metadata for static generation as they aren't written to the
|
|
// response directly.
|
|
const onHeadersFinished = new _detachedpromise.DetachedPromise();
|
|
const renderToStream = (0, _tracer.getTracer)().wrap(_constants.AppRenderSpan.getBodyResult, {
|
|
spanName: `render route (app) ${pagePath}`,
|
|
attributes: {
|
|
"next.route": pagePath
|
|
}
|
|
}, async ({ asNotFound, tree, formState })=>{
|
|
const polyfills = buildManifest.polyfillFiles.filter((polyfill)=>polyfill.endsWith(".js") && !polyfill.endsWith(".module.js")).map((polyfill)=>({
|
|
src: `${assetPrefix}/_next/${polyfill}${(0, _getassetquerystring.getAssetQueryString)(ctx, false)}`,
|
|
integrity: subresourceIntegrityManifest == null ? void 0 : subresourceIntegrityManifest[polyfill],
|
|
crossOrigin: renderOpts.crossOrigin,
|
|
noModule: true,
|
|
nonce
|
|
}));
|
|
const [preinitScripts, bootstrapScript] = (0, _requiredscripts.getRequiredScripts)(buildManifest, assetPrefix, renderOpts.crossOrigin, subresourceIntegrityManifest, (0, _getassetquerystring.getAssetQueryString)(ctx, true), nonce);
|
|
// This will when called actually render the RSC layer. During an SSR pass it will
|
|
// typically get passed to a Entrypoint component which calls initiates it during the
|
|
// the SSR render however there are some cases where this
|
|
const serverComponentsRenderer = (0, _createservercomponentsrenderer.createReactServerRenderer)(/*#__PURE__*/ (0, _jsxruntime.jsx)(ReactServerApp, {
|
|
tree: tree,
|
|
ctx: ctx,
|
|
preinitScripts: preinitScripts,
|
|
asNotFound: asNotFound
|
|
}), ComponentMod, clientReferenceManifest, serverComponentsErrorHandler, serverComponentsPostponeHandler);
|
|
const renderInlinedDataTransformStream = new TransformStream();
|
|
const children = /*#__PURE__*/ (0, _jsxruntime.jsx)(HeadManagerContext.Provider, {
|
|
value: {
|
|
appDir: true,
|
|
nonce
|
|
},
|
|
children: /*#__PURE__*/ (0, _jsxruntime.jsx)(ServerInsertedHTMLProvider, {
|
|
children: /*#__PURE__*/ (0, _jsxruntime.jsx)(ReactServerEntrypoint, {
|
|
renderReactServer: serverComponentsRenderer,
|
|
inlinedDataTransformStream: renderInlinedDataTransformStream,
|
|
clientReferenceManifest: clientReferenceManifest,
|
|
formState: formState,
|
|
nonce: nonce
|
|
})
|
|
})
|
|
});
|
|
const getServerInsertedHTML = (0, _makegetserverinsertedhtml.makeGetServerInsertedHTML)({
|
|
polyfills,
|
|
renderServerInsertedHTML,
|
|
hasPostponed
|
|
});
|
|
const renderer = (0, _staticrenderer.createStaticRenderer)({
|
|
ppr: renderOpts.experimental.ppr,
|
|
isStaticGeneration,
|
|
// If provided, the postpone state should be parsed as JSON so it can be
|
|
// provided to React.
|
|
postponed: renderOpts.postponed ? JSON.parse(renderOpts.postponed) : null,
|
|
streamOptions: {
|
|
onError: htmlRendererErrorHandler,
|
|
onHeaders: (headers)=>{
|
|
// If this is during static generation, we shouldn't write to the
|
|
// headers object directly, instead we should add to the render
|
|
// result.
|
|
if (isStaticGeneration) {
|
|
headers.forEach((value, key)=>{
|
|
metadata.headers ??= {};
|
|
metadata.headers[key] = value;
|
|
});
|
|
// Resolve the promise to continue the stream.
|
|
onHeadersFinished.resolve();
|
|
} else {
|
|
headers.forEach((value, key)=>{
|
|
res.appendHeader(key, value);
|
|
});
|
|
}
|
|
},
|
|
maxHeadersLength: 600,
|
|
nonce,
|
|
bootstrapScripts: [
|
|
bootstrapScript
|
|
],
|
|
formState
|
|
}
|
|
});
|
|
try {
|
|
let { stream, postponed } = await renderer.render(children);
|
|
// If the stream was postponed, we need to add the result to the
|
|
// metadata so that it can be resumed later.
|
|
if (postponed) {
|
|
// If our render did not produce a postponed state but we did postpone
|
|
// during the RSC render we need to still treat this as a postpone
|
|
metadata.postponed = JSON.stringify(postponed);
|
|
// We don't need to "continue" this stream now as it's continued when
|
|
// we resume the stream.
|
|
return {
|
|
stream
|
|
};
|
|
}
|
|
const options = {
|
|
inlinedDataStream: renderInlinedDataTransformStream.readable,
|
|
isStaticGeneration: isStaticGeneration || generateStaticHTML,
|
|
getServerInsertedHTML: ()=>getServerInsertedHTML(allCapturedErrors),
|
|
serverInsertedHTMLToHead: !renderOpts.postponed,
|
|
// If this render generated a postponed state or this is a resume
|
|
// render, we don't want to validate the root layout as it's already
|
|
// partially rendered.
|
|
validateRootLayout: !postponed && !renderOpts.postponed ? validateRootLayout : undefined,
|
|
// App Render doesn't need to inject any additional suffixes.
|
|
suffix: undefined
|
|
};
|
|
if (renderOpts.postponed) {
|
|
stream = await (0, _nodewebstreamshelper.continuePostponedFizzStream)(stream, options);
|
|
} else {
|
|
stream = await (0, _nodewebstreamshelper.continueFizzStream)(stream, options);
|
|
}
|
|
return {
|
|
stream
|
|
};
|
|
} catch (err) {
|
|
if ((0, _staticgenerationbailout.isStaticGenBailoutError)(err) || typeof err === "object" && err !== null && "message" in err && typeof err.message === "string" && err.message.includes("https://nextjs.org/docs/advanced-features/static-html-export")) {
|
|
// Ensure that "next dev" prints the red error overlay
|
|
throw err;
|
|
}
|
|
// If this is a static generation error, we need to throw it so that it
|
|
// can be handled by the caller if we're in static generation mode.
|
|
if (isStaticGeneration && (0, _hooksservercontext.isDynamicServerError)(err)) {
|
|
throw err;
|
|
}
|
|
// If a bailout made it to this point, it means it wasn't wrapped inside
|
|
// a suspense boundary.
|
|
const shouldBailoutToCSR = (0, _bailouttocsr.isBailoutToCSRError)(err);
|
|
if (shouldBailoutToCSR) {
|
|
const stack = (0, _formatservererror.getStackWithoutErrorMessage)(err);
|
|
if (renderOpts.experimental.missingSuspenseWithCSRBailout) {
|
|
(0, _log.error)(`${err.reason} should be wrapped in a suspense boundary at page "${pagePath}". Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout\n${stack}`);
|
|
throw err;
|
|
}
|
|
(0, _log.warn)(`Entire page "${pagePath}" deopted into client-side rendering due to "${err.reason}". Read more: https://nextjs.org/docs/messages/deopted-into-client-rendering\n${stack}`);
|
|
}
|
|
if ((0, _notfound.isNotFoundError)(err)) {
|
|
res.statusCode = 404;
|
|
}
|
|
let hasRedirectError = false;
|
|
if ((0, _redirect.isRedirectError)(err)) {
|
|
hasRedirectError = true;
|
|
res.statusCode = (0, _redirect.getRedirectStatusCodeFromError)(err);
|
|
if (err.mutableCookies) {
|
|
const headers = new Headers();
|
|
// If there were mutable cookies set, we need to set them on the
|
|
// response.
|
|
if ((0, _requestcookies.appendMutableCookies)(headers, err.mutableCookies)) {
|
|
res.setHeader("set-cookie", Array.from(headers.values()));
|
|
}
|
|
}
|
|
const redirectUrl = (0, _addpathprefix.addPathPrefix)((0, _redirect.getURLFromRedirectError)(err), renderOpts.basePath);
|
|
res.setHeader("Location", redirectUrl);
|
|
}
|
|
const is404 = res.statusCode === 404;
|
|
if (!is404 && !hasRedirectError && !shouldBailoutToCSR) {
|
|
res.statusCode = 500;
|
|
}
|
|
const errorType = is404 ? "not-found" : hasRedirectError ? "redirect" : undefined;
|
|
const [errorPreinitScripts, errorBootstrapScript] = (0, _requiredscripts.getRequiredScripts)(buildManifest, assetPrefix, renderOpts.crossOrigin, subresourceIntegrityManifest, (0, _getassetquerystring.getAssetQueryString)(ctx, false), nonce);
|
|
const errorServerComponentsRenderer = (0, _createservercomponentsrenderer.createReactServerRenderer)(/*#__PURE__*/ (0, _jsxruntime.jsx)(ReactServerError, {
|
|
tree: tree,
|
|
ctx: ctx,
|
|
preinitScripts: errorPreinitScripts,
|
|
errorType: errorType
|
|
}), ComponentMod, clientReferenceManifest, serverComponentsErrorHandler, serverComponentsPostponeHandler);
|
|
// Preserve the existing RSC inline chunks from the page rendering.
|
|
// To avoid the same stream being operated twice, clone the origin stream for error rendering.
|
|
const errorInlinedDataTransformStream = (0, _nodewebstreamshelper.cloneTransformStream)(renderInlinedDataTransformStream);
|
|
try {
|
|
const fizzStream = await (0, _nodewebstreamshelper.renderToInitialFizzStream)({
|
|
ReactDOMServer: require("react-dom/server.edge"),
|
|
element: /*#__PURE__*/ (0, _jsxruntime.jsx)(ReactServerEntrypoint, {
|
|
renderReactServer: errorServerComponentsRenderer,
|
|
inlinedDataTransformStream: errorInlinedDataTransformStream,
|
|
clientReferenceManifest: clientReferenceManifest,
|
|
formState: formState,
|
|
nonce: nonce
|
|
}),
|
|
streamOptions: {
|
|
nonce,
|
|
// Include hydration scripts in the HTML
|
|
bootstrapScripts: [
|
|
errorBootstrapScript
|
|
],
|
|
formState
|
|
}
|
|
});
|
|
return {
|
|
// Returning the error that was thrown so it can be used to handle
|
|
// the response in the caller.
|
|
err,
|
|
stream: await (0, _nodewebstreamshelper.continueFizzStream)(fizzStream, {
|
|
inlinedDataStream: errorInlinedDataTransformStream.readable,
|
|
isStaticGeneration,
|
|
getServerInsertedHTML: ()=>getServerInsertedHTML([]),
|
|
serverInsertedHTMLToHead: true,
|
|
validateRootLayout,
|
|
suffix: undefined
|
|
})
|
|
};
|
|
} catch (finalErr) {
|
|
if (process.env.NODE_ENV === "development" && (0, _notfound.isNotFoundError)(finalErr)) {
|
|
const bailOnNotFound = require("../../client/components/dev-root-not-found-boundary").bailOnNotFound;
|
|
bailOnNotFound();
|
|
}
|
|
throw finalErr;
|
|
}
|
|
}
|
|
});
|
|
// For action requests, we handle them differently with a special render result.
|
|
const actionRequestResult = await (0, _actionhandler.handleAction)({
|
|
req,
|
|
res,
|
|
ComponentMod,
|
|
serverModuleMap,
|
|
generateFlight,
|
|
staticGenerationStore,
|
|
requestStore,
|
|
serverActions,
|
|
ctx
|
|
});
|
|
let formState = null;
|
|
if (actionRequestResult) {
|
|
if (actionRequestResult.type === "not-found") {
|
|
const notFoundLoaderTree = createNotFoundLoaderTree(loaderTree);
|
|
const response = await renderToStream({
|
|
asNotFound: true,
|
|
tree: notFoundLoaderTree,
|
|
formState
|
|
});
|
|
return new _renderresult.default(response.stream, {
|
|
metadata
|
|
});
|
|
} else if (actionRequestResult.type === "done") {
|
|
if (actionRequestResult.result) {
|
|
actionRequestResult.result.assignMetadata(metadata);
|
|
return actionRequestResult.result;
|
|
} else if (actionRequestResult.formState) {
|
|
formState = actionRequestResult.formState;
|
|
}
|
|
}
|
|
}
|
|
const options = {
|
|
metadata
|
|
};
|
|
let response = await renderToStream({
|
|
asNotFound: isNotFoundPath,
|
|
tree: loaderTree,
|
|
formState
|
|
});
|
|
// If we have pending revalidates, wait until they are all resolved.
|
|
if (staticGenerationStore.pendingRevalidates) {
|
|
options.waitUntil = Promise.all(Object.values(staticGenerationStore.pendingRevalidates));
|
|
}
|
|
(0, _patchfetch.addImplicitTags)(staticGenerationStore);
|
|
if (staticGenerationStore.tags) {
|
|
metadata.fetchTags = staticGenerationStore.tags.join(",");
|
|
}
|
|
// Create the new render result for the response.
|
|
const result = new _renderresult.default(response.stream, options);
|
|
// If we aren't performing static generation, we can return the result now.
|
|
if (!isStaticGeneration) {
|
|
return result;
|
|
}
|
|
// If this is static generation, we should read this in now rather than
|
|
// sending it back to be sent to the client.
|
|
response.stream = await result.toUnchunkedString(true);
|
|
// Timeout after 1.5 seconds for the headers to write. If it takes
|
|
// longer than this it's more likely that the stream has stalled and
|
|
// there is a React bug. The headers will then be updated in the render
|
|
// result below when the metadata is re-added to the new render result.
|
|
const onTimeout = new _detachedpromise.DetachedPromise();
|
|
const timeout = setTimeout(()=>{
|
|
onTimeout.reject(new Error("Timeout waiting for headers to be emitted, this is a bug in Next.js"));
|
|
}, 1500);
|
|
// Race against the timeout and the headers being written.
|
|
await Promise.race([
|
|
onHeadersFinished.promise,
|
|
onTimeout.promise
|
|
]);
|
|
// It got here, which means it did not reject, so clear the timeout to avoid
|
|
// it from rejecting again (which is a no-op anyways).
|
|
clearTimeout(timeout);
|
|
// If PPR is enabled and the postpone was triggered but lacks the postponed
|
|
// state information then we should error out unless the client side rendering
|
|
// bailout error was also emitted which indicates that part of the stream was
|
|
// not rendered.
|
|
if (renderOpts.experimental.ppr && staticGenerationStore.postponeWasTriggered && !metadata.postponed && (!response.err || !(0, _bailouttocsr.isBailoutToCSRError)(response.err))) {
|
|
// a call to postpone was made but was caught and not detected by Next.js. We should fail the build immediately
|
|
// as we won't be able to generate the static part
|
|
(0, _log.warn)("");
|
|
(0, _log.error)(`Prerendering ${urlPathname} needs to partially bail out because something dynamic was used. ` + `React throws a special object to indicate where we need to bail out but it was caught ` + `by a try/catch or a Promise was not awaited. These special objects should not be caught ` + `by your own try/catch. Learn more: https://nextjs.org/docs/messages/ppr-caught-error`);
|
|
if (capturedErrors.length > 0) {
|
|
(0, _log.warn)("The following error was thrown during build, and may help identify the source of the issue:");
|
|
(0, _log.error)(capturedErrors[0]);
|
|
}
|
|
throw new _ismissingpostponeerror.MissingPostponeDataError(`An unexpected error occurred while prerendering ${urlPathname}. Please check the logs above for more details.`);
|
|
}
|
|
if (!flightDataResolver) {
|
|
throw new Error("Invariant: Flight data resolver is missing when generating static HTML");
|
|
}
|
|
// If we encountered any unexpected errors during build we fail the
|
|
// prerendering phase and the build.
|
|
if (capturedErrors.length > 0) {
|
|
throw capturedErrors[0];
|
|
}
|
|
// Wait for and collect the flight payload data if we don't have it
|
|
// already
|
|
const flightData = await flightDataResolver();
|
|
if (flightData) {
|
|
metadata.flightData = flightData;
|
|
}
|
|
// If force static is specifically set to false, we should not revalidate
|
|
// the page.
|
|
if (staticGenerationStore.forceStatic === false) {
|
|
staticGenerationStore.revalidate = 0;
|
|
}
|
|
// Copy the revalidation value onto the render result metadata.
|
|
metadata.revalidate = staticGenerationStore.revalidate ?? ctx.defaultRevalidate;
|
|
// provide bailout info for debugging
|
|
if (metadata.revalidate === 0) {
|
|
metadata.staticBailoutInfo = {
|
|
description: staticGenerationStore.dynamicUsageDescription,
|
|
stack: staticGenerationStore.dynamicUsageStack
|
|
};
|
|
}
|
|
return new _renderresult.default(response.stream, options);
|
|
}
|
|
const renderToHTMLOrFlight = (req, res, pagePath, query, renderOpts)=>{
|
|
// TODO: this includes query string, should it?
|
|
const pathname = (0, _validateurl.validateURL)(req.url);
|
|
return _requestasyncstoragewrapper.RequestAsyncStorageWrapper.wrap(renderOpts.ComponentMod.requestAsyncStorage, {
|
|
req,
|
|
res,
|
|
renderOpts
|
|
}, (requestStore)=>_staticgenerationasyncstoragewrapper.StaticGenerationAsyncStorageWrapper.wrap(renderOpts.ComponentMod.staticGenerationAsyncStorage, {
|
|
urlPathname: pathname,
|
|
renderOpts,
|
|
postpone: _react.default.unstable_postpone
|
|
}, (staticGenerationStore)=>renderToHTMLOrFlightImpl(req, res, pagePath, query, renderOpts, {
|
|
requestStore,
|
|
staticGenerationStore,
|
|
componentMod: renderOpts.ComponentMod,
|
|
renderOpts
|
|
})));
|
|
};
|
|
|
|
//# sourceMappingURL=app-render.js.map
|