next
Version:
The React Framework
157 lines (156 loc) • 7.09 kB
JavaScript
import { getCacheKeyForDynamicParam, parseDynamicParamFromURLPart, doesStaticSegmentAppearInURL, getRenderedPathname, getRenderedSearch } from './route-params';
import { createHrefFromUrl } from './components/router-reducer/create-href-from-url';
export function createInitialRSCPayloadFromFallbackPrerender(response, fallbackInitialRSCPayload) {
// This is a static fallback page. In order to hydrate the page, we need to
// parse the client params from the URL, but to account for the possibility
// that the page was rewritten, we need to check the response headers
// for x-nextjs-rewritten-path or x-nextjs-rewritten-query headers. Since
// we can't access the headers of the initial document response, the client
// performs a fetch request to the current location. Since it's possible that
// the fetch request will be dynamically rewritten to a different path than
// the initial document, this fetch request delivers _all_ the hydration data
// for the page; it was not inlined into the document, like it normally
// would be.
//
// TODO: Consider treating the case where fetch is rewritten to a different
// path from the document as a special deopt case. We should optimistically
// assume this won't happen, inline the data into the document, and perform
// a minimal request (like a HEAD or range request) to verify that the
// response matches. Tricky to get right because we need to account for
// all the different deployment environments we support, like output:
// "export" mode, where we currently don't assume that custom response
// headers are present.
// Patch the transport tree sent by the server with the correct params
// parsed from the URL + response object.
const renderedPathname = getRenderedPathname(response);
const renderedSearch = getRenderedSearch(response);
const canonicalUrl = createHrefFromUrl(new URL(location.href));
const fallbackTransportData = fallbackInitialRSCPayload.t;
const payload = {
c: canonicalUrl.split('/'),
q: renderedSearch,
i: fallbackInitialRSCPayload.i,
t: {
t: fillInFallbackTransportTree(fallbackTransportData.t, renderedPathname.split('/').filter((part)=>part !== ''), 0),
h: fallbackTransportData.h
},
m: fallbackInitialRSCPayload.m,
G: fallbackInitialRSCPayload.G,
S: fallbackInitialRSCPayload.S
};
if (fallbackInitialRSCPayload.b) {
payload.b = fallbackInitialRSCPayload.b;
}
return payload;
}
/**
* A fallback prerender is rendered without concrete param values. Fill them
* in by parsing the rendered pathname, so the hydrated tree has the same
* identity a direct render of this URL would have.
*/ function fillInFallbackTransportTree(node, pathnameParts, pathnamePartsIndex) {
const originalSegment = node.s;
let newSegment;
let doesAppearInURL;
if (typeof originalSegment === 'string') {
newSegment = originalSegment;
doesAppearInURL = doesStaticSegmentAppearInURL(originalSegment);
} else {
const paramValue = parseDynamicParamFromURLPart(originalSegment.t, pathnameParts, pathnamePartsIndex);
newSegment = {
n: originalSegment.n,
t: originalSegment.t,
k: getCacheKeyForDynamicParam(paramValue),
s: originalSegment.s
};
doesAppearInURL = true;
}
// Only increment the index if the segment appears in the URL. If it's a
// "virtual" segment, like a route group, it remains the same.
const childPathnamePartsIndex = doesAppearInURL ? pathnamePartsIndex + 1 : pathnamePartsIndex;
const children = node.c;
let newChildren;
if (children !== undefined) {
newChildren = new Map();
for (const [parallelRouteKey, childNode] of children){
newChildren.set(parallelRouteKey, fillInFallbackTransportTree(childNode, pathnameParts, childPathnamePartsIndex));
}
}
const newNode = {
s: newSegment,
d: node.d
};
if (node.h !== undefined) {
newNode.h = node.h;
}
if (newChildren !== undefined) {
newNode.c = newChildren;
}
return newNode;
}
export function getNextFlightSegmentPath(flightSegmentPath) {
// Since `FlightSegmentPath` is a repeated tuple of `Segment` and `ParallelRouteKey`, we slice off two items
// to get the next segment path.
return flightSegmentPath.slice(2);
}
/**
* This function is used to prepare the flight router state for the request.
* It removes markers that are not needed by the server, and are purely used
* for stashing state on the client.
* @param flightRouterState - The flight router state to prepare.
* @param isHmrRefresh - Whether this is an HMR refresh request.
* @returns The prepared flight router state.
*/ export function prepareFlightRouterStateForRequest(flightRouterState, isHmrRefresh) {
// HMR requests need the complete, unmodified state for proper functionality
if (isHmrRefresh) {
return encodeURIComponent(JSON.stringify(flightRouterState));
}
return encodeURIComponent(JSON.stringify(stripClientOnlyDataFromFlightRouterState(flightRouterState)));
}
/**
* Recursively strips client-only data from FlightRouterState while preserving
* server-needed information for proper rendering decisions.
*/ function stripClientOnlyDataFromFlightRouterState(flightRouterState) {
const [segment, parallelRoutes, _refreshState, refreshMarker, prefetchHints] = flightRouterState;
// Strip client-only data from the segment
const cleanedSegment = stripClientOnlyDataFromSegment(segment);
// Recursively process parallel routes
const cleanedParallelRoutes = {};
for (const [key, childState] of Object.entries(parallelRoutes)){
cleanedParallelRoutes[key] = stripClientOnlyDataFromFlightRouterState(childState);
}
const result = [
cleanedSegment,
cleanedParallelRoutes
];
if (refreshMarker) {
result[2] = null // null slightly more compact than undefined
;
result[3] = refreshMarker;
}
// Append optional fields if present
if (prefetchHints !== undefined) {
result[4] = prefetchHints;
}
// Leave out the page's search params. They're only used by the client, and
// request headers may be forwarded through redirects.
return result;
}
/**
* Strips client-only data from segments:
* - staticSiblings from dynamic segment tuples (only needed for client-side
* prefetch reuse decisions)
*/ function stripClientOnlyDataFromSegment(segment) {
if (typeof segment === 'string') {
return segment;
}
// Dynamic segment tuple: [paramName, paramCacheKey, paramType, staticSiblings]
// Strip staticSiblings (4th element) since server doesn't need it
const [paramName, paramCacheKey, paramType] = segment;
return [
paramName,
paramCacheKey,
paramType,
null
];
}
//# sourceMappingURL=flight-data-helpers.js.map