UNPKG

next

Version:

The React Framework

1,451 lines • 77.5 kB
import { PrefetchHint } from '../../shared/lib/app-router-types';
import { DEFAULT_SEGMENT_KEY, NOT_FOUND_SEGMENT_KEY, PAGE_SEGMENT_KEY } from '../../shared/lib/segment';
import { HEAD_REQUEST_KEY } from '../../shared/lib/segment-cache/segment-value-encoding';
import { createHrefFromUrl } from './router-reducer/create-href-from-url';
import { fetchServerResponse } from './router-reducer/fetch-server-response';
import { dispatchAppRouterAction } from './use-action-queue';
import { ACTION_SERVER_PATCH } from './router-reducer/router-reducer-types';
import { isNavigatingToNewRootLayout } from './router-reducer/is-navigating-to-new-root-layout';
import { getLastCommittedTree } from './router-reducer/reducers/committed-state';
import { createNavigationSeed } from './segment-cache/decode-server-response';
import { segmentCacheMap, rebaseInactiveRouteTree, doesRouteStructureMatch, readSegmentCacheEntryForNavigation, waitForSegmentCacheEntry, invalidateRouteCacheEntries, spawnStaticStageCacheWrite, writeRuntimePrefetchStreamIntoCache, EntryStatus, MetadataOnlyRequestTree } from './segment-cache/cache';
import { discoverKnownRoute } from './segment-cache/optimistic-routes';
import { getRenderedSearchFromVaryPath, compareParams, ParamsChange, didReadChangedParam } from './segment-cache/vary-path';
import { readFromBFCache, readFromBFCacheDuringRegularNavigation, writeToBFCache, updateBFCacheEntryFromDynamicResponse, computeDynamicStaleAt } from './segment-cache/bfcache';
export function createRootNavigationTask(tree, head) {
    return {
        tree,
        head
    };
}
export var FreshnessPolicy = /*#__PURE__*/ function(FreshnessPolicy) {
    FreshnessPolicy[FreshnessPolicy["Default"] = 0] = "Default";
    FreshnessPolicy[FreshnessPolicy["Hydration"] = 1] = "Hydration";
    FreshnessPolicy[FreshnessPolicy["HistoryTraversal"] = 2] = "HistoryTraversal";
    FreshnessPolicy[FreshnessPolicy["RefreshAll"] = 3] = "RefreshAll";
    FreshnessPolicy[FreshnessPolicy["HMRRefresh"] = 4] = "HMRRefresh";
    FreshnessPolicy[FreshnessPolicy["Gesture"] = 5] = "Gesture";
    return FreshnessPolicy;
}({});
const noop = ()=>{};
export function createInitialRenderTreeForHydration(navigatedAt, initialRoot, seedDynamicStaleAt) {
    // Create the initial cache node tree, using the data embedded into the
    // HTML document.
    const accumulation = {
        separateRefreshUrls: null,
        scrollRef: null
    };
    const parentNeedsDynamicRequest = false;
    const restrictToShell = false;
    // Hydration is bound to the shared map.
    const map = segmentCacheMap;
    const tree = createRenderTreeOnNavigation(navigatedAt, initialRoot.tree, 1, seedDynamicStaleAt, parentNeedsDynamicRequest, accumulation, map, restrictToShell);
    const head = createRenderTreeOnNavigation(navigatedAt, initialRoot.head, 1, seedDynamicStaleAt, parentNeedsDynamicRequest, accumulation, map, restrictToShell);
    return createRootNavigationTask(tree, head);
}
// Creates a new Cache Node tree (i.e. copy-on-write) that represents the
// optimistic result of a navigation, using both the current Cache Node tree and
// data that was prefetched prior to navigation.
//
// At the moment we call this function, we haven't yet received the navigation
// response from the server. It could send back something completely different
// from the tree that was prefetched — due to rewrites, default routes, parallel
// routes, etc.
//
// But in most cases, it will return the same tree that we prefetched, just with
// the dynamic holes filled in. So we optimistically assume this will happen,
// and accept that the real result could be arbitrarily different.
//
// We'll reuse anything that was already in the previous tree, since that's what
// the server does.
//
// New segments (ones that don't appear in the old tree) are assigned an
// unresolved promise. The data for these promises will be fulfilled later, when
// the navigation response is received.
//
// The tree can be rendered immediately after it is created (that's why this is
// a synchronous function). Any new trees that do not have prefetch data will
// suspend during rendering, until the dynamic data streams in.
//
// Returns the tasks for the route tree and the head, each containing both the
// updated Cache Node and a path to the pending subtrees that need to be
// resolved by the navigation response.
//
// A return value of `null` means a full-page (MPA) navigation is required.
export function startPPRNavigation(navigatedAt, oldUrl, oldRenderedSearch, oldRoot, newRoot, freshness, seedDynamicStaleAt, isSamePageNavigation, accumulation, // The segment cache map this navigation is bound to: a locked navigation's
// driving-task map, or the shared map. See `segmentCacheMap` in cache.ts.
map, // Instant Navigation Testing API only — restricts segment reads to shell
// entries. Always false outside the testing API. See navigation-testing-lock.
restrictToShell) {
    const parentNeedsDynamicRequest = false;
    const parentRefreshState = null;
    const oldRootRefreshState = {
        canonicalUrl: createHrefFromUrl(oldUrl),
        renderedSearch: oldRenderedSearch
    };
    const tree = updateRenderTreeOnNavigation(navigatedAt, oldRoot.tree, newRoot.tree, freshness, seedDynamicStaleAt, isSamePageNavigation, parentNeedsDynamicRequest, oldRootRefreshState, parentRefreshState, accumulation, map, restrictToShell);
    if (tree === null) {
        // The route tree changed at or above the root layout. Perform a full-page
        // navigation.
        return null;
    }
    const head = updateRenderTreeOnNavigation(navigatedAt, oldRoot.head, newRoot.head, freshness, seedDynamicStaleAt, isSamePageNavigation, parentNeedsDynamicRequest, oldRootRefreshState, parentRefreshState, accumulation, map, restrictToShell);
    if (head === null) {
        // Unreachable: a one-node tree has no root layout to change and no slots.
        return null;
    }
    return createRootNavigationTask(tree, head);
}
// TODO: Unify NavigationTask with CacheNode.
function createNavigationTask(status, route, node, dynamicRequestTree, children) {
    return {
        status,
        route,
        node,
        dynamicRequestTree,
        children
    };
}
function updateRenderTreeOnNavigation(navigatedAt, oldRenderTree, newRouteTree, freshness, seedDynamicStaleAt, isSamePageNavigation, parentNeedsDynamicRequest, oldRootRefreshState, parentRefreshState, accumulation, map, // Instant Navigation Testing API only — restricts segment reads to shell
// entries. Always false outside the testing API. See navigation-testing-lock.
restrictToShell) {
    // Check if the route structure changed. If only the params changed, that's
    // handled further down.
    const newSegment = newRouteTree.segment;
    if (!doesRouteStructureMatch(oldRenderTree, newRouteTree)) {
        // This segment does not match the previous route. We're now entering the
        // new part of the target route. Switch to the "create" path.
        if ((newRouteTree.prefetchHints & PrefetchHint.IsRootLayoutOrAbove) !== 0 && isNavigatingToNewRootLayout(oldRenderTree, newRouteTree) || // The global Not Found route (app/global-not-found.tsx) is a special
        // case, because it acts like a root layout, but in the router tree, it
        // is rendered in the same position as app/layout.tsx.
        //
        // Any navigation to the global Not Found route should trigger a
        // full-page navigation.
        //
        // TODO: We should probably model this by changing the key of the root
        // segment when this happens. Then the root layout check would work
        // as expected, without a special case.
        newSegment === NOT_FOUND_SEGMENT_KEY) {
            return null;
        }
        return createRenderTreeOnNavigation(navigatedAt, newRouteTree, freshness, seedDynamicStaleAt, parentNeedsDynamicRequest, accumulation, map, restrictToShell);
    }
    const newSlots = newRouteTree.slots;
    let shouldRefreshDynamicData = false;
    switch(freshness){
        case 0:
        case 2:
        case 1:
        case 5:
            shouldRefreshDynamicData = false;
            break;
        case 3:
        case 4:
            shouldRefreshDynamicData = true;
            break;
        default:
            freshness;
            break;
    }
    const isLeafSegment = newSlots === null;
    // Get the data for this segment. Since it was part of the previous route,
    // usually we just reuse the data from the old render tree. If the params
    // changed, or during a refresh or revalidation, consult the prefetch cache
    // or response seed instead.
    let newRenderTree;
    let needsDynamicRequest;
    const paramsChange = compareParams(oldRenderTree.varyPath, newRouteTree.varyPath);
    if (paramsChange !== ParamsChange.None) {
        // Path params are part of LayoutRouter's React key, so changing one
        // remounts this segment and everything below it. Generate a new bfcacheId
        // to match. Search params aren't part of the key, so a page whose search
        // params changed keeps its existing id.
        let bfcacheId;
        if (paramsChange === ParamsChange.PathParam) {
            bfcacheId = generateBFCacheId(freshness);
        } else {
            bfcacheId = oldRenderTree.data.bfcacheId;
        }
        switch(freshness){
            case 0:
            case 5:
                {
                    // If the existing data didn't read any of the params that changed, we
                    // can keep using it. Refreshes always fetch new data, and back/forward
                    // navigations restore the entry from the BFCache instead.
                    const oldCacheNode = oldRenderTree.data;
                    if (!didReadChangedParam(oldRenderTree.varyPath, newRouteTree.varyPath, oldCacheNode.varyParams)) {
                        const cacheNode = createCacheNode(oldCacheNode.rsc, oldCacheNode.prefetchRsc, oldCacheNode.varyParams, bfcacheId);
                        if (freshness !== 5) {
                            writeToBFCache(navigatedAt, newRouteTree.varyPath, cacheNode, seedDynamicStaleAt);
                        }
                        newRenderTree = createRenderTree(newRouteTree, cacheNode);
                        needsDynamicRequest = false;
                        break;
                    }
                // Intentional fallthrough
                }
            case 1:
            case 2:
            case 3:
            case 4:
                {
                    const result = createRenderTreeForSegment(navigatedAt, newRouteTree, freshness, seedDynamicStaleAt, bfcacheId, map, restrictToShell);
                    newRenderTree = result.node;
                    needsDynamicRequest = result.needsDynamicRequest;
                    break;
                }
        }
        // A param change mostly acts the same as a refresh, except it does
        // trigger a scroll.
        if (isLeafSegment) {
            accumulateScrollRef(freshness, newRenderTree.data, accumulation);
        }
    } else if (shouldRefreshDynamicData || // During a same-page navigation, we always refetch the page segments
    isLeafSegment && isSamePageNavigation) {
        // This is a refresh of an existing segment. Ignore the existing render
        // tree and create a new one.
        const result = createRenderTreeForSegment(navigatedAt, newRouteTree, freshness, seedDynamicStaleAt, // Refreshing data preserves the identity of the active segment.
        oldRenderTree.data.bfcacheId, map, restrictToShell);
        newRenderTree = result.node;
        needsDynamicRequest = result.needsDynamicRequest;
        // Carry forward the old node's scrollRef. This preserves scroll intent
        // when a prior navigation's render tree is replaced by a refresh before
        // the scroll handler has had a chance to fire — e.g. when router.push()
        // and router.refresh() are called in the same startTransition batch.
        newRenderTree.data.scrollRef = oldRenderTree.data.scrollRef;
    } else {
        // This segment appears in both the old and new routes. Reuse the existing
        // data without triggering a request.
        // TODO: Consider adding a fast path where if this segment is unchanged and
        // all of its children are unchanged, we return the exact same RenderTree
        // object. Reusing the exact previous object gives React more of a chance to
        // bail out of rendering.
        newRenderTree = createRenderTree(newRouteTree, oldRenderTree.data);
        needsDynamicRequest = false;
    }
    // During a refresh navigation, there's a special case that happens when
    // entering a "default" slot. The default slot may not be part of the
    // current route; it may have been reused from an older route. If so,
    // we need to fetch its data from the old route's URL rather than current
    // route's URL. Keep track of this as we traverse the tree.
    const maybeRefreshState = newRouteTree.refreshState;
    const refreshState = maybeRefreshState !== undefined && maybeRefreshState !== null ? // refresh URL as we continue traversing the tree.
    maybeRefreshState : parentRefreshState;
    newRenderTree.refreshState = refreshState;
    // If this segment itself needs to fetch new data from the server, then by
    // definition it is being refreshed. Track its refresh URL so we know which
    // URL to request the data from.
    if (needsDynamicRequest && refreshState !== null) {
        accumulateRefreshUrl(accumulation, refreshState);
    }
    // As we diff the trees, we may sometimes modify (copy-on-write, not mutate)
    // the Route Tree that was returned by the server — for example, in the case
    // of default parallel routes, we preserve the currently active segment. To
    // avoid mutating the original tree, we clone the router state children along
    // the return path.
    let patchedRouterStateChildren = {};
    let taskChildren = null;
    // Most navigations require a request to fetch additional data from the
    // server, either because the data was not already prefetched, or because the
    // target route contains dynamic data that cannot be prefetched.
    //
    // However, if the target route is fully static, and it's already completely
    // loaded into the segment cache, then we can skip the server request.
    //
    // This starts off as `false`, and is set to `true` if any of the child
    // routes requires a dynamic request.
    let childNeedsDynamicRequest = false;
    // As we traverse the children, we'll construct a FlightRouterState that can
    // be sent to the server to request the dynamic data. If it turns out that
    // nothing in the subtree is dynamic (i.e. childNeedsDynamicRequest is false
    // at the end), then this will be discarded.
    // TODO: We can probably optimize the format of this data structure to only
    // include paths that are dynamic. Instead of reusing the
    // FlightRouterState type.
    let dynamicRequestTreeChildren = {};
    if (newSlots !== null) {
        const oldRenderTreeSlots = oldRenderTree.slots;
        const newRenderTreeSlots = new Map();
        newRenderTree.slots = newRenderTreeSlots;
        taskChildren = new Map();
        for (let [parallelRouteKey, newRouteTreeChild] of newSlots){
            const oldRenderTreeChild = oldRenderTreeSlots?.get(parallelRouteKey);
            if (oldRenderTreeChild === undefined) {
                // This should never happen, but if it does, it suggests a malformed
                // server response. Trigger a full-page navigation.
                return null;
            }
            const oldSegmentChild = oldRenderTreeChild.segment;
            const newSegmentChild = newRouteTreeChild.segment;
            if (// Skip this branch during a history traversal. We restore the tree that
            // was stashed in the history entry as-is.
            freshness !== 2 && newSegmentChild === DEFAULT_SEGMENT_KEY && oldSegmentChild !== DEFAULT_SEGMENT_KEY && // The active segment was rendered with this layout's params. If a
            // path param changed, we can't keep it. Use the default segment from
            // the server instead.
            paramsChange !== ParamsChange.PathParam) {
                // This is a "default" segment. These are never sent by the server during
                // a soft navigation; instead, the client reuses whatever segment was
                // already active in that slot on the previous route.
                newRouteTreeChild = reuseActiveSegmentInDefaultSlot(oldRootRefreshState, oldRenderTreeChild);
            }
            const taskChild = updateRenderTreeOnNavigation(navigatedAt, oldRenderTreeChild, newRouteTreeChild, freshness, seedDynamicStaleAt, isSamePageNavigation, parentNeedsDynamicRequest || needsDynamicRequest, oldRootRefreshState, refreshState, accumulation, map, restrictToShell);
            if (taskChild === null) {
                // One of the child tasks discovered a change to the root layout.
                // Immediately unwind from this recursive traversal. This will trigger a
                // full-page navigation.
                return null;
            }
            // Recursively propagate up the child tasks.
            taskChildren.set(parallelRouteKey, taskChild);
            newRenderTreeSlots.set(parallelRouteKey, taskChild.node);
            // The child tree's route state may be different from the prefetched
            // route sent by the server. We need to clone it as we traverse back up
            // the tree.
            const taskChildRoute = taskChild.route;
            patchedRouterStateChildren[parallelRouteKey] = taskChildRoute;
            const dynamicRequestTreeChild = taskChild.dynamicRequestTree;
            if (dynamicRequestTreeChild !== null) {
                // Something in the child tree is dynamic.
                childNeedsDynamicRequest = true;
                dynamicRequestTreeChildren[parallelRouteKey] = dynamicRequestTreeChild;
            } else {
                dynamicRequestTreeChildren[parallelRouteKey] = taskChildRoute;
            }
        }
    }
    const newFlightRouterState = createRouterStateForSegment(newRouteTree, patchedRouterStateChildren, refreshState);
    return createNavigationTask(needsDynamicRequest ? 0 : 1, newFlightRouterState, newRenderTree, createDynamicRequestTree(newFlightRouterState, dynamicRequestTreeChildren, needsDynamicRequest, childNeedsDynamicRequest, parentNeedsDynamicRequest), taskChildren);
}
/**
 * Assigns a ScrollRef to a new leaf CacheNode so the scroll handler
 * knows to scroll to it after navigation. All leaves in the same
 * navigation share the same ScrollRef — the first segment to scroll
 * consumes it, preventing others from also scrolling.
 *
 * Called for newly entered segments, and for segments whose params changed
 * (even if their data was reused). Refreshes keep the existing scroll ref.
 *
 * Skipped during hydration (initial render should not scroll) and
 * history traversal (scroll restoration is handled separately).
 *
 * The head passes through here as a leaf too; its `scrollRef` is never read
 * (only LayoutRouter reads one), and the page leaf sets the same shared ref.
 */ function accumulateScrollRef(freshness, cacheNode, accumulation) {
    switch(freshness){
        case 0:
        case 5:
        case 3:
        case 4:
            if (accumulation.scrollRef === null) {
                accumulation.scrollRef = {
                    current: true
                };
            }
            cacheNode.scrollRef = accumulation.scrollRef;
            break;
        case 1:
            break;
        case 2:
            break;
        default:
            freshness;
            break;
    }
}
function createRenderTreeOnNavigation(navigatedAt, newRouteTree, freshness, seedDynamicStaleAt, parentNeedsDynamicRequest, accumulation, map, // Instant Navigation Testing API only — restricts segment reads to shell
// entries. Always false outside the testing API. See navigation-testing-lock.
restrictToShell) {
    // Same traversal as updateRenderTreeOnNavigation, but simpler. We switch to this
    // path once we reach the part of the tree that was not in the previous route.
    // We don't need to diff against the old tree, we just need to create a new
    // one. We also don't need to worry about any refresh-related logic.
    //
    // For the most part, this is a subset of updateRenderTreeOnNavigation, so any
    // change that happens in this function likely needs to be applied to that
    // one, too. However there are some places where the behavior intentionally
    // diverges, which is why we keep them separate.
    const newSlots = newRouteTree.slots;
    const result = createRenderTreeForSegment(navigatedAt, newRouteTree, freshness, seedDynamicStaleAt, // This segment was not part of the previous route, so mint a fresh
    // bfcacheId.
    generateBFCacheId(freshness), map, restrictToShell);
    const newRenderTree = result.node;
    const needsDynamicRequest = result.needsDynamicRequest;
    const isLeafSegment = newSlots === null;
    if (isLeafSegment) {
        accumulateScrollRef(freshness, newRenderTree.data, accumulation);
    }
    let patchedRouterStateChildren = {};
    let taskChildren = null;
    let childNeedsDynamicRequest = false;
    let dynamicRequestTreeChildren = {};
    if (newSlots !== null) {
        const newRenderTreeSlots = new Map();
        newRenderTree.slots = newRenderTreeSlots;
        taskChildren = new Map();
        for (const [parallelRouteKey, newRouteTreeChild] of newSlots){
            const taskChild = createRenderTreeOnNavigation(navigatedAt, newRouteTreeChild, freshness, seedDynamicStaleAt, parentNeedsDynamicRequest || needsDynamicRequest, accumulation, map, restrictToShell);
            taskChildren.set(parallelRouteKey, taskChild);
            newRenderTreeSlots.set(parallelRouteKey, taskChild.node);
            const taskChildRoute = taskChild.route;
            patchedRouterStateChildren[parallelRouteKey] = taskChildRoute;
            const dynamicRequestTreeChild = taskChild.dynamicRequestTree;
            if (dynamicRequestTreeChild !== null) {
                childNeedsDynamicRequest = true;
                dynamicRequestTreeChildren[parallelRouteKey] = dynamicRequestTreeChild;
            } else {
                dynamicRequestTreeChildren[parallelRouteKey] = taskChildRoute;
            }
        }
    }
    // This route is not part of the current tree, so there's no reason to
    // track the refresh URL.
    const refreshState = null;
    const newFlightRouterState = createRouterStateForSegment(newRouteTree, patchedRouterStateChildren, refreshState);
    return createNavigationTask(needsDynamicRequest ? 0 : 1, newFlightRouterState, newRenderTree, createDynamicRequestTree(newFlightRouterState, dynamicRequestTreeChildren, needsDynamicRequest, childNeedsDynamicRequest, parentNeedsDynamicRequest), taskChildren);
}
// Converts a route tree node into the router state the client sends back to
// the server. Page nodes carry their rendered search in its own slot.
function createRouterStateForSegment(routeTree, children, refreshState) {
    const routerState = [
        routeTree.segment,
        children,
        refreshState !== null ? [
            refreshState.canonicalUrl,
            refreshState.renderedSearch
        ] : null,
        null,
        routeTree.prefetchHints
    ];
    if (routeTree.segment === PAGE_SEGMENT_KEY) {
        const renderedSearch = getRenderedSearchFromVaryPath(routeTree.varyPath);
        if (renderedSearch !== null) {
            routerState[5] = renderedSearch;
        }
    }
    return routerState;
}
function patchRouterStateWithNewChildren(baseRouterState, newChildren) {
    const clone = [
        baseRouterState[0],
        newChildren
    ];
    // Based on equivalent logic in apply-router-state-patch-to-tree, but should
    // confirm whether we need to copy all of these fields. Not sure the server
    // ever sends, e.g. the refetch marker.
    if (2 in baseRouterState) {
        clone[2] = baseRouterState[2];
    }
    if (3 in baseRouterState) {
        clone[3] = baseRouterState[3];
    }
    if (4 in baseRouterState) {
        clone[4] = baseRouterState[4];
    }
    if (5 in baseRouterState) {
        clone[5] = baseRouterState[5];
    }
    return clone;
}
function createDynamicRequestTree(newRouterState, dynamicRequestTreeChildren, needsDynamicRequest, childNeedsDynamicRequest, parentNeedsDynamicRequest) {
    // Create a FlightRouterState that instructs the server how to render the
    // requested segment.
    //
    // Or, if neither this segment nor any of the children require a new data,
    // then we return `null` to skip the request.
    let dynamicRequestTree = null;
    if (needsDynamicRequest) {
        dynamicRequestTree = patchRouterStateWithNewChildren(newRouterState, dynamicRequestTreeChildren);
        // The "refetch" marker is set on the top-most segment that requires new
        // data. We can omit it if a parent was already marked.
        if (!parentNeedsDynamicRequest) {
            dynamicRequestTree[3] = 'refetch';
        }
    } else if (childNeedsDynamicRequest) {
        // This segment does not request new data, but at least one of its
        // children does.
        dynamicRequestTree = patchRouterStateWithNewChildren(newRouterState, dynamicRequestTreeChildren);
    } else {
        dynamicRequestTree = null;
    }
    return dynamicRequestTree;
}
function accumulateRefreshUrl(accumulation, refreshState) {
    // This is a refresh navigation, and we're inside a "default" slot that's
    // not part of the current route; it was reused from an older route. In
    // order to get fresh data for this reused route, we need to issue a
    // separate request using the old route's URL.
    //
    // Track these extra URLs in the accumulated result. Later, we'll construct
    // an appropriate request for each unique URL in the final set. The reason
    // we don't do it immediately here is so we can deduplicate multiple
    // instances of the same URL into a single request. See
    // listenForDynamicRequest for more details.
    const refreshUrl = refreshState.canonicalUrl;
    const separateRefreshUrls = accumulation.separateRefreshUrls;
    if (separateRefreshUrls === null) {
        accumulation.separateRefreshUrls = new Set([
            refreshUrl
        ]);
    } else {
        separateRefreshUrls.add(refreshUrl);
    }
}
function reuseActiveSegmentInDefaultSlot(oldRootRefreshState, oldRenderTree) {
    // This is a "default" segment. These are never sent by the server during a
    // soft navigation; instead, the client reuses whatever segment was already
    // active in that slot on the previous route. This means if we later need to
    // refresh the segment, it will have to be refetched from the previous route's
    // URL. We store the refresh context on the active render tree.
    let reusedUrl;
    let reusedRenderedSearch;
    const oldRefreshState = oldRenderTree.refreshState;
    if (oldRefreshState !== null) {
        // This segment was already reused from an even older route. Keep its
        // existing URL and refresh state.
        reusedUrl = oldRefreshState.canonicalUrl;
        reusedRenderedSearch = oldRefreshState.renderedSearch;
    } else {
        // Since this route didn't already have a refresh state, it must have been
        // reachable from the root of the old route. So we use the refresh state
        // that represents the old route.
        reusedUrl = oldRootRefreshState.canonicalUrl;
        reusedRenderedSearch = oldRootRefreshState.renderedSearch;
    }
    const reusedRouteTree = rebaseInactiveRouteTree(oldRenderTree);
    reusedRouteTree.refreshState = {
        canonicalUrl: reusedUrl,
        renderedSearch: reusedRenderedSearch
    };
    return reusedRouteTree;
}
function createRenderTree(routeTree, cacheNode) {
    return {
        requestKey: routeTree.requestKey,
        segment: routeTree.segment,
        shellVaryPath: routeTree.shellVaryPath,
        refreshState: null,
        data: cacheNode,
        varyPath: routeTree.varyPath,
        slots: null,
        prefetchHints: routeTree.prefetchHints
    };
}
function createRenderTreeForSegment(now, // A route tree node, or the one-node metadata tree that stands in for the
// head (see createMetadataRouteTree).
tree, freshness, dynamicStaleAt, bfcacheId, map, // Instant Navigation Testing API only — restricts segment reads to shell
// entries. Always false outside the testing API. See navigation-testing-lock.
restrictToShell) {
    // Construct an owned render tree using data from the BFCache, the client's
    // Segment Cache, or seeded from a server response.
    //
    // If there's a cache miss, or if we only have a partial hit, we'll render
    // the partial state immediately, and spawn a request to the server to fill
    // in the missing data.
    //
    // If the segment is fully cached on the client already, we can omit this
    // segment from the server request.
    //
    // If we already have a dynamic data response associated with this navigation,
    // as in the case of a Server Action-initiated redirect or refresh, we may
    // also be able to use that data without spawning a new request. (This is
    // referred to as the "seed" data.)
    const seedData = tree.data;
    const seedRsc = seedData !== null ? seedData.rsc : null;
    const seedVaryParams = seedData !== null ? seedData.varyParams : null;
    // During certain kinds of navigations, we may be able to render from
    // the BFCache.
    switch(freshness){
        case 0:
            {
                // Check BFCache during regular navigations. The entry's staleAt
                // determines whether it's still fresh. This is used when
                // staleTimes.dynamic is configured globally or when a page exports
                // unstable_dynamicStaleTime for per-page control.
                const bfcacheEntry = readFromBFCacheDuringRegularNavigation(now, tree.varyPath);
                if (bfcacheEntry !== null) {
                    // A regular navigation that happens to read cached data is still a
                    // fresh navigation, so we use the caller-supplied bfcacheId — the
                    // BFCacheEntry's id is only restored on history-traversal
                    // navigations.
                    return {
                        node: createRenderTree(tree, createCacheNode(bfcacheEntry.rsc, bfcacheEntry.prefetchRsc, bfcacheEntry.varyParams, bfcacheId)),
                        needsDynamicRequest: false
                    };
                }
                break;
            }
        case 1:
            {
                // This is not related to the BFCache but it is a special case.
                //
                // We should never spawn network requests during hydration. We must treat
                // the initial payload as authoritative, because the initial page load is
                // used as a last-ditch mechanism for recovering the app.
                //
                // This is also an important safety check because if this leaks into the
                // server rendering path (which theoretically it never should because the
                // server payload should be consistent), the server would hang because these
                // promises would never resolve.
                //
                // TODO: There is an existing case where the global "not found" boundary
                // triggers this path. But it does render correctly despite that. That's an
                // unusual render path so it's not surprising, but we should look into
                // modeling it in a more consistent way. See also the /_notFound special
                // case in updateRenderTreeOnNavigation.
                const cacheNode = createCacheNode(seedRsc, null, seedVaryParams, bfcacheId);
                writeToBFCache(now, tree.varyPath, cacheNode, dynamicStaleAt);
                return {
                    node: createRenderTree(tree, cacheNode),
                    needsDynamicRequest: false
                };
            }
        case 2:
            const bfcacheEntry = readFromBFCache(tree.varyPath);
            if (bfcacheEntry !== null) {
                // Only show prefetched data if the dynamic data is still pending. This
                // avoids a flash back to the prefetch state in a case where it's highly
                // likely to have already streamed in.
                //
                // Tehnically, what we're actually checking is whether the dynamic
                // network response was received. But since it's a streaming response,
                // this does not mean that all the dynamic data has fully streamed in.
                // It just means that _some_ of the dynamic data was received. But as a
                // heuristic, we assume that the rest dynamic data will stream in
                // quickly, so it's still better to skip the prefetch state.
                const oldRsc = bfcacheEntry.rsc;
                const oldRscDidResolve = !isDeferredRsc(oldRsc) || oldRsc.status !== 'pending';
                const dropPrefetchRsc = oldRscDidResolve;
                // Restore the bfcacheId from the cached entry so that back/forward
                // navigations preserve the original id, regardless of whether
                // `cacheComponents` Activity preservation is enabled.
                return {
                    node: createRenderTree(tree, createCacheNode(bfcacheEntry.rsc, dropPrefetchRsc ? null : bfcacheEntry.prefetchRsc, bfcacheEntry.varyParams, bfcacheEntry.bfcacheId)),
                    needsDynamicRequest: false
                };
            }
            break;
        case 3:
        case 4:
        case 5:
            break;
        default:
            freshness;
            break;
    }
    let cachedRsc = null;
    let isCachedRscPartial = true;
    let cachedVaryParams = null;
    const segmentEntry = readSegmentCacheEntryForNavigation(now, map, tree.varyPath, restrictToShell);
    if (segmentEntry !== null) {
        switch(segmentEntry.status){
            case EntryStatus.Fulfilled:
                {
                    // Happy path: a cache hit
                    cachedRsc = segmentEntry.rsc;
                    isCachedRscPartial = segmentEntry.isPartial;
                    cachedVaryParams = segmentEntry.varyParams;
                    break;
                }
            case EntryStatus.Pending:
                {
                    // We haven't received data for this segment yet, but there's already
                    // an in-progress request. Since it's extremely likely to arrive
                    // before the dynamic data response, we might as well use it.
                    const promiseForFulfilledEntry = waitForSegmentCacheEntry(segmentEntry);
                    cachedRsc = promiseForFulfilledEntry.then((entry)=>entry !== null ? entry.rsc : null);
                    // The entry's data hasn't arrived, and neither has the source of the
                    // params it depends on; `cachedVaryParams` stays null.
                    // Because the request is still pending, we typically don't know yet
                    // whether the response will be partial. We shouldn't skip this segment
                    // during the dynamic navigation request. Otherwise, we might need to
                    // do yet another request to fill in the remaining data, creating
                    // a waterfall.
                    //
                    // The one exception is if this segment is being fetched with via
                    // prefetch={true} (i.e. the "force stale" or "full" strategy). If so,
                    // we can assume the response will be full. This field is set to `false`
                    // for such segments.
                    isCachedRscPartial = segmentEntry.isPartial;
                    break;
                }
            case EntryStatus.Empty:
            case EntryStatus.Rejected:
                {
                    break;
                }
            default:
                {
                    segmentEntry;
                    break;
                }
        }
    }
    if (process.env.__NEXT_OPTIMISTIC_ROUTING && tree.segment === HEAD_REQUEST_KEY && isCachedRscPartial) {
        // TODO: When optimistic routing is enabled, don't block on waiting for
        // the viewport to resolve. This is a temporary workaround until Vary
        // Params are tracked when rendering the metadata. We'll fix it before
        // this feature is stable. However, it's not a critical issue because 1)
        // it will stream in eventually anyway 2) metadata is wrapped in an
        // internal Suspense boundary, so is always non-blocking; this only
        // affects the viewport node, which is meant to blocking, however... 3)
        // before Segment Cache landed this wasn't always the case, anyway, so
        // it's unlikely that many people are relying on this behavior. Still,
        // will be fixed before stable. It's the very next step in the sequence of
        // work on this project.
        //
        // This line of code works because the App Router treats `null` as
        // "no renderable head available", rather than an empty head. React treats
        // an empty string as empty.
        cachedRsc = '';
    }
    // Now combine the cached data with the seed data to determine what we can
    // render immediately, versus what needs to stream in later.
    // A partial state to show immediately while we wait for the final data to
    // arrive. If `rsc` is already a complete value (not partial), or if we
    // don't have any useful partial state, this will be `null`.
    let prefetchRsc;
    // The final, resolved segment data. If the data is missing, this will be a
    // promise that resolves to the eventual data. A resolved value of `null`
    // means the data failed to load; the LayoutRouter will suspend indefinitely
    // until the router updates again (refer to finishNavigationTask).
    let rsc;
    // The source of the params `rsc` depends on. A server response or a
    // fulfilled segment cache entry carries one; a deferred `rsc` gets its
    // source when the response arrives (finishPendingCacheNode).
    let varyParams;
    let doesSegmentNeedDynamicRequest;
    if (seedRsc !== null) {
        // We already have a dynamic server response for this segment.
        if (isCachedRscPartial) {
            // The seed data may still be streaming in, so it's worth showing the
            // partial cached state in the meantime.
            prefetchRsc = cachedRsc;
            rsc = seedRsc;
            varyParams = seedVaryParams;
        } else {
            // We already have a completely cached segment. Ignore the seed data,
            // which may still be streaming in. This shouldn't happen in the normal
            // case because the client will inform the server which segments are
            // already fully cached, and the server will skip rendering them.
            prefetchRsc = null;
            rsc = cachedRsc;
            varyParams = cachedVaryParams;
        }
        doesSegmentNeedDynamicRequest = false;
    } else {
        if (isCachedRscPartial) {
            // The cached data contains dynamic holes, or it's missing entirely. We'll
            // show the partial state immediately (if available), and stream in the
            // final data.
            //
            // Create a pending promise that we can later write to when the
            // data arrives from the server.
            prefetchRsc = cachedRsc;
            rsc = createDeferredRsc();
            varyParams = null;
        } else {
            // The data is fully cached.
            prefetchRsc = null;
            rsc = cachedRsc;
            varyParams = cachedVaryParams;
        }
        doesSegmentNeedDynamicRequest = isCachedRscPartial;
    }
    // Now that we're creating a new segment, write its data to the BFCache. A
    // subsequent back/forward navigation will reuse this same data, until or
    // unless it's cleared by a refresh/revalidation.
    //
    // Skip BFCache writes for optimistic navigations since they are transient
    // and will be replaced by the canonical navigation.
    const cacheNode = createCacheNode(rsc, prefetchRsc, varyParams, bfcacheId);
    if (freshness !== 5) {
        writeToBFCache(now, tree.varyPath, cacheNode, dynamicStaleAt);
    }
    return {
        node: createRenderTree(tree, cacheNode),
        // TODO: We should store this field on the CacheNode itself. I think we can
        // probably unify NavigationTask, CacheNode, and DeferredRsc into a
        // single type. Or at least CacheNode and DeferredRsc.
        needsDynamicRequest: doesSegmentNeedDynamicRequest
    };
}
function createCacheNode(rsc, prefetchRsc, varyParams, bfcacheId, scrollRef = null) {
    return {
        rsc,
        prefetchRsc,
        varyParams,
        scrollRef,
        bfcacheId
    };
}
// Globally-unique counter for fresh bfcacheIds. Incremented every time a new
// CacheNode is created on the client. The id surfaces to user code as a
// string via `useRouter().bfcacheId`.
let nextBFCacheId = 0;
function generateBFCacheId(freshness) {
    // Server-side rendering and the initial client-side hydration tree both
    // use a fixed sentinel so they reconcile cleanly across hydration. The
    // counter only advances on real client-side navigations after hydration.
    if (typeof window === 'undefined') return 0;
    if (freshness === 1) return 0;
    return ++nextBFCacheId;
}
// Represents whether the previuos navigation resulted in a route tree mismatch.
// A mismatch results in a refresh of the page. If there are two successive
// mismatches, we will fall back to an MPA navigation, to prevent a retry loop.
let previousNavigationDidMismatch = false;
// Writes a dynamic server response into the tree created by
// updateRenderTreeOnNavigation. All pending promises that were spawned by the
// navigation will be resolved, either with dynamic data from the server, or
// `null` to indicate that the data is missing.
//
// A `null` value will trigger a lazy fetch during render, which will then patch
// up the tree using the same mechanism as the non-PPR implementation
// (serverPatchReducer).
//
// Usually, the server will respond with exactly the subset of data that we're
// waiting for — everything below the nearest shared layout. But technically,
// the server can return anything it wants.
//
// This does _not_ create a new tree; it modifies the existing one in place.
// Which means it must follow the Suspense rules of cache safety.
export function spawnDynamicRequests(navigation, primaryUrl, nextUrl, freshnessPolicy, accumulation, // The route cache entry used for this navigation, if it came from route
// prediction. Passed through so it can be marked as having a dynamic rewrite
// if the server returns a different pathname than expected (indicating
// dynamic rewrite behavior that varies by param value).
routeCacheEntry, // The original navigation's push/replace intent. Threaded through to the
// server-patch retry logic so it can inherit the intent if the original
// transition hasn't committed yet.
navigateType, navigationLock, // The segment cache map this navigation is bound to. See `segmentCacheMap`
// in cache.ts.
map, signal) {
    let dynamicRequestTree = navigation.tree.dynamicRequestTree;
    if (dynamicRequestTree === null) {
        if (navigation.head.status === 0) {
            // Every segment is cached, but the head is not. Ask the server for the
            // head alone.
            dynamicRequestTree = MetadataOnlyRequestTree;
        } else {
            // This navigation was fully cached. There are no dynamic requests to spawn.
            previousNavigationDidMismatch = false;
            return;
        }
    }
    // This is intentionally not an async function to discourage the caller from
    // awaiting the result. Any subsequent async operations spawned by this
    // function should result in a separate navigation task, rather than
    // block the original one.
    //
    // In this function we spawn (but do not await) all the network requests that
    // block the navigation, and collect the promises. The next function,
    // `finishNavigationTask`, can await the promises in any order without
    // accidentally introducing a network waterfall.
    const primaryRequestPromise = fetchMissingDynamicData(navigation.tree, navigation.head, dynamicRequestTree, primaryUrl, nextUrl, freshnessPolicy, routeCacheEntry, navigationLock, map, signal);
    const separateRefreshUrls = accumulation.separateRefreshUrls;
    let refreshRequestPromises = null;
    if (separateRefreshUrls !== null) {
        // There are multiple URLs that we need to request the data from. This
        // happens when a "default" parallel route slot is present in the tree, and
        // its data cannot be fetched from the current route. We need to split the
        // combined dynamic request tree into separate requests per URL.
        // TODO: Create a scoped dynamic request tree that omits anything that
        // is not relevant to the given URL. Without doing this, the server may
        // sometimes render more data than necessary; this is not a regression
        // compared to the pre-Segment Cache implementation, though, just an
        // optimization we can make in the future.
        // Construct a request tree for each additional refresh URL. This will
        // prune away everything except the parts of the tree that match the
        // given refresh URL.
        refreshRequestPromises = [];
        const canonicalUrl = createHrefFromUrl(primaryUrl);
        for (const refreshUrl of separateRefreshUrls){
            if (refreshUrl === canonicalUrl) {
                continue;
            }
            // TODO: Create a scoped dynamic request tree that omits anything that
            // is not relevant to the given URL. Without doing this, the server may
            // sometimes render more data than necessary; this is not a regression
            // compared to the pre-Segment Cache implementation, though, just an
            // optimization we can make in the future.
            // const scopedDynamicRequestTree = splitTaskByURL(task, refreshUrl)
            const scopedDynamicRequestTree = dynamicRequestTree;
            if (scopedDynamicRequestTree !== null) {
                refreshRequestPromises.push(fetchMissingDynamicData(navigation.tree, // The head belongs to the primary URL.
                null, scopedDynamicRequestTree, new URL(refreshUrl, location.origin), // TODO: Just noticed that this should actually the Next-Url at the
                // time the refresh URL was set, not the current Next-Url. Need to
                // start tracking this alongside the refresh URL. In the meantime,
                // if a refresh fails due to a mismatch, it will trigger a
                // hard refresh.
                nextUrl, freshnessPolicy, routeCacheEntry, navigationLock, map, signal));
            }
        }
    }
    // Further async operations are moved into this separate function to
    // discourage sequential network requests.
    const voidPromise = finishNavigationTask(navigation, nextUrl, primaryRequestPromise, refreshRequestPromises, routeCacheEntry, navigateType);
    // `finishNavigationTask` is responsible for error handling, so we can attach
    // noop callbacks to this promise.
    voidPromise.then(noop, noop);
}
async function finishNavigationTask(navigation, nextUrl, primaryRequestPromise, refreshRequestPromises, routeCacheEntry, navigateType) {
    // Wait for all the requests to finish, or for the first one to fail.
    let exitStatus = await waitForRequestsToFinish(primaryRequestPromise, refreshRequestPromises);
    // Once the all the requests have finished, check the tree for any remaining
    // pending tasks. If anything is still pending, it means the server response
    // does not match the client, and we must refresh to get back to a consistent
    // state. We can skip this step if we already detected a mismatch during the
    // first phase; it doesn't matter in that case because we're going to refresh
    // the whole tree regardless.
    if (exitStatus === 0) {
        exitStatus = abortRemainingPendingTasks(navigation.tree, null, null);
        // A response without a head is a mismatch, like any missing segment. The
        // head's deferred rsc must be resolved to `null` here, never rejected: it
        // renders at the app root, so a rejection would hit the root error
        // boundary while the retry is in flight.
        const headExitStatus = abortRemainingPendingTasks(navigation.head, null, null);
        if (headExitStatus > exitStatus) {
            exitStatus = headExitStatus;
        }
    }
    switch(exitStatus){
        case -1:
            {
                // This navigation was superseded and its request aborted. Its cache nodes
                // may already be reused by the newer navigation, so leave them untouched
                // for the newer request to fulfill. If the tree was abandoned entirely,
                // it can be garbage collected along with its unresolved promises. We do
                // not retry or hard-navigate.
                return;
            }
        case 0:
            {
                // The task has completely finished. There's no missing data. Exit.
                previousNavigationDidMismatch = false;
                return;
            }
        case 1:
            {
                // Some data failed to finish loading. Trigger a soft retry that re-fetches
                // the tree's dynamic data.
                // TODO: As an extra precaution against soft retry loops, consider
                // tracking whether a navigation was itself triggered by a retry. If two
                // happen in a row, fall back to a hard retry.
                const isHardRetry = false;
                const primaryRequestResult = await primaryRequestPromise;
                dispatchRetryDueToTreeMismatch(isHardRetry, primaryRequestResult.url, nextUrl, primaryRequestResult.seed, navigation, routeCacheEntry, navigateType, 3);
                return;
            }
        case 3:
            {
                // The route matched, but the request was redirected, so we committed the
                // wrong canonical URL. Re-resolve the route to invalidate the now-stale
                // route cache and correct the URL — but reuse the data we already received
                // (HistoryTraversal) instead of re-fetching it. See issue #95195.
                const isHardRetry = false;
                const primaryRequestResult = await primaryRequestPromise;
                dispatchRetryDueToTreeMismatch(isHardRetry, primaryRequestResult.url, nextUrl, primaryRequestResult.seed, navigation, routeCacheEntry, navigateType, 2);
                return;
            }
        case 2:
            {
                // Some data failed to finish loading in a non-recoverable way, such as a
                // network error. Trigger an MPA navigation.
                //
                // Hard navigating/refreshing is how we prevent an infinite retry loop
                // caused by a network error — when the network fails, we fall back to the
                // browser behavior for offline navigations. In the future, Next.js may
                // introduce its own custom handling of offline navigations, but that
                // doesn't exist yet.
                const isHardRetry = true;
                const primaryRequestResult = await primaryRequestPromise;
                dispatchRetryDueToTreeMismatch(isHardRetry, primaryRequestResult.url, nextUrl, primaryRequestResult.seed, navigation, routeCacheEntry, navigateType, 3);
                return;
            }
        default:
            {
                return exitStatus;
            }
    }
}
function waitForRequestsToFinish(primaryRequestPromise, refreshRequestPromises) {
    // Custom async combinator logic. This could be replaced by Promise.any but
    // we don't assume that's available.
    //
    // Each promise resolves once the server responsds and the data is written
    // into the render tree. Resolve the combined promise once all the
    // requests finish.
    //
    // Or, resolve as soon as one of the requests fails, without waiting for the
    // others to finish.
    return new Promise((resolve)=>{
        const onFulfill = (result)=>{
            if (result.exitStatus === 0) {
                remainingCount--;
                if (remainingCount === 0) {
                    // All the requests finished successfully.
                    resolve(0);
                }
            } else {
                // One of the requests failed. Exit with a failing status.
                // NOTE: It's possible for one of the requests to fail with SoftRetry
                // and a later one to fail with HardRetry. In this case, we choose to
                // retry immediately, rather than delay the retry until all the requests
                // finish. If it fails again, we will hard retry on the next
                // attempt, anyway.
                resolve(result.exitStatus);
            }
        };
        // onReject shouldn't ever be called because fetchMissingDynamicData's
        // entire body is wrapped in a try/catch. This is just defensive.
        const onReject = ()=>resolve(2);
        // Attach the listeners to the promises.
        let remainingCount = 1;
        primaryRequestPromise.then(onFulfill, onReject);
        if (refreshRequestPromises !== null) {
            remainingCount += refreshRequestPromises.length;
            refreshRequestPromises.forEach((refreshRequestPromise)=>refreshRequestPromise.then(onFulfill, onReject));
        }
    });
}
function dispatchRetryDueToTreeMismatch(isHardRetry, retryUrl, retryNextUrl, seed, navigation, // The route cache entry used for this navigation, if it came from route
// prediction. If the navigation results in a mismatch, we mark it as having
// a dynamic rewrite so future predictions bail out.
routeCacheEntry, // The original navigation's push/replace intent.
originalNavigateType, // Freshness policy for the retry navigation. `RefreshAll` re-fetches the
// tree's dynamic data (used for genuine tree mismatches). `HistoryTraversal`
// reuses the data already in the tree (used when only the URL needs
// correcting after a redirect).
retryFreshnessPolicy) {
    // If the navigation used a route prediction, mark the node it was predicted
    // from as having a dynamic rewrite since it resulted in a mismatch. A route
    // entry the server resolved has nothing to mark: nothing was predicted
    // from it.
    if (routeCacheEntry !== null) {
        const predictedFrom = routeCacheEntry.predictedFrom;
        if (predictedFrom !== null) {
            predictedFrom.hasDynamicRewrite = true;
        }
    } else if (seed !== null) {
        // Even without a direct reference to the route cache entry, we can still
        // mark the route as having a dynamic rewrite by traversing the known route
        // tree. This handles cases where the navigation didn't originate from a
        // route prediction, but still needs to mark the pattern.
        const now = Date.now();
        discoverKnownRoute(now, retryUrl.pathname, retryUrl.search, retryNextUrl, null, seed.root, false, createHrefFromUrl(retryUrl), seed.renderedSearch, false, true // hasDynamicRewrite
        );
    }
    // Invalidate all route cache entries. If the navigation used a route entry
    // the server resolved, its tree is what the server just contradicted, so
    // the retry must re-fetch it rather than navigate with it again. This also
    // triggers re-prefetching of visible links.
    invalidateRouteCacheEntries(retryNextUrl, {
        tree: navigation.tree.node,
        head: navigation.head.node
    });
    // If this is the second time in a row that a navigation resulted in a
    // mismatch, fall back to a hard (MPA) refresh.
    isHardRetry = isHardRetry || previousNavigationDidMismatch;
    previousNavigationDidMismatch = true;
    // If the original navigation hasn't committed to the browser history yet
    // (the transition suspended before React committed), inherit its push/replace
    // intent. Otherwise, the pushState already ran, so use 'replace' to avoid
    // creating a duplicate history entry.
    //
    // This works because React entangles the retry's state update with the
    // original pending transition — they commit together as a single batch,
    // so the navigate type from the retry is what HistoryUpdater ultimately sees.
    //
    // TODO: Ideally this check would happen right before we schedule the React
    // update (i.e., closer to where the action is dispatched into the queue),
    // not here where the action is constructed. But the current action queue
    // doesn't provide a natural place for that. Revisit when we refactor the
    // action queue into a more reactive navigation model.
    const baseTree = navigation.tree.route;
    const lastCommitted = getLastCommittedTree();
    const retryNavigateType = lastCommitted !== null && baseTree !== lastCommitted ? originalNavigateType : 'replace';
    const retryAction = {
        type: ACTION_SERVER_PATCH,
        previousTree: baseTree,
        url: retryUrl,
        nextUrl: retryNextUrl,
        seed,
        mpa: isHardRetry,
        navigateType: retryNavigateType,
        freshnessPolicy: retryFreshnessPolicy
    };
    dispatchAppRouterAction(retryAction);
}
async function fetchMissingDynamicData(tree, head, dynamicRequestTree, url, nextUrl, freshnessPolicy, routeCacheEntry, navigationLock, map, signal) {
    try {
        const result = await fetchServerResponse(url, {
            flightRouterState: dynamicRequestTree,
            nextUrl,
            isHmrRefresh: freshnessPolicy === 4,
            signal
        });
        if (typeof result === 'string') {
            // fetchServerResponse will return an href to indicate that the SPA
            // navigation failed. For example, if the server triggered a hard
            // redirect, or the fetch request errored. Initiate an MPA navigation
            // to the given href.
            return {
                exitStatus: 2,
                url: new URL(result, location.origin),
                seed: null
            };
        }
        const now = Date.now();
        const seed = createNavigationSeed(now, tree.route, result.transportData, // Navigation responses stream in incrementally, so their vary params
        // can't be drained here; they decode as null.
        null, result.isResponsePartial, // Navigation responses always include the param values in the tree, so
        // there's no pathname to parse them from (nor a need to).
        null, result.renderedSearch, null, result.dynamicStaleTime);
        // If the navigation lock is active, wait for it to be released before
        // writing the dynamic data. This allows tests to assert on the prefetched
        // UI state.
        if (process.env.__NEXT_EXPOSE_TESTING_API && navigationLock !== null) {
            await navigationLock;
        }
        if (routeCacheEntry !== null && result.staticStageResponse !== null) {
            spawnStaticStageCacheWrite(now, result.staticStageResponse, result.isResponsePartial, result.responseHeaders, dynamicRequestTree, result.renderedSearch, map);
        }
        if (routeCacheEntry !== null && result.runtimePrefetchStream !== null) {
            writeRuntimePrefetchStreamIntoCache(now, result.runtimePrefetchStream, dynamicRequestTree, result.renderedSearch, map).catch(()=>{
            // The runtime prefetch cache write failed. Not fatal — the
            // navigation completed normally, we just won't cache runtime data.
            });
        }
        // result.dynamicStaleTime is in seconds (from the server's `d` field).
        // Convert to an absolute timestamp using the centralized helper.
        const dynamicStaleAt = computeDynamicStaleAt(now, result.dynamicStaleTime);
        const didReceiveUnknownParallelRoute = writeDynamicDataIntoNavigationTask(tree, seed.root.tree, dynamicStaleAt, result.debugInfo, result.revealAfter);
        if (head !== null) {
            writeDynamicDataIntoNavigationTask(head, seed.root.head, dynamicStaleAt, result.debugInfo, result.revealAfter);
        }
        const resolvedUrl = new URL(result.canonicalUrl, location.origin);
        // Decide whether the navigation needs to be retried.
        //
        // - A tree mismatch (unknown parallel route) means the data is incomplete,
        //   so we soft-retry and re-fetch the whole tree.
        // - Otherwise, the navigation committed the canonical URL from the route
        //   cache entry it used (a prediction or prefetch). If the request resolved
        //   to a *different* canonical URL — e.g. a middleware/proxy redirect the
        //   prediction didn't account for — then the committed URL is wrong and the
        //   route cache it came from is no longer reliable (the redirect implies a
        //   server change the prediction couldn't know about, like logging in or
        //   out). We re-resolve the route to invalidate the stale cache and correct
        //   the browser URL, reusing the data we just received rather than
        //   re-fetching it. When the entry already reflects the redirect (e.g. a
        //   prefetch that followed it), the committed URL matches and no retry is
        //   needed. See issue #95195.
        let didCommitWrongUrl = false;
        if (routeCacheEntry !== null) {
            const committedUrl = new URL(routeCacheEntry.canonicalUrl, location.origin);
            didCommitWrongUrl = committedUrl.pathname !== resolvedUrl.pathname || committedUrl.search !== resolvedUrl.search;
        }
        const exitStatus = didReceiveUnknownParallelRoute ? 1 : didCommitWrongUrl ? 3 : 0;
        return {
            exitStatus,
            url: resolvedUrl,
            seed
        };
    } catch  {
        if (signal?.aborted) {
            // A newer HMR refresh superseded this one and aborted its request. Treat
            // it as canceled rather than a failure, so we don't retry or
            // hard-navigate.
            return {
                exitStatus: -1,
                url,
                seed: null
            };
        }
        // This shouldn't happen because fetchServerResponse's entire body is
        // wrapped in a try/catch. If it does, though, it implies the server failed
        // to respond with any tree at all. So we must fall back to a hard retry.
        return {
            exitStatus: 2,
            url: url,
            seed: null
        };
    }
}
function writeDynamicDataIntoNavigationTask(task, serverRouteTree, dynamicStaleAt, debugInfo, revealAfter) {
    // A non-null data object means the response accounted for this segment,
    // even if it didn't render it (data.rsc may still be null, e.g. for the
    // intermediate segments on the path to a rendered subtree).
    const dynamicData = serverRouteTree.data;
    if (task.status === 0 && dynamicData !== null) {
        task.status = 1;
        const cacheNode = task.node.data;
        finishPendingCacheNode(cacheNode, dynamicData, debugInfo, revealAfter);
        // The BFCache entry for this segment was written before the response
        // arrived. Bring it up to date with what the response filled in: its
        // staleAt (the per-page unstable_dynamicStaleTime if set, or the default
        // DYNAMIC_STALETIME_MS) and the source of the params its data depends
        // on. We only update segments that received dynamic data — static
        // segments are unaffected.
        updateBFCacheEntryFromDynamicResponse(serverRouteTree.varyPath, cacheNode, dynamicStaleAt);
    }
    const taskChildren = task.children;
    const serverChildren = serverRouteTree.slots;
    // Detect whether the server sends a parallel route slot that the client
    // doesn't know about.
    let didReceiveUnknownParallelRoute = false;
    if (taskChildren !== null) {
        if (serverChildren !== null) {
            for (const [parallelRouteKey, serverRouteTreeChild] of serverChildren){
                const taskChild = taskChildren.get(parallelRouteKey);
                if (taskChild === undefined) {
                    // The server sent a child segment that the client doesn't know about.
                    //
                    // When we receive an unknown parallel route, we must consider it a
                    // mismatch. This is unlike the case where the segment itself
                    // mismatches, because multiple routes can be active simultaneously.
                    // But a given layout should never have a mismatching set of
                    // child slots.
                    //
                    // Theoretically, this should only happen in development during an HMR
                    // refresh, because the set of parallel routes for a layout does not
                    // change over the lifetime of a build/deployment. In production, we
                    // should have already mismatched on either the build id or the segment
                    // path. But as an extra precaution, we validate in prod, too.
                    didReceiveUnknownParallelRoute = true;
                } else {
                    // Check that the response is for the route we expected: same route
                    // structure and same params, including the page's search params.
                    if (doesRouteStructureMatch(taskChild.node, serverRouteTreeChild) && serverRouteTreeChild.data !== null && compareParams(taskChild.node.varyPath, serverRouteTreeChild.varyPath) === ParamsChange.None) {
                        // Found a match for this task. Keep traversing down the task tree.
                        const childDidReceiveUnknownParallelRoute = writeDynamicDataIntoNavigationTask(taskChild, serverRouteTreeChild, dynamicStaleAt, debugInfo, revealAfter);
                        if (childDidReceiveUnknownParallelRoute) {
                            didReceiveUnknownParallelRoute = true;
                        }
                    }
                }
            }
        } else {
            if (serverChildren !== null) {
                // The server sent a child segment that the client doesn't know about.
                didReceiveUnknownParallelRoute = true;
            }
        }
    }
    return didReceiveUnknownParallelRoute;
}
function finishPendingCacheNode(cacheNode, dynamicData, debugInfo, revealAfter) {
    // Writes a dynamic response into an existing render tree. This does _not_
    // create a new tree, it updates the existing tree in-place. So it must follow
    // the Suspense rules of cache safety — it can resolve pending promises, but
    // it cannot overwrite existing data. It can add segments to the tree (because
    // a missing segment will cause the layout router to suspend) but it cannot
    // delete them.
    //
    // We must resolve every promise in the tree, or else it will suspend
    // indefinitely. If we did not receive data for a segment, we will resolve its
    // data promise to `null` to trigger a lazy fetch during render.
    // Use the dynamic data from the server to fulfill the deferred RSC promise.
    const rsc = cacheNode.rsc;
    const dynamicSegmentData = dynamicData.rsc;
    if (dynamicSegmentData === null) {
        // This particular server request did not
        // render this segment. There may be a separate pending request that will,
        // though, so we won't abort the task until all pending requests finish.
        return;
    }
    // TODO: `varyParams` must always describe the render that produced `rsc`,
    // but nothing in the CacheNode type ties the two fields together; this
    // function keeps them in lockstep by writing both at once. Eventually the
    // whole CacheNode should be a thenable whose fields are populated through
    // dedicated helpers that own the state transition.
    if (rsc === null) {
        // This is a lazy cache node. We can overwrite it. This is only safe
        // because we know that the LayoutRouter suspends if `rsc` is `null`.
        cacheNode.rsc = dynamicSegmentData;
        cacheNode.varyParams = dynamicData.varyParams;
    } else if (isDeferredRsc(rsc) && rsc.status === 'pending') {
        // This is a deferred RSC promise. We can fulfill it with the data we just
        // received from the server. The source of the params that data depends
        // on travels with it.
        //
        // In the streaming dev render, defer the fill until `revealAfter` settles,
        // so React doesn't render the boundary's children before their row has been
        // decoded (otherwise it suspends on the still-pending children and commits
        // a premature fallback). Outside that render `revealAfter` is null and we
        // resolve immediately.
        cacheNode.varyParams = dynamicData.varyParams;
        if (revealAfter !== null) {
            const resolveRsc = ()=>rsc.resolve(dynamicSegmentData, debugInfo);
            // Use the same callback for both outcomes: we don't expect `revealAfter`
            // to reject, but if it ever did (e.g. a connection drop mid-stream) we'd
            // still want to resolve the RSC.
            revealAfter.then(resolveRsc, resolveRsc);
        } else {
            rsc.resolve(dynamicSegmentData, debugInfo);
        }
    } else {
    // This is not a deferred RSC promise that's still pending, nor is it
    // empty, so it must have been populated by a different navigation. We
    // must not overwrite it (nor its dependency source).
    }
}
function abortRemainingPendingTasks(task, error, debugInfo) {
    let exitStatus;
    if (task.status === 0) {
        // The data for this segment is still missing.
        task.status = 2;
        abortPendingCacheNode(task.node.data, error, debugInfo);
        // If the server failed to fulfill the data for this segment, it implies
        // that the route tree received from the server mismatched the tree that
        // was previously prefetched.
        //
        // In an app with fully static routes and no proxy-driven redirects or
        // rewrites, this should never happen, because the route for a URL would
        // always be the same across multiple requests. So, this implies that some
        // runtime routing condition changed, likely in a proxy, without being
        // pushed to the client.
        //
        // When this happens, we treat this the same as a refresh(). The entire
        // tree will be re-rendered from the root.
        if (task.node.refreshState === null) {
            // Trigger a "soft" refresh. Essentially the same as calling `refresh()`
            // in a Server Action.
            exitStatus = 1;
        } else {
            // The mismatch was discovered inside an inactive parallel route. This
            // implies the inactive parallel route is no longer reachable at the URL
            // that originally rendered it. Fall back to an MPA refresh.
            // TODO: An alternative could be to trigger a soft refresh but to _not_
            // re-use the inactive parallel routes this time. Similar to what would
            // happen if were to do a hard refrehs, but without the HTML page.
            exitStatus = 2;
        }
    } else {
        // This segment finished. (An error here is treated as Done because they are
        // surfaced to the application during render.)
        exitStatus = 0;
    }
    const taskChildren = task.children;
    if (taskChildren !== null) {
        for (const [, taskChild] of taskChildren){
            const childExitStatus = abortRemainingPendingTasks(taskChild, error, debugInfo);
            // Propagate the exit status up the tree. The statuses are ordered by
            // their precedence.
            if (childExitStatus > exitStatus) {
                exitStatus = childExitStatus;
            }
        }
    }
    return exitStatus;
}
function abortPendingCacheNode(cacheNode, error, debugInfo) {
    const rsc = cacheNode.rsc;
    if (isDeferredRsc(rsc)) {
        if (error === null) {
            // This will trigger a lazy fetch during render.
            rsc.resolve(null, debugInfo);
        } else {
            // This will trigger an error during rendering.
            rsc.reject(error, debugInfo);
        }
    }
}
const DEFERRED = Symbol();
// This type exists to distinguish a DeferredRsc from a Flight promise. It's a
// compromise to avoid adding an extra field on every Cache Node, which would be
// awkward because the pre-PPR parts of codebase would need to account for it,
// too. We can remove it once type Cache Node type is more settled.
export function isDeferredRsc(value) {
    return value && typeof value === 'object' && value.tag === DEFERRED;
}
function createDeferredRsc() {
    // Create an unresolved promise that represents data derived from a Flight
    // response. The promise will be resolved later as soon as we start receiving
    // data from the server, i.e. as soon as the Flight client decodes and returns
    // the top-level response object.
    // The `_debugInfo` field contains profiling information. Promises that are
    // created by Flight already have this info added by React; for any derived
    // promise created by the router, we need to transfer the Flight debug info
    // onto the derived promise.
    //
    // The debug info represents the latency between the start of the navigation
    // and the start of rendering. (It does not represent the time it takes for
    // whole stream to finish.)
    const debugInfo = [];
    let resolve;
    let reject;
    const pendingRsc = new Promise((res, rej)=>{
        resolve = res;
        reject = rej;
    });
    pendingRsc.status = 'pending';
    pendingRsc.resolve = (value, responseDebugInfo)=>{
        if (pendingRsc.status === 'pending') {
            const fulfilledRsc = pendingRsc;
            fulfilledRsc.status = 'fulfilled';
            fulfilledRsc.value = value;
            if (responseDebugInfo !== null) {
                // Transfer the debug info to the derived promise.
                debugInfo.push.apply(debugInfo, responseDebugInfo);
            }
            resolve(value);
        }
    };
    pendingRsc.reject = (error, responseDebugInfo)=>{
        if (pendingRsc.status === 'pending') {
            const rejectedRsc = pendingRsc;
            rejectedRsc.status = 'rejected';
            rejectedRsc.reason = error;
            if (responseDebugInfo !== null) {
                // Transfer the debug info to the derived promise.
                debugInfo.push.apply(debugInfo, responseDebugInfo);
            }
            reject(error);
        }
    };
    pendingRsc.tag = DEFERRED;
    pendingRsc._debugInfo = debugInfo;
    return pendingRsc;
}
/**
 * Helper for the Instant Navigation Testing API. Captures the withheld-data
 * gate of the locked navigation that is current when router work spawns a
 * dynamic write, so the write awaits that same gate even if a newer locked
 * navigation rolls the lock over before its response is applied.
 *
 * Not exposed in production builds by default.
 */ export function getCurrentNavigationLock() {
    if (process.env.__NEXT_EXPOSE_TESTING_API) {
        const { getCurrentNavigationGate } = require('./segment-cache/navigation-testing-lock');
        return getCurrentNavigationGate();
    }
    return null;
}
/**
 * Helper for the Instant Navigation Testing API. Signals that a new locked
 * navigation is beginning: force-resolves the previous locked navigation's
 * withheld-data gate (without ending the scope) and returns a fresh gate for
 * this navigation, which the caller threads to its dynamic-data write. See
 * `beginLockedNavigation` in `navigation-testing-lock`.
 *
 * Not exposed in production builds by default.
 */ export function beginLockedNavigation() {
    if (process.env.__NEXT_EXPOSE_TESTING_API) {
        const { beginLockedNavigation: begin } = require('./segment-cache/navigation-testing-lock');
        return begin();
    }
    return null;
}
/**
 * Helper for the Instant Navigation Testing API. Called during a history
 * traversal: resets the testing lock to a fresh pending scope, releasing any
 * withheld data from prior navigations. See `resetNavigationLockToPending` in
 * `navigation-testing-lock`.
 */ export function resetNavigationLockToPending() {
    if (process.env.__NEXT_EXPOSE_TESTING_API) {
        const { resetNavigationLockToPending: reset } = require('./segment-cache/navigation-testing-lock');
        reset();
    }
}

//# sourceMappingURL=render-tree.js.map