deploy: v0.12 fix 存档key名

This commit is contained in:
2026-06-24 03:09:02 +00:00
parent 7a08d49fe3
commit e0a5281119
58743 changed files with 7297269 additions and 39505 deletions
@@ -0,0 +1,8 @@
import type { FlightRouterState } from '../../../shared/lib/app-router-types';
import type { Params } from '../../../server/request/params';
export declare function extractPathFromFlightRouterState(flightRouterState: FlightRouterState): string | undefined;
export declare function computeChangedPath(treeA: FlightRouterState, treeB: FlightRouterState): string | null;
/**
* Recursively extracts dynamic parameters from FlightRouterState.
*/
export declare function getSelectedParams(currentTree: FlightRouterState, params?: Params): Params;
@@ -0,0 +1,128 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
computeChangedPath: null,
extractPathFromFlightRouterState: null,
getSelectedParams: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
computeChangedPath: function() {
return computeChangedPath;
},
extractPathFromFlightRouterState: function() {
return extractPathFromFlightRouterState;
},
getSelectedParams: function() {
return getSelectedParams;
}
});
const _interceptionroutes = require("../../../shared/lib/router/utils/interception-routes");
const _segment = require("../../../shared/lib/segment");
const _matchsegments = require("../match-segments");
const removeLeadingSlash = (segment)=>{
return segment[0] === '/' ? segment.slice(1) : segment;
};
const segmentToPathname = (segment)=>{
if (typeof segment === 'string') {
// 'children' is not a valid path -- it's technically a parallel route that corresponds with the current segment's page
// if we don't skip it, then the computed pathname might be something like `/children` which doesn't make sense.
if (segment === 'children') return '';
return segment;
}
return segment[1];
};
function normalizeSegments(segments) {
return segments.reduce((acc, segment)=>{
segment = removeLeadingSlash(segment);
if (segment === '' || (0, _segment.isGroupSegment)(segment)) {
return acc;
}
return `${acc}/${segment}`;
}, '') || '/';
}
function extractPathFromFlightRouterState(flightRouterState) {
const segment = Array.isArray(flightRouterState[0]) ? flightRouterState[0][1] : flightRouterState[0];
if (segment === _segment.DEFAULT_SEGMENT_KEY || _interceptionroutes.INTERCEPTION_ROUTE_MARKERS.some((m)=>segment.startsWith(m))) return undefined;
if (segment.startsWith(_segment.PAGE_SEGMENT_KEY)) return '';
const segments = [
segmentToPathname(segment)
];
const parallelRoutes = flightRouterState[1] ?? {};
const childrenPath = parallelRoutes.children ? extractPathFromFlightRouterState(parallelRoutes.children) : undefined;
if (childrenPath !== undefined) {
segments.push(childrenPath);
} else {
for (const [key, value] of Object.entries(parallelRoutes)){
if (key === 'children') continue;
const childPath = extractPathFromFlightRouterState(value);
if (childPath !== undefined) {
segments.push(childPath);
}
}
}
return normalizeSegments(segments);
}
function computeChangedPathImpl(treeA, treeB) {
const [segmentA, parallelRoutesA] = treeA;
const [segmentB, parallelRoutesB] = treeB;
const normalizedSegmentA = segmentToPathname(segmentA);
const normalizedSegmentB = segmentToPathname(segmentB);
if (_interceptionroutes.INTERCEPTION_ROUTE_MARKERS.some((m)=>normalizedSegmentA.startsWith(m) || normalizedSegmentB.startsWith(m))) {
return '';
}
if (!(0, _matchsegments.matchSegment)(segmentA, segmentB)) {
// once we find where the tree changed, we compute the rest of the path by traversing the tree
return extractPathFromFlightRouterState(treeB) ?? '';
}
for(const parallelRouterKey in parallelRoutesA){
if (parallelRoutesB[parallelRouterKey]) {
const changedPath = computeChangedPathImpl(parallelRoutesA[parallelRouterKey], parallelRoutesB[parallelRouterKey]);
if (changedPath !== null) {
return `${segmentToPathname(segmentB)}/${changedPath}`;
}
}
}
return null;
}
function computeChangedPath(treeA, treeB) {
const changedPath = computeChangedPathImpl(treeA, treeB);
if (changedPath == null || changedPath === '/') {
return changedPath;
}
// lightweight normalization to remove route groups
return normalizeSegments(changedPath.split('/'));
}
function getSelectedParams(currentTree, params = {}) {
const parallelRoutes = currentTree[1];
for (const parallelRoute of Object.values(parallelRoutes)){
const segment = parallelRoute[0];
const isDynamicParameter = Array.isArray(segment);
const segmentValue = isDynamicParameter ? segment[1] : segment;
if (!segmentValue || segmentValue.startsWith(_segment.PAGE_SEGMENT_KEY)) continue;
// Ensure catchAll and optional catchall are turned into an array
const isCatchAll = isDynamicParameter && (segment[2] === 'c' || segment[2] === 'oc');
if (isCatchAll) {
params[segment[0]] = segment[1].split('/');
} else if (isDynamicParameter) {
params[segment[0]] = segment[1];
}
params = getSelectedParams(parallelRoute, params);
}
return params;
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=compute-changed-path.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
export declare function createHrefFromUrl(url: Pick<URL, 'pathname' | 'search' | 'hash'>, includeHash?: boolean): string;
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "createHrefFromUrl", {
enumerable: true,
get: function() {
return createHrefFromUrl;
}
});
function createHrefFromUrl(url, includeHash = true) {
return url.pathname + url.search + (includeHash ? url.hash : '');
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=create-href-from-url.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/create-href-from-url.ts"],"sourcesContent":["export function createHrefFromUrl(\n url: Pick<URL, 'pathname' | 'search' | 'hash'>,\n includeHash: boolean = true\n): string {\n return url.pathname + url.search + (includeHash ? url.hash : '')\n}\n"],"names":["createHrefFromUrl","url","includeHash","pathname","search","hash"],"mappings":";;;;+BAAgBA;;;eAAAA;;;AAAT,SAASA,kBACdC,GAA8C,EAC9CC,cAAuB,IAAI;IAE3B,OAAOD,IAAIE,QAAQ,GAAGF,IAAIG,MAAM,GAAIF,CAAAA,cAAcD,IAAII,IAAI,GAAG,EAAC;AAChE","ignoreList":[0]}
@@ -0,0 +1,10 @@
import type { FlightDataPath } from '../../../shared/lib/app-router-types';
import type { AppRouterState } from './router-reducer-types';
export interface InitialRouterStateParameters {
navigatedAt: number;
initialCanonicalUrlParts: string[];
initialRenderedSearch: string;
initialFlightData: FlightDataPath[];
location: Location | null;
}
export declare function createInitialRouterState({ navigatedAt, initialFlightData, initialCanonicalUrlParts, initialRenderedSearch, location, }: InitialRouterStateParameters): AppRouterState;
@@ -0,0 +1,59 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "createInitialRouterState", {
enumerable: true,
get: function() {
return createInitialRouterState;
}
});
const _createhreffromurl = require("./create-href-from-url");
const _computechangedpath = require("./compute-changed-path");
const _flightdatahelpers = require("../../flight-data-helpers");
const _pprnavigations = require("./ppr-navigations");
function createInitialRouterState({ navigatedAt, initialFlightData, initialCanonicalUrlParts, initialRenderedSearch, location }) {
// When initialized on the server, the canonical URL is provided as an array of parts.
// This is to ensure that when the RSC payload streamed to the client, crawlers don't interpret it
// as a URL that should be crawled.
const initialCanonicalUrl = initialCanonicalUrlParts.join('/');
const normalizedFlightData = (0, _flightdatahelpers.getFlightDataPartsFromPath)(initialFlightData[0]);
const { tree: initialTree, seedData: initialSeedData, head: initialHead } = normalizedFlightData;
// For the SSR render, seed data should always be available (we only send back a `null` response
// in the case of a `loading` segment, pre-PPR.)
const canonicalUrl = // location.href is read as the initial value for canonicalUrl in the browser
// This is safe to do as canonicalUrl can't be rendered, it's only used to control the history updates in the useEffect further down in this file.
location ? (0, _createhreffromurl.createHrefFromUrl)(location) : initialCanonicalUrl;
const initialState = {
tree: initialTree,
cache: (0, _pprnavigations.createInitialCacheNodeForHydration)(navigatedAt, initialTree, initialSeedData, initialHead),
pushRef: {
pendingPush: false,
mpaNavigation: false,
// First render needs to preserve the previous window.history.state
// to avoid it being overwritten on navigation back/forward with MPA Navigation.
preserveCustomHistoryState: true
},
focusAndScrollRef: {
apply: false,
onlyHashChange: false,
hashFragment: null,
segmentPaths: []
},
canonicalUrl,
renderedSearch: initialRenderedSearch,
nextUrl: // the || operator is intentional, the pathname can be an empty string
((0, _computechangedpath.extractPathFromFlightRouterState)(initialTree) || location?.pathname) ?? null,
previousNextUrl: null,
debugInfo: null
};
return initialState;
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=create-initial-router-state.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/create-initial-router-state.ts"],"sourcesContent":["import type { FlightDataPath } from '../../../shared/lib/app-router-types'\n\nimport { createHrefFromUrl } from './create-href-from-url'\nimport { extractPathFromFlightRouterState } from './compute-changed-path'\n\nimport type { AppRouterState } from './router-reducer-types'\nimport { getFlightDataPartsFromPath } from '../../flight-data-helpers'\nimport { createInitialCacheNodeForHydration } from './ppr-navigations'\n\nexport interface InitialRouterStateParameters {\n navigatedAt: number\n initialCanonicalUrlParts: string[]\n initialRenderedSearch: string\n initialFlightData: FlightDataPath[]\n location: Location | null\n}\n\nexport function createInitialRouterState({\n navigatedAt,\n initialFlightData,\n initialCanonicalUrlParts,\n initialRenderedSearch,\n location,\n}: InitialRouterStateParameters): AppRouterState {\n // When initialized on the server, the canonical URL is provided as an array of parts.\n // This is to ensure that when the RSC payload streamed to the client, crawlers don't interpret it\n // as a URL that should be crawled.\n const initialCanonicalUrl = initialCanonicalUrlParts.join('/')\n\n const normalizedFlightData = getFlightDataPartsFromPath(initialFlightData[0])\n const {\n tree: initialTree,\n seedData: initialSeedData,\n head: initialHead,\n } = normalizedFlightData\n // For the SSR render, seed data should always be available (we only send back a `null` response\n // in the case of a `loading` segment, pre-PPR.)\n\n const canonicalUrl =\n // location.href is read as the initial value for canonicalUrl in the browser\n // This is safe to do as canonicalUrl can't be rendered, it's only used to control the history updates in the useEffect further down in this file.\n location\n ? // window.location does not have the same type as URL but has all the fields createHrefFromUrl needs.\n createHrefFromUrl(location)\n : initialCanonicalUrl\n\n const initialState = {\n tree: initialTree,\n cache: createInitialCacheNodeForHydration(\n navigatedAt,\n initialTree,\n initialSeedData,\n initialHead\n ),\n pushRef: {\n pendingPush: false,\n mpaNavigation: false,\n // First render needs to preserve the previous window.history.state\n // to avoid it being overwritten on navigation back/forward with MPA Navigation.\n preserveCustomHistoryState: true,\n },\n focusAndScrollRef: {\n apply: false,\n onlyHashChange: false,\n hashFragment: null,\n segmentPaths: [],\n },\n canonicalUrl,\n renderedSearch: initialRenderedSearch,\n nextUrl:\n // the || operator is intentional, the pathname can be an empty string\n (extractPathFromFlightRouterState(initialTree) || location?.pathname) ??\n null,\n previousNextUrl: null,\n debugInfo: null,\n }\n\n return initialState\n}\n"],"names":["createInitialRouterState","navigatedAt","initialFlightData","initialCanonicalUrlParts","initialRenderedSearch","location","initialCanonicalUrl","join","normalizedFlightData","getFlightDataPartsFromPath","tree","initialTree","seedData","initialSeedData","head","initialHead","canonicalUrl","createHrefFromUrl","initialState","cache","createInitialCacheNodeForHydration","pushRef","pendingPush","mpaNavigation","preserveCustomHistoryState","focusAndScrollRef","apply","onlyHashChange","hashFragment","segmentPaths","renderedSearch","nextUrl","extractPathFromFlightRouterState","pathname","previousNextUrl","debugInfo"],"mappings":";;;;+BAiBgBA;;;eAAAA;;;mCAfkB;oCACe;mCAGN;gCACQ;AAU5C,SAASA,yBAAyB,EACvCC,WAAW,EACXC,iBAAiB,EACjBC,wBAAwB,EACxBC,qBAAqB,EACrBC,QAAQ,EACqB;IAC7B,sFAAsF;IACtF,kGAAkG;IAClG,mCAAmC;IACnC,MAAMC,sBAAsBH,yBAAyBI,IAAI,CAAC;IAE1D,MAAMC,uBAAuBC,IAAAA,6CAA0B,EAACP,iBAAiB,CAAC,EAAE;IAC5E,MAAM,EACJQ,MAAMC,WAAW,EACjBC,UAAUC,eAAe,EACzBC,MAAMC,WAAW,EAClB,GAAGP;IACJ,gGAAgG;IAChG,gDAAgD;IAEhD,MAAMQ,eACJ,6EAA6E;IAC7E,kJAAkJ;IAClJX,WAEIY,IAAAA,oCAAiB,EAACZ,YAClBC;IAEN,MAAMY,eAAe;QACnBR,MAAMC;QACNQ,OAAOC,IAAAA,kDAAkC,EACvCnB,aACAU,aACAE,iBACAE;QAEFM,SAAS;YACPC,aAAa;YACbC,eAAe;YACf,mEAAmE;YACnE,gFAAgF;YAChFC,4BAA4B;QAC9B;QACAC,mBAAmB;YACjBC,OAAO;YACPC,gBAAgB;YAChBC,cAAc;YACdC,cAAc,EAAE;QAClB;QACAb;QACAc,gBAAgB1B;QAChB2B,SAEE,AADA,sEAAsE;QACrEC,CAAAA,IAAAA,oDAAgC,EAACrB,gBAAgBN,UAAU4B,QAAO,KACnE;QACFC,iBAAiB;QACjBC,WAAW;IACb;IAEA,OAAOjB;AACT","ignoreList":[0]}
@@ -0,0 +1,2 @@
import type { Segment } from '../../../shared/lib/app-router-types';
export declare function createRouterCacheKey(segment: Segment, withoutSearchParameters?: boolean): string;
@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "createRouterCacheKey", {
enumerable: true,
get: function() {
return createRouterCacheKey;
}
});
const _segment = require("../../../shared/lib/segment");
function createRouterCacheKey(segment, withoutSearchParameters = false) {
// if the segment is an array, it means it's a dynamic segment
// for example, ['lang', 'en', 'd']. We need to convert it to a string to store it as a cache node key.
if (Array.isArray(segment)) {
return `${segment[0]}|${segment[1]}|${segment[2]}`;
}
// Page segments might have search parameters, ie __PAGE__?foo=bar
// When `withoutSearchParameters` is true, we only want to return the page segment
if (withoutSearchParameters && segment.startsWith(_segment.PAGE_SEGMENT_KEY)) {
return _segment.PAGE_SEGMENT_KEY;
}
return segment;
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=create-router-cache-key.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/create-router-cache-key.ts"],"sourcesContent":["import type { Segment } from '../../../shared/lib/app-router-types'\nimport { PAGE_SEGMENT_KEY } from '../../../shared/lib/segment'\n\nexport function createRouterCacheKey(\n segment: Segment,\n withoutSearchParameters: boolean = false\n) {\n // if the segment is an array, it means it's a dynamic segment\n // for example, ['lang', 'en', 'd']. We need to convert it to a string to store it as a cache node key.\n if (Array.isArray(segment)) {\n return `${segment[0]}|${segment[1]}|${segment[2]}`\n }\n\n // Page segments might have search parameters, ie __PAGE__?foo=bar\n // When `withoutSearchParameters` is true, we only want to return the page segment\n if (withoutSearchParameters && segment.startsWith(PAGE_SEGMENT_KEY)) {\n return PAGE_SEGMENT_KEY\n }\n\n return segment\n}\n"],"names":["createRouterCacheKey","segment","withoutSearchParameters","Array","isArray","startsWith","PAGE_SEGMENT_KEY"],"mappings":";;;;+BAGgBA;;;eAAAA;;;yBAFiB;AAE1B,SAASA,qBACdC,OAAgB,EAChBC,0BAAmC,KAAK;IAExC,8DAA8D;IAC9D,uGAAuG;IACvG,IAAIC,MAAMC,OAAO,CAACH,UAAU;QAC1B,OAAO,GAAGA,OAAO,CAAC,EAAE,CAAC,CAAC,EAAEA,OAAO,CAAC,EAAE,CAAC,CAAC,EAAEA,OAAO,CAAC,EAAE,EAAE;IACpD;IAEA,kEAAkE;IAClE,kFAAkF;IAClF,IAAIC,2BAA2BD,QAAQI,UAAU,CAACC,yBAAgB,GAAG;QACnE,OAAOA,yBAAgB;IACzB;IAEA,OAAOL;AACT","ignoreList":[0]}
@@ -0,0 +1,52 @@
import type { FlightRouterState } from '../../../shared/lib/app-router-types';
import { type NEXT_ROUTER_PREFETCH_HEADER, type NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL, RSC_HEADER, NEXT_HMR_REFRESH_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER } from '../app-router-headers';
import { type NormalizedFlightData } from '../../flight-data-helpers';
import type { NormalizedSearch } from '../segment-cache/cache-key';
export interface FetchServerResponseOptions {
readonly flightRouterState: FlightRouterState;
readonly nextUrl: string | null;
readonly isHmrRefresh?: boolean;
}
type SpaFetchServerResponseResult = {
flightData: NormalizedFlightData[];
canonicalUrl: URL;
renderedSearch: NormalizedSearch;
couldBeIntercepted: boolean;
prerendered: boolean;
postponed: boolean;
staleTime: number;
debugInfo: Array<any> | null;
};
type MpaFetchServerResponseResult = string;
export type FetchServerResponseResult = MpaFetchServerResponseResult | SpaFetchServerResponseResult;
export type RequestHeaders = {
[RSC_HEADER]?: '1';
[NEXT_ROUTER_STATE_TREE_HEADER]?: string;
[NEXT_URL]?: string;
[NEXT_ROUTER_PREFETCH_HEADER]?: '1' | '2';
[NEXT_ROUTER_SEGMENT_PREFETCH_HEADER]?: string;
'x-deployment-id'?: string;
[NEXT_HMR_REFRESH_HEADER]?: '1';
'Next-Test-Fetch-Priority'?: RequestInit['priority'];
[NEXT_HTML_REQUEST_ID_HEADER]?: string;
[NEXT_REQUEST_ID_HEADER]?: string;
};
/**
* Fetch the flight data for the provided url. Takes in the current router state
* to decide what to render server-side.
*/
export declare function fetchServerResponse(url: URL, options: FetchServerResponseOptions): Promise<FetchServerResponseResult>;
export type RSCResponse<T> = {
ok: boolean;
redirected: boolean;
headers: Headers;
body: ReadableStream<Uint8Array> | null;
status: number;
url: string;
flightResponse: (Promise<T> & {
_debugInfo?: Array<any>;
}) | null;
};
export declare function createFetch<T>(url: URL, headers: RequestHeaders, fetchPriority: 'auto' | 'high' | 'low' | null, shouldImmediatelyDecode: boolean, signal?: AbortSignal): Promise<RSCResponse<T>>;
export declare function createFromNextReadableStream<T>(flightStream: ReadableStream<Uint8Array>, requestHeaders: RequestHeaders): Promise<T>;
export {};
@@ -0,0 +1,340 @@
'use client';
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
createFetch: null,
createFromNextReadableStream: null,
fetchServerResponse: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
createFetch: function() {
return createFetch;
},
createFromNextReadableStream: function() {
return createFromNextReadableStream;
},
fetchServerResponse: function() {
return fetchServerResponse;
}
});
const _client = require("react-server-dom-webpack/client");
const _approuterheaders = require("../app-router-headers");
const _appcallserver = require("../../app-call-server");
const _appfindsourcemapurl = require("../../app-find-source-map-url");
const _flightdatahelpers = require("../../flight-data-helpers");
const _appbuildid = require("../../app-build-id");
const _setcachebustingsearchparam = require("./set-cache-busting-search-param");
const _routeparams = require("../../route-params");
const _deploymentid = require("../../../shared/lib/deployment-id");
const createFromReadableStream = _client.createFromReadableStream;
const createFromFetch = _client.createFromFetch;
let createDebugChannel;
if (process.env.NODE_ENV !== 'production' && process.env.__NEXT_REACT_DEBUG_CHANNEL) {
createDebugChannel = require('../../dev/debug-channel').createDebugChannel;
}
function doMpaNavigation(url) {
return (0, _routeparams.urlToUrlWithoutFlightMarker)(new URL(url, location.origin)).toString();
}
let isPageUnloading = false;
if (typeof window !== 'undefined') {
// Track when the page is unloading, e.g. due to reloading the page or
// performing hard navigations. This allows us to suppress error logging when
// the browser cancels in-flight requests during page unload.
window.addEventListener('pagehide', ()=>{
isPageUnloading = true;
});
// Reset the flag on pageshow, e.g. when navigating back and the JavaScript
// execution context is restored by the browser.
window.addEventListener('pageshow', ()=>{
isPageUnloading = false;
});
}
async function fetchServerResponse(url, options) {
const { flightRouterState, nextUrl } = options;
const headers = {
// Enable flight response
[_approuterheaders.RSC_HEADER]: '1',
// Provide the current router state
[_approuterheaders.NEXT_ROUTER_STATE_TREE_HEADER]: (0, _flightdatahelpers.prepareFlightRouterStateForRequest)(flightRouterState, options.isHmrRefresh)
};
if (process.env.NODE_ENV === 'development' && options.isHmrRefresh) {
headers[_approuterheaders.NEXT_HMR_REFRESH_HEADER] = '1';
}
if (nextUrl) {
headers[_approuterheaders.NEXT_URL] = nextUrl;
}
// In static export mode, we need to modify the URL to request the .txt file,
// but we should preserve the original URL for the canonical URL and error handling.
const originalUrl = url;
try {
if (process.env.NODE_ENV === 'production') {
if (process.env.__NEXT_CONFIG_OUTPUT === 'export') {
// In "output: export" mode, we can't rely on headers to distinguish
// between HTML and RSC requests. Instead, we append an extra prefix
// to the request.
url = new URL(url);
if (url.pathname.endsWith('/')) {
url.pathname += 'index.txt';
} else {
url.pathname += '.txt';
}
}
}
// Typically, during a navigation, we decode the response using Flight's
// `createFromFetch` API, which accepts a `fetch` promise.
// TODO: Remove this check once the old PPR flag is removed
const isLegacyPPR = process.env.__NEXT_PPR && !process.env.__NEXT_CACHE_COMPONENTS;
const shouldImmediatelyDecode = !isLegacyPPR;
const res = await createFetch(url, headers, 'auto', shouldImmediatelyDecode);
const responseUrl = (0, _routeparams.urlToUrlWithoutFlightMarker)(new URL(res.url));
const canonicalUrl = res.redirected ? responseUrl : originalUrl;
const contentType = res.headers.get('content-type') || '';
const interception = !!res.headers.get('vary')?.includes(_approuterheaders.NEXT_URL);
const postponed = !!res.headers.get(_approuterheaders.NEXT_DID_POSTPONE_HEADER);
const staleTimeHeaderSeconds = res.headers.get(_approuterheaders.NEXT_ROUTER_STALE_TIME_HEADER);
const staleTime = staleTimeHeaderSeconds !== null ? parseInt(staleTimeHeaderSeconds, 10) * 1000 : -1;
let isFlightResponse = contentType.startsWith(_approuterheaders.RSC_CONTENT_TYPE_HEADER);
if (process.env.NODE_ENV === 'production') {
if (process.env.__NEXT_CONFIG_OUTPUT === 'export') {
if (!isFlightResponse) {
isFlightResponse = contentType.startsWith('text/plain');
}
}
}
// If fetch returns something different than flight response handle it like a mpa navigation
// If the fetch was not 200, we also handle it like a mpa navigation
if (!isFlightResponse || !res.ok || !res.body) {
// in case the original URL came with a hash, preserve it before redirecting to the new URL
if (url.hash) {
responseUrl.hash = url.hash;
}
return doMpaNavigation(responseUrl.toString());
}
// We may navigate to a page that requires a different Webpack runtime.
// In prod, every page will have the same Webpack runtime.
// In dev, the Webpack runtime is minimal for each page.
// We need to ensure the Webpack runtime is updated before executing client-side JS of the new page.
// TODO: This needs to happen in the Flight Client.
// Or Webpack needs to include the runtime update in the Flight response as
// a blocking script.
if (process.env.NODE_ENV !== 'production' && !process.env.TURBOPACK) {
await require('../../dev/hot-reloader/app/hot-reloader-app').waitForWebpackRuntimeHotUpdate();
}
let flightResponsePromise = res.flightResponse;
if (flightResponsePromise === null) {
// Typically, `createFetch` would have already started decoding the
// Flight response. If it hasn't, though, we need to decode it now.
// TODO: This should only be reachable if legacy PPR is enabled (i.e. PPR
// without Cache Components). Remove this branch once legacy PPR
// is deleted.
const flightStream = postponed ? createUnclosingPrefetchStream(res.body) : res.body;
flightResponsePromise = createFromNextReadableStream(flightStream, headers);
}
const flightResponse = await flightResponsePromise;
if ((0, _appbuildid.getAppBuildId)() !== flightResponse.b) {
return doMpaNavigation(res.url);
}
const normalizedFlightData = (0, _flightdatahelpers.normalizeFlightData)(flightResponse.f);
if (typeof normalizedFlightData === 'string') {
return doMpaNavigation(normalizedFlightData);
}
return {
flightData: normalizedFlightData,
canonicalUrl: canonicalUrl,
renderedSearch: (0, _routeparams.getRenderedSearch)(res),
couldBeIntercepted: interception,
prerendered: flightResponse.S,
postponed,
staleTime,
debugInfo: flightResponsePromise._debugInfo ?? null
};
} catch (err) {
if (!isPageUnloading) {
console.error(`Failed to fetch RSC payload for ${originalUrl}. Falling back to browser navigation.`, err);
}
// If fetch fails handle it like a mpa navigation
// TODO-APP: Add a test for the case where a CORS request fails, e.g. external url redirect coming from the response.
// See https://github.com/vercel/next.js/issues/43605#issuecomment-1451617521 for a reproduction.
return originalUrl.toString();
}
}
async function createFetch(url, headers, fetchPriority, shouldImmediatelyDecode, signal) {
// TODO: In output: "export" mode, the headers do nothing. Omit them (and the
// cache busting search param) from the request so they're
// maximally cacheable.
if (process.env.__NEXT_TEST_MODE && fetchPriority !== null) {
headers['Next-Test-Fetch-Priority'] = fetchPriority;
}
const deploymentId = (0, _deploymentid.getDeploymentId)();
if (deploymentId) {
headers['x-deployment-id'] = deploymentId;
}
if (process.env.NODE_ENV !== 'production') {
if (self.__next_r) {
headers[_approuterheaders.NEXT_HTML_REQUEST_ID_HEADER] = self.__next_r;
}
// Create a new request ID for the server action request. The server uses
// this to tag debug information sent via WebSocket to the client, which
// then routes those chunks to the debug channel associated with this ID.
headers[_approuterheaders.NEXT_REQUEST_ID_HEADER] = crypto.getRandomValues(new Uint32Array(1))[0].toString(16);
}
const fetchOptions = {
// Backwards compat for older browsers. `same-origin` is the default in modern browsers.
credentials: 'same-origin',
headers,
priority: fetchPriority || undefined,
signal
};
// `fetchUrl` is slightly different from `url` because we add a cache-busting
// search param to it. This should not leak outside of this function, so we
// track them separately.
let fetchUrl = new URL(url);
(0, _setcachebustingsearchparam.setCacheBustingSearchParam)(fetchUrl, headers);
let fetchPromise = fetch(fetchUrl, fetchOptions);
// Immediately pass the fetch promise to the Flight client so that the debug
// info includes the latency from the client to the server. The internal timer
// in React starts as soon as `createFromFetch` is called.
//
// The only case where we don't do this is during a prefetch, because we have
// to do some extra processing of the response stream (see
// `createUnclosingPrefetchStream`). But this is fine, because a top-level
// prefetch response never blocks a navigation; if it hasn't already been
// written into the cache by the time the navigation happens, the router will
// go straight to a dynamic request.
let flightResponsePromise = shouldImmediatelyDecode ? createFromNextFetch(fetchPromise, headers) : null;
let browserResponse = await fetchPromise;
// If the server responds with a redirect (e.g. 307), and the redirected
// location does not contain the cache busting search param set in the
// original request, the response is likely invalid — when following the
// redirect, the browser forwards the request headers, but since the cache
// busting search param is missing, the server will reject the request due to
// a mismatch.
//
// Ideally, we would be able to intercept the redirect response and perform it
// manually, instead of letting the browser automatically follow it, but this
// is not allowed by the fetch API.
//
// So instead, we must "replay" the redirect by fetching the new location
// again, but this time we'll append the cache busting search param to prevent
// a mismatch.
//
// TODO: We can optimize Next.js's built-in middleware APIs by returning a
// custom status code, to prevent the browser from automatically following it.
//
// This does not affect Server Action-based redirects; those are encoded
// differently, as part of the Flight body. It only affects redirects that
// occur in a middleware or a third-party proxy.
let redirected = browserResponse.redirected;
if (process.env.__NEXT_CLIENT_VALIDATE_RSC_REQUEST_HEADERS) {
// This is to prevent a redirect loop. Same limit used by Chrome.
const MAX_REDIRECTS = 20;
for(let n = 0; n < MAX_REDIRECTS; n++){
if (!browserResponse.redirected) {
break;
}
const responseUrl = new URL(browserResponse.url, fetchUrl);
if (responseUrl.origin !== fetchUrl.origin) {
break;
}
if (responseUrl.searchParams.get(_approuterheaders.NEXT_RSC_UNION_QUERY) === fetchUrl.searchParams.get(_approuterheaders.NEXT_RSC_UNION_QUERY)) {
break;
}
// The RSC request was redirected. Assume the response is invalid.
//
// Append the cache busting search param to the redirected URL and
// fetch again.
// TODO: We should abort the previous request.
fetchUrl = new URL(responseUrl);
(0, _setcachebustingsearchparam.setCacheBustingSearchParam)(fetchUrl, headers);
fetchPromise = fetch(fetchUrl, fetchOptions);
flightResponsePromise = shouldImmediatelyDecode ? createFromNextFetch(fetchPromise, headers) : null;
browserResponse = await fetchPromise;
// We just performed a manual redirect, so this is now true.
redirected = true;
}
}
// Remove the cache busting search param from the response URL, to prevent it
// from leaking outside of this function.
const responseUrl = new URL(browserResponse.url, fetchUrl);
responseUrl.searchParams.delete(_approuterheaders.NEXT_RSC_UNION_QUERY);
const rscResponse = {
url: responseUrl.href,
// This is true if any redirects occurred, either automatically by the
// browser, or manually by us. So it's different from
// `browserResponse.redirected`, which only tells us whether the browser
// followed a redirect, and only for the last response in the chain.
redirected,
// These can be copied from the last browser response we received. We
// intentionally only expose the subset of fields that are actually used
// elsewhere in the codebase.
ok: browserResponse.ok,
headers: browserResponse.headers,
body: browserResponse.body,
status: browserResponse.status,
// This is the exact promise returned by `createFromFetch`. It contains
// debug information that we need to transfer to any derived promises that
// are later rendered by React.
flightResponse: flightResponsePromise
};
return rscResponse;
}
function createFromNextReadableStream(flightStream, requestHeaders) {
return createFromReadableStream(flightStream, {
callServer: _appcallserver.callServer,
findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,
debugChannel: createDebugChannel && createDebugChannel(requestHeaders)
});
}
function createFromNextFetch(promiseForResponse, requestHeaders) {
return createFromFetch(promiseForResponse, {
callServer: _appcallserver.callServer,
findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,
debugChannel: createDebugChannel && createDebugChannel(requestHeaders)
});
}
function createUnclosingPrefetchStream(originalFlightStream) {
// When PPR is enabled, prefetch streams may contain references that never
// resolve, because that's how we encode dynamic data access. In the decoded
// object returned by the Flight client, these are reified into hanging
// promises that suspend during render, which is effectively what we want.
// The UI resolves when it switches to the dynamic data stream
// (via useDeferredValue(dynamic, static)).
//
// However, the Flight implementation currently errors if the server closes
// the response before all the references are resolved. As a cheat to work
// around this, we wrap the original stream in a new stream that never closes,
// and therefore doesn't error.
const reader = originalFlightStream.getReader();
return new ReadableStream({
async pull (controller) {
while(true){
const { done, value } = await reader.read();
if (!done) {
// Pass to the target stream and keep consuming the Flight response
// from the server.
controller.enqueue(value);
continue;
}
// The server stream has closed. Exit, but intentionally do not close
// the target stream.
return;
}
}
});
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=fetch-server-response.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
import type { Mutable, ReadonlyReducerState, ReducerState } from './router-reducer-types';
export declare function handleMutable(state: ReadonlyReducerState, mutable: Mutable): ReducerState;
@@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "handleMutable", {
enumerable: true,
get: function() {
return handleMutable;
}
});
const _computechangedpath = require("./compute-changed-path");
function isNotUndefined(value) {
return typeof value !== 'undefined';
}
function handleMutable(state, mutable) {
// shouldScroll is true by default, can override to false.
const shouldScroll = mutable.shouldScroll ?? true;
let previousNextUrl = state.previousNextUrl;
let nextUrl = state.nextUrl;
if (isNotUndefined(mutable.patchedTree)) {
// If we received a patched tree, we need to compute the changed path.
const changedPath = (0, _computechangedpath.computeChangedPath)(state.tree, mutable.patchedTree);
if (changedPath) {
// If the tree changed, we need to update the nextUrl
previousNextUrl = nextUrl;
nextUrl = changedPath;
} else if (!nextUrl) {
// if the tree ends up being the same (ie, no changed path), and we don't have a nextUrl, then we should use the canonicalUrl
nextUrl = state.canonicalUrl;
}
// otherwise this will be a no-op and continue to use the existing nextUrl
}
return {
// Set href.
canonicalUrl: mutable.canonicalUrl ?? state.canonicalUrl,
renderedSearch: mutable.renderedSearch ?? state.renderedSearch,
pushRef: {
pendingPush: isNotUndefined(mutable.pendingPush) ? mutable.pendingPush : state.pushRef.pendingPush,
mpaNavigation: isNotUndefined(mutable.mpaNavigation) ? mutable.mpaNavigation : state.pushRef.mpaNavigation,
preserveCustomHistoryState: isNotUndefined(mutable.preserveCustomHistoryState) ? mutable.preserveCustomHistoryState : state.pushRef.preserveCustomHistoryState
},
// All navigation requires scroll and focus management to trigger.
focusAndScrollRef: {
apply: shouldScroll ? isNotUndefined(mutable?.scrollableSegments) ? true : state.focusAndScrollRef.apply : false,
onlyHashChange: mutable.onlyHashChange || false,
hashFragment: shouldScroll ? // #top is handled in layout-router.
mutable.hashFragment && mutable.hashFragment !== '' ? decodeURIComponent(mutable.hashFragment.slice(1)) : state.focusAndScrollRef.hashFragment : null,
segmentPaths: shouldScroll ? mutable?.scrollableSegments ?? state.focusAndScrollRef.segmentPaths : []
},
// Apply cache.
cache: mutable.cache ? mutable.cache : state.cache,
// Apply patched router state.
tree: isNotUndefined(mutable.patchedTree) ? mutable.patchedTree : state.tree,
nextUrl,
previousNextUrl: previousNextUrl,
debugInfo: mutable.collectedDebugInfo ?? null
};
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=handle-mutable.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
import type { FlightRouterState } from '../../../shared/lib/app-router-types';
export declare function isNavigatingToNewRootLayout(currentTree: FlightRouterState, nextTree: FlightRouterState): boolean;
@@ -0,0 +1,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "isNavigatingToNewRootLayout", {
enumerable: true,
get: function() {
return isNavigatingToNewRootLayout;
}
});
function isNavigatingToNewRootLayout(currentTree, nextTree) {
// Compare segments
const currentTreeSegment = currentTree[0];
const nextTreeSegment = nextTree[0];
// If any segment is different before we find the root layout, the root layout has changed.
// E.g. /same/(group1)/layout.js -> /same/(group2)/layout.js
// First segment is 'same' for both, keep looking. (group1) changed to (group2) before the root layout was found, it must have changed.
if (Array.isArray(currentTreeSegment) && Array.isArray(nextTreeSegment)) {
// Compare dynamic param name and type but ignore the value, different values would not affect the current root layout
// /[name] - /slug1 and /slug2, both values (slug1 & slug2) still has the same layout /[name]/layout.js
if (currentTreeSegment[0] !== nextTreeSegment[0] || currentTreeSegment[2] !== nextTreeSegment[2]) {
return true;
}
} else if (currentTreeSegment !== nextTreeSegment) {
return true;
}
// Current tree root layout found
if (currentTree[4]) {
// If the next tree doesn't have the root layout flag, it must have changed.
return !nextTree[4];
}
// Current tree didn't have its root layout here, must have changed.
if (nextTree[4]) {
return true;
}
// We can't assume it's `parallelRoutes.children` here in case the root layout is `app/@something/layout.js`
// But it's not possible to be more than one parallelRoutes before the root layout is found
// TODO-APP: change to traverse all parallel routes
const currentTreeChild = Object.values(currentTree[1])[0];
const nextTreeChild = Object.values(nextTree[1])[0];
if (!currentTreeChild || !nextTreeChild) return true;
return isNavigatingToNewRootLayout(currentTreeChild, nextTreeChild);
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=is-navigating-to-new-root-layout.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/is-navigating-to-new-root-layout.ts"],"sourcesContent":["import type { FlightRouterState } from '../../../shared/lib/app-router-types'\n\nexport function isNavigatingToNewRootLayout(\n currentTree: FlightRouterState,\n nextTree: FlightRouterState\n): boolean {\n // Compare segments\n const currentTreeSegment = currentTree[0]\n const nextTreeSegment = nextTree[0]\n\n // If any segment is different before we find the root layout, the root layout has changed.\n // E.g. /same/(group1)/layout.js -> /same/(group2)/layout.js\n // First segment is 'same' for both, keep looking. (group1) changed to (group2) before the root layout was found, it must have changed.\n if (Array.isArray(currentTreeSegment) && Array.isArray(nextTreeSegment)) {\n // Compare dynamic param name and type but ignore the value, different values would not affect the current root layout\n // /[name] - /slug1 and /slug2, both values (slug1 & slug2) still has the same layout /[name]/layout.js\n if (\n currentTreeSegment[0] !== nextTreeSegment[0] ||\n currentTreeSegment[2] !== nextTreeSegment[2]\n ) {\n return true\n }\n } else if (currentTreeSegment !== nextTreeSegment) {\n return true\n }\n\n // Current tree root layout found\n if (currentTree[4]) {\n // If the next tree doesn't have the root layout flag, it must have changed.\n return !nextTree[4]\n }\n // Current tree didn't have its root layout here, must have changed.\n if (nextTree[4]) {\n return true\n }\n // We can't assume it's `parallelRoutes.children` here in case the root layout is `app/@something/layout.js`\n // But it's not possible to be more than one parallelRoutes before the root layout is found\n // TODO-APP: change to traverse all parallel routes\n const currentTreeChild = Object.values(currentTree[1])[0]\n const nextTreeChild = Object.values(nextTree[1])[0]\n if (!currentTreeChild || !nextTreeChild) return true\n return isNavigatingToNewRootLayout(currentTreeChild, nextTreeChild)\n}\n"],"names":["isNavigatingToNewRootLayout","currentTree","nextTree","currentTreeSegment","nextTreeSegment","Array","isArray","currentTreeChild","Object","values","nextTreeChild"],"mappings":";;;;+BAEgBA;;;eAAAA;;;AAAT,SAASA,4BACdC,WAA8B,EAC9BC,QAA2B;IAE3B,mBAAmB;IACnB,MAAMC,qBAAqBF,WAAW,CAAC,EAAE;IACzC,MAAMG,kBAAkBF,QAAQ,CAAC,EAAE;IAEnC,2FAA2F;IAC3F,4DAA4D;IAC5D,uIAAuI;IACvI,IAAIG,MAAMC,OAAO,CAACH,uBAAuBE,MAAMC,OAAO,CAACF,kBAAkB;QACvE,sHAAsH;QACtH,uGAAuG;QACvG,IACED,kBAAkB,CAAC,EAAE,KAAKC,eAAe,CAAC,EAAE,IAC5CD,kBAAkB,CAAC,EAAE,KAAKC,eAAe,CAAC,EAAE,EAC5C;YACA,OAAO;QACT;IACF,OAAO,IAAID,uBAAuBC,iBAAiB;QACjD,OAAO;IACT;IAEA,iCAAiC;IACjC,IAAIH,WAAW,CAAC,EAAE,EAAE;QAClB,4EAA4E;QAC5E,OAAO,CAACC,QAAQ,CAAC,EAAE;IACrB;IACA,oEAAoE;IACpE,IAAIA,QAAQ,CAAC,EAAE,EAAE;QACf,OAAO;IACT;IACA,4GAA4G;IAC5G,2FAA2F;IAC3F,mDAAmD;IACnD,MAAMK,mBAAmBC,OAAOC,MAAM,CAACR,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE;IACzD,MAAMS,gBAAgBF,OAAOC,MAAM,CAACP,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE;IACnD,IAAI,CAACK,oBAAoB,CAACG,eAAe,OAAO;IAChD,OAAOV,4BAA4BO,kBAAkBG;AACvD","ignoreList":[0]}
@@ -0,0 +1,56 @@
import type { CacheNodeSeedData, FlightRouterState, FlightSegmentPath } from '../../../shared/lib/app-router-types';
import type { CacheNode } from '../../../shared/lib/app-router-types';
import type { HeadData } from '../../../shared/lib/app-router-types';
export type NavigationTask = {
status: NavigationTaskStatus;
route: FlightRouterState;
node: CacheNode;
dynamicRequestTree: FlightRouterState | null;
refreshUrl: string | null;
children: Map<string, NavigationTask> | null;
};
export declare const enum FreshnessPolicy {
Default = 0,
Hydration = 1,
HistoryTraversal = 2,
RefreshAll = 3,
HMRRefresh = 4
}
declare const enum NavigationTaskStatus {
Pending = 0,
Fulfilled = 1,
Rejected = 2
}
export type NavigationRequestAccumulation = {
scrollableSegments: Array<FlightSegmentPath> | null;
separateRefreshUrls: Set<string> | null;
};
export declare function createInitialCacheNodeForHydration(navigatedAt: number, initialTree: FlightRouterState, seedData: CacheNodeSeedData | null, seedHead: HeadData): CacheNode;
export declare function startPPRNavigation(navigatedAt: number, oldUrl: URL, oldCacheNode: CacheNode | null, oldRouterState: FlightRouterState, newRouterState: FlightRouterState, freshness: FreshnessPolicy, seedData: CacheNodeSeedData | null, seedHead: HeadData | null, prefetchData: CacheNodeSeedData | null, prefetchHead: HeadData | null, isPrefetchHeadPartial: boolean, isSamePageNavigation: boolean, accumulation: NavigationRequestAccumulation): NavigationTask | null;
export declare function spawnDynamicRequests(task: NavigationTask, primaryUrl: URL, nextUrl: string | null, freshnessPolicy: FreshnessPolicy, accumulation: NavigationRequestAccumulation): void;
type PendingDeferredRsc<T> = Promise<T> & {
status: 'pending';
resolve: (value: T, debugInfo: Array<any> | null) => void;
reject: (error: any, debugInfo: Array<any> | null) => void;
tag: Symbol;
_debugInfo: Array<any>;
};
type FulfilledDeferredRsc<T> = Promise<T> & {
status: 'fulfilled';
value: T;
resolve: (value: T, debugInfo: Array<any> | null) => void;
reject: (error: any, debugInfo: Array<any> | null) => void;
tag: Symbol;
_debugInfo: Array<any>;
};
type RejectedDeferredRsc<T> = Promise<T> & {
status: 'rejected';
reason: any;
resolve: (value: T, debugInfo: Array<any> | null) => void;
reject: (error: any, debugInfo: Array<any> | null) => void;
tag: Symbol;
_debugInfo: Array<any>;
};
type DeferredRsc<T extends React.ReactNode = React.ReactNode> = PendingDeferredRsc<T> | FulfilledDeferredRsc<T> | RejectedDeferredRsc<T>;
export declare function isDeferredRsc(value: any): value is DeferredRsc;
export {};
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
import type { FlightRouterState, CacheNode } from '../../../../shared/lib/app-router-types';
export declare function findHeadInCache(cache: CacheNode, parallelRoutes: FlightRouterState[1]): [CacheNode, string, string] | null;
@@ -0,0 +1,64 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "findHeadInCache", {
enumerable: true,
get: function() {
return findHeadInCache;
}
});
const _segment = require("../../../../shared/lib/segment");
const _createroutercachekey = require("../create-router-cache-key");
function findHeadInCache(cache, parallelRoutes) {
return findHeadInCacheImpl(cache, parallelRoutes, '', '');
}
function findHeadInCacheImpl(cache, parallelRoutes, keyPrefix, keyPrefixWithoutSearchParams) {
const isLastItem = Object.keys(parallelRoutes).length === 0;
if (isLastItem) {
// Returns the entire Cache Node of the segment whose head we will render.
return [
cache,
keyPrefix,
keyPrefixWithoutSearchParams
];
}
// First try the 'children' parallel route if it exists
// when starting from the "root", this corresponds with the main page component
const parallelRoutesKeys = Object.keys(parallelRoutes).filter((key)=>key !== 'children');
// if we are at the root, we need to check the children slot first
if ('children' in parallelRoutes) {
parallelRoutesKeys.unshift('children');
}
for (const key of parallelRoutesKeys){
const [segment, childParallelRoutes] = parallelRoutes[key];
// If the parallel is not matched and using the default segment,
// skip searching the head from it.
if (segment === _segment.DEFAULT_SEGMENT_KEY) {
continue;
}
const childSegmentMap = cache.parallelRoutes.get(key);
if (!childSegmentMap) {
continue;
}
const cacheKey = (0, _createroutercachekey.createRouterCacheKey)(segment);
const cacheKeyWithoutSearchParams = (0, _createroutercachekey.createRouterCacheKey)(segment, true);
const cacheNode = childSegmentMap.get(cacheKey);
if (!cacheNode) {
continue;
}
const item = findHeadInCacheImpl(cacheNode, childParallelRoutes, keyPrefix + '/' + cacheKey, keyPrefix + '/' + cacheKeyWithoutSearchParams);
if (item) {
return item;
}
}
return null;
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=find-head-in-cache.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/find-head-in-cache.ts"],"sourcesContent":["import type {\n FlightRouterState,\n CacheNode,\n} from '../../../../shared/lib/app-router-types'\nimport { DEFAULT_SEGMENT_KEY } from '../../../../shared/lib/segment'\nimport { createRouterCacheKey } from '../create-router-cache-key'\n\nexport function findHeadInCache(\n cache: CacheNode,\n parallelRoutes: FlightRouterState[1]\n): [CacheNode, string, string] | null {\n return findHeadInCacheImpl(cache, parallelRoutes, '', '')\n}\n\nfunction findHeadInCacheImpl(\n cache: CacheNode,\n parallelRoutes: FlightRouterState[1],\n keyPrefix: string,\n keyPrefixWithoutSearchParams: string\n): [CacheNode, string, string] | null {\n const isLastItem = Object.keys(parallelRoutes).length === 0\n if (isLastItem) {\n // Returns the entire Cache Node of the segment whose head we will render.\n return [cache, keyPrefix, keyPrefixWithoutSearchParams]\n }\n\n // First try the 'children' parallel route if it exists\n // when starting from the \"root\", this corresponds with the main page component\n const parallelRoutesKeys = Object.keys(parallelRoutes).filter(\n (key) => key !== 'children'\n )\n\n // if we are at the root, we need to check the children slot first\n if ('children' in parallelRoutes) {\n parallelRoutesKeys.unshift('children')\n }\n\n for (const key of parallelRoutesKeys) {\n const [segment, childParallelRoutes] = parallelRoutes[key]\n // If the parallel is not matched and using the default segment,\n // skip searching the head from it.\n if (segment === DEFAULT_SEGMENT_KEY) {\n continue\n }\n const childSegmentMap = cache.parallelRoutes.get(key)\n if (!childSegmentMap) {\n continue\n }\n\n const cacheKey = createRouterCacheKey(segment)\n const cacheKeyWithoutSearchParams = createRouterCacheKey(segment, true)\n\n const cacheNode = childSegmentMap.get(cacheKey)\n if (!cacheNode) {\n continue\n }\n\n const item = findHeadInCacheImpl(\n cacheNode,\n childParallelRoutes,\n keyPrefix + '/' + cacheKey,\n keyPrefix + '/' + cacheKeyWithoutSearchParams\n )\n\n if (item) {\n return item\n }\n }\n\n return null\n}\n"],"names":["findHeadInCache","cache","parallelRoutes","findHeadInCacheImpl","keyPrefix","keyPrefixWithoutSearchParams","isLastItem","Object","keys","length","parallelRoutesKeys","filter","key","unshift","segment","childParallelRoutes","DEFAULT_SEGMENT_KEY","childSegmentMap","get","cacheKey","createRouterCacheKey","cacheKeyWithoutSearchParams","cacheNode","item"],"mappings":";;;;+BAOgBA;;;eAAAA;;;yBAHoB;sCACC;AAE9B,SAASA,gBACdC,KAAgB,EAChBC,cAAoC;IAEpC,OAAOC,oBAAoBF,OAAOC,gBAAgB,IAAI;AACxD;AAEA,SAASC,oBACPF,KAAgB,EAChBC,cAAoC,EACpCE,SAAiB,EACjBC,4BAAoC;IAEpC,MAAMC,aAAaC,OAAOC,IAAI,CAACN,gBAAgBO,MAAM,KAAK;IAC1D,IAAIH,YAAY;QACd,0EAA0E;QAC1E,OAAO;YAACL;YAAOG;YAAWC;SAA6B;IACzD;IAEA,uDAAuD;IACvD,+EAA+E;IAC/E,MAAMK,qBAAqBH,OAAOC,IAAI,CAACN,gBAAgBS,MAAM,CAC3D,CAACC,MAAQA,QAAQ;IAGnB,kEAAkE;IAClE,IAAI,cAAcV,gBAAgB;QAChCQ,mBAAmBG,OAAO,CAAC;IAC7B;IAEA,KAAK,MAAMD,OAAOF,mBAAoB;QACpC,MAAM,CAACI,SAASC,oBAAoB,GAAGb,cAAc,CAACU,IAAI;QAC1D,gEAAgE;QAChE,mCAAmC;QACnC,IAAIE,YAAYE,4BAAmB,EAAE;YACnC;QACF;QACA,MAAMC,kBAAkBhB,MAAMC,cAAc,CAACgB,GAAG,CAACN;QACjD,IAAI,CAACK,iBAAiB;YACpB;QACF;QAEA,MAAME,WAAWC,IAAAA,0CAAoB,EAACN;QACtC,MAAMO,8BAA8BD,IAAAA,0CAAoB,EAACN,SAAS;QAElE,MAAMQ,YAAYL,gBAAgBC,GAAG,CAACC;QACtC,IAAI,CAACG,WAAW;YACd;QACF;QAEA,MAAMC,OAAOpB,oBACXmB,WACAP,qBACAX,YAAY,MAAMe,UAClBf,YAAY,MAAMiB;QAGpB,IAAIE,MAAM;YACR,OAAOA;QACT;IACF;IAEA,OAAO;AACT","ignoreList":[0]}
@@ -0,0 +1,2 @@
import type { FlightRouterState } from '../../../../shared/lib/app-router-types';
export declare function hasInterceptionRouteInCurrentTree([segment, parallelRoutes,]: FlightRouterState): boolean;
@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "hasInterceptionRouteInCurrentTree", {
enumerable: true,
get: function() {
return hasInterceptionRouteInCurrentTree;
}
});
const _interceptionroutes = require("../../../../shared/lib/router/utils/interception-routes");
function hasInterceptionRouteInCurrentTree([segment, parallelRoutes]) {
// If we have a dynamic segment, it's marked as an interception route by the presence of the `i` suffix.
if (Array.isArray(segment) && (segment[2] === 'di(..)(..)' || segment[2] === 'ci(..)(..)' || segment[2] === 'di(.)' || segment[2] === 'ci(.)' || segment[2] === 'di(..)' || segment[2] === 'ci(..)' || segment[2] === 'di(...)' || segment[2] === 'ci(...)')) {
return true;
}
// If segment is not an array, apply the existing string-based check
if (typeof segment === 'string' && (0, _interceptionroutes.isInterceptionRouteAppPath)(segment)) {
return true;
}
// Iterate through parallelRoutes if they exist
if (parallelRoutes) {
for(const key in parallelRoutes){
if (hasInterceptionRouteInCurrentTree(parallelRoutes[key])) {
return true;
}
}
}
return false;
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=has-interception-route-in-current-tree.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/has-interception-route-in-current-tree.ts"],"sourcesContent":["import type { FlightRouterState } from '../../../../shared/lib/app-router-types'\nimport { isInterceptionRouteAppPath } from '../../../../shared/lib/router/utils/interception-routes'\n\nexport function hasInterceptionRouteInCurrentTree([\n segment,\n parallelRoutes,\n]: FlightRouterState): boolean {\n // If we have a dynamic segment, it's marked as an interception route by the presence of the `i` suffix.\n if (\n Array.isArray(segment) &&\n (segment[2] === 'di(..)(..)' ||\n segment[2] === 'ci(..)(..)' ||\n segment[2] === 'di(.)' ||\n segment[2] === 'ci(.)' ||\n segment[2] === 'di(..)' ||\n segment[2] === 'ci(..)' ||\n segment[2] === 'di(...)' ||\n segment[2] === 'ci(...)')\n ) {\n return true\n }\n\n // If segment is not an array, apply the existing string-based check\n if (typeof segment === 'string' && isInterceptionRouteAppPath(segment)) {\n return true\n }\n\n // Iterate through parallelRoutes if they exist\n if (parallelRoutes) {\n for (const key in parallelRoutes) {\n if (hasInterceptionRouteInCurrentTree(parallelRoutes[key])) {\n return true\n }\n }\n }\n\n return false\n}\n"],"names":["hasInterceptionRouteInCurrentTree","segment","parallelRoutes","Array","isArray","isInterceptionRouteAppPath","key"],"mappings":";;;;+BAGgBA;;;eAAAA;;;oCAF2B;AAEpC,SAASA,kCAAkC,CAChDC,SACAC,eACkB;IAClB,wGAAwG;IACxG,IACEC,MAAMC,OAAO,CAACH,YACbA,CAAAA,OAAO,CAAC,EAAE,KAAK,gBACdA,OAAO,CAAC,EAAE,KAAK,gBACfA,OAAO,CAAC,EAAE,KAAK,WACfA,OAAO,CAAC,EAAE,KAAK,WACfA,OAAO,CAAC,EAAE,KAAK,YACfA,OAAO,CAAC,EAAE,KAAK,YACfA,OAAO,CAAC,EAAE,KAAK,aACfA,OAAO,CAAC,EAAE,KAAK,SAAQ,GACzB;QACA,OAAO;IACT;IAEA,oEAAoE;IACpE,IAAI,OAAOA,YAAY,YAAYI,IAAAA,8CAA0B,EAACJ,UAAU;QACtE,OAAO;IACT;IAEA,+CAA+C;IAC/C,IAAIC,gBAAgB;QAClB,IAAK,MAAMI,OAAOJ,eAAgB;YAChC,IAAIF,kCAAkCE,cAAc,CAACI,IAAI,GAAG;gBAC1D,OAAO;YACT;QACF;IACF;IAEA,OAAO;AACT","ignoreList":[0]}
@@ -0,0 +1,2 @@
import type { ReadonlyReducerState, ReducerState } from '../router-reducer-types';
export declare function hmrRefreshReducer(state: ReadonlyReducerState): ReducerState;
@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "hmrRefreshReducer", {
enumerable: true,
get: function() {
return hmrRefreshReducer;
}
});
const _refreshreducer = require("./refresh-reducer");
const _pprnavigations = require("../ppr-navigations");
function hmrRefreshReducer(state) {
return (0, _refreshreducer.refreshDynamicData)(state, _pprnavigations.FreshnessPolicy.HMRRefresh);
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=hmr-refresh-reducer.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/hmr-refresh-reducer.ts"],"sourcesContent":["import type {\n ReadonlyReducerState,\n ReducerState,\n} from '../router-reducer-types'\nimport { refreshDynamicData } from './refresh-reducer'\nimport { FreshnessPolicy } from '../ppr-navigations'\n\nexport function hmrRefreshReducer(state: ReadonlyReducerState): ReducerState {\n return refreshDynamicData(state, FreshnessPolicy.HMRRefresh)\n}\n"],"names":["hmrRefreshReducer","state","refreshDynamicData","FreshnessPolicy","HMRRefresh"],"mappings":";;;;+BAOgBA;;;eAAAA;;;gCAHmB;gCACH;AAEzB,SAASA,kBAAkBC,KAA2B;IAC3D,OAAOC,IAAAA,kCAAkB,EAACD,OAAOE,+BAAe,CAACC,UAAU;AAC7D","ignoreList":[0]}
@@ -0,0 +1,9 @@
import type { FlightRouterState, FlightSegmentPath } from '../../../../shared/lib/app-router-types';
import type { Mutable, NavigateAction, ReadonlyReducerState, ReducerState } from '../router-reducer-types';
import { type NavigationResult } from '../../segment-cache/navigation';
export declare const DYNAMIC_STALETIME_MS: number;
export declare const STATIC_STALETIME_MS: number;
export declare function handleExternalUrl(state: ReadonlyReducerState, mutable: Mutable, url: string, pendingPush: boolean): ReducerState;
export declare function generateSegmentsFromPatch(flightRouterPatch: FlightRouterState): FlightSegmentPath[];
export declare function handleNavigationResult(url: URL, state: ReadonlyReducerState, mutable: Mutable, pendingPush: boolean, result: NavigationResult): ReducerState;
export declare function navigateReducer(state: ReadonlyReducerState, action: NavigateAction): ReducerState;
@@ -0,0 +1,168 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
DYNAMIC_STALETIME_MS: null,
STATIC_STALETIME_MS: null,
generateSegmentsFromPatch: null,
handleExternalUrl: null,
handleNavigationResult: null,
navigateReducer: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
DYNAMIC_STALETIME_MS: function() {
return DYNAMIC_STALETIME_MS;
},
STATIC_STALETIME_MS: function() {
return STATIC_STALETIME_MS;
},
generateSegmentsFromPatch: function() {
return generateSegmentsFromPatch;
},
handleExternalUrl: function() {
return handleExternalUrl;
},
handleNavigationResult: function() {
return handleNavigationResult;
},
navigateReducer: function() {
return navigateReducer;
}
});
const _createhreffromurl = require("../create-href-from-url");
const _handlemutable = require("../handle-mutable");
const _navigation = require("../../segment-cache/navigation");
const _types = require("../../segment-cache/types");
const _cache = require("../../segment-cache/cache");
const _pprnavigations = require("../ppr-navigations");
const DYNAMIC_STALETIME_MS = Number(process.env.__NEXT_CLIENT_ROUTER_DYNAMIC_STALETIME) * 1000;
const STATIC_STALETIME_MS = (0, _cache.getStaleTimeMs)(Number(process.env.__NEXT_CLIENT_ROUTER_STATIC_STALETIME));
function handleExternalUrl(state, mutable, url, pendingPush) {
mutable.mpaNavigation = true;
mutable.canonicalUrl = url;
mutable.pendingPush = pendingPush;
mutable.scrollableSegments = undefined;
return (0, _handlemutable.handleMutable)(state, mutable);
}
function generateSegmentsFromPatch(flightRouterPatch) {
const segments = [];
const [segment, parallelRoutes] = flightRouterPatch;
if (Object.keys(parallelRoutes).length === 0) {
return [
[
segment
]
];
}
for (const [parallelRouteKey, parallelRoute] of Object.entries(parallelRoutes)){
for (const childSegment of generateSegmentsFromPatch(parallelRoute)){
// If the segment is empty, it means we are at the root of the tree
if (segment === '') {
segments.push([
parallelRouteKey,
...childSegment
]);
} else {
segments.push([
segment,
parallelRouteKey,
...childSegment
]);
}
}
}
return segments;
}
function handleNavigationResult(url, state, mutable, pendingPush, result) {
switch(result.tag){
case _types.NavigationResultTag.MPA:
{
// Perform an MPA navigation.
const newUrl = result.data;
return handleExternalUrl(state, mutable, newUrl, pendingPush);
}
case _types.NavigationResultTag.Success:
{
// Received a new result.
mutable.cache = result.data.cacheNode;
mutable.patchedTree = result.data.flightRouterState;
mutable.renderedSearch = result.data.renderedSearch;
mutable.canonicalUrl = result.data.canonicalUrl;
// TODO: During a refresh, we don't set the `scrollableSegments`. There's
// some confusing and subtle logic in `handleMutable` that decides what
// to do when `shouldScroll` is set but `scrollableSegments` is not. I'm
// not convinced it's totally coherent but the tests assert on this
// particular behavior so I've ported the logic as-is from the previous
// router implementation, for now.
mutable.scrollableSegments = result.data.scrollableSegments ?? undefined;
mutable.shouldScroll = result.data.shouldScroll;
mutable.hashFragment = result.data.hash;
// Check if the only thing that changed was the hash fragment.
const oldUrl = new URL(state.canonicalUrl, url);
const onlyHashChange = // We don't need to compare the origins, because client-driven
// navigations are always same-origin.
url.pathname === oldUrl.pathname && url.search === oldUrl.search && url.hash !== oldUrl.hash;
if (onlyHashChange) {
// The only updated part of the URL is the hash.
mutable.onlyHashChange = true;
mutable.shouldScroll = result.data.shouldScroll;
mutable.hashFragment = url.hash;
// Setting this to an empty array triggers a scroll for all new and
// updated segments. See `ScrollAndFocusHandler` for more details.
mutable.scrollableSegments = [];
}
return (0, _handlemutable.handleMutable)(state, mutable);
}
case _types.NavigationResultTag.Async:
{
return result.data.then((asyncResult)=>handleNavigationResult(url, state, mutable, pendingPush, asyncResult), // If the navigation failed, return the current state.
// TODO: This matches the current behavior but we need to do something
// better here if the network fails.
()=>{
return state;
});
}
default:
{
result;
return state;
}
}
}
function navigateReducer(state, action) {
const { url, isExternalUrl, navigateType, shouldScroll } = action;
const mutable = {};
const href = (0, _createhreffromurl.createHrefFromUrl)(url);
const pendingPush = navigateType === 'push';
mutable.preserveCustomHistoryState = false;
mutable.pendingPush = pendingPush;
if (isExternalUrl) {
return handleExternalUrl(state, mutable, url.toString(), pendingPush);
}
// Handles case where `<meta http-equiv="refresh">` tag is present,
// which will trigger an MPA navigation.
if (document.getElementById('__next-page-redirect')) {
return handleExternalUrl(state, mutable, href, pendingPush);
}
// Temporary glue code between the router reducer and the new navigation
// implementation. Eventually we'll rewrite the router reducer to a
// state machine.
const currentUrl = new URL(state.canonicalUrl, location.origin);
const result = (0, _navigation.navigate)(url, currentUrl, state.cache, state.tree, state.nextUrl, _pprnavigations.FreshnessPolicy.Default, shouldScroll, mutable);
return handleNavigationResult(url, state, mutable, pendingPush, result);
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=navigate-reducer.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
import type { ReadonlyReducerState, ReducerState } from '../router-reducer-types';
import { FreshnessPolicy } from '../ppr-navigations';
export declare function refreshReducer(state: ReadonlyReducerState): ReducerState;
export declare function refreshDynamicData(state: ReadonlyReducerState, freshnessPolicy: FreshnessPolicy.RefreshAll | FreshnessPolicy.HMRRefresh): ReducerState;
@@ -0,0 +1,68 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
refreshDynamicData: null,
refreshReducer: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
refreshDynamicData: function() {
return refreshDynamicData;
},
refreshReducer: function() {
return refreshReducer;
}
});
const _navigatereducer = require("./navigate-reducer");
const _navigation = require("../../segment-cache/navigation");
const _cache = require("../../segment-cache/cache");
const _hasinterceptionrouteincurrenttree = require("./has-interception-route-in-current-tree");
const _pprnavigations = require("../ppr-navigations");
function refreshReducer(state) {
// TODO: Currently, all refreshes purge the prefetch cache. In the future,
// only client-side refreshes will have this behavior; the server-side
// `refresh` should send new data without purging the prefetch cache.
const currentNextUrl = state.nextUrl;
const currentRouterState = state.tree;
(0, _cache.revalidateEntireCache)(currentNextUrl, currentRouterState);
return refreshDynamicData(state, _pprnavigations.FreshnessPolicy.RefreshAll);
}
function refreshDynamicData(state, freshnessPolicy) {
const currentNextUrl = state.nextUrl;
// We always send the last next-url, not the current when performing a dynamic
// request. This is because we update the next-url after a navigation, but we
// want the same interception route to be matched that used the last next-url.
const nextUrlForRefresh = (0, _hasinterceptionrouteincurrenttree.hasInterceptionRouteInCurrentTree)(state.tree) ? state.previousNextUrl || currentNextUrl : null;
// A refresh is modeled as a navigation to the current URL, but where any
// existing dynamic data (including in shared layouts) is re-fetched.
const currentCanonicalUrl = state.canonicalUrl;
const currentUrl = new URL(currentCanonicalUrl, location.origin);
const currentFlightRouterState = state.tree;
const shouldScroll = true;
const navigationSeed = {
tree: state.tree,
renderedSearch: state.renderedSearch,
data: null,
head: null
};
const now = Date.now();
const result = (0, _navigation.navigateToSeededRoute)(now, currentUrl, currentCanonicalUrl, navigationSeed, currentUrl, state.cache, currentFlightRouterState, freshnessPolicy, nextUrlForRefresh, shouldScroll);
const mutable = {};
mutable.preserveCustomHistoryState = false;
return (0, _navigatereducer.handleNavigationResult)(currentUrl, state, mutable, false, result);
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=refresh-reducer.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/refresh-reducer.ts"],"sourcesContent":["import type {\n Mutable,\n ReadonlyReducerState,\n ReducerState,\n} from '../router-reducer-types'\nimport { handleNavigationResult } from './navigate-reducer'\nimport { navigateToSeededRoute } from '../../segment-cache/navigation'\nimport { revalidateEntireCache } from '../../segment-cache/cache'\nimport { hasInterceptionRouteInCurrentTree } from './has-interception-route-in-current-tree'\nimport { FreshnessPolicy } from '../ppr-navigations'\n\nexport function refreshReducer(state: ReadonlyReducerState): ReducerState {\n // TODO: Currently, all refreshes purge the prefetch cache. In the future,\n // only client-side refreshes will have this behavior; the server-side\n // `refresh` should send new data without purging the prefetch cache.\n const currentNextUrl = state.nextUrl\n const currentRouterState = state.tree\n revalidateEntireCache(currentNextUrl, currentRouterState)\n return refreshDynamicData(state, FreshnessPolicy.RefreshAll)\n}\n\nexport function refreshDynamicData(\n state: ReadonlyReducerState,\n freshnessPolicy: FreshnessPolicy.RefreshAll | FreshnessPolicy.HMRRefresh\n): ReducerState {\n const currentNextUrl = state.nextUrl\n\n // We always send the last next-url, not the current when performing a dynamic\n // request. This is because we update the next-url after a navigation, but we\n // want the same interception route to be matched that used the last next-url.\n const nextUrlForRefresh = hasInterceptionRouteInCurrentTree(state.tree)\n ? state.previousNextUrl || currentNextUrl\n : null\n\n // A refresh is modeled as a navigation to the current URL, but where any\n // existing dynamic data (including in shared layouts) is re-fetched.\n const currentCanonicalUrl = state.canonicalUrl\n const currentUrl = new URL(currentCanonicalUrl, location.origin)\n const currentFlightRouterState = state.tree\n const shouldScroll = true\n\n const navigationSeed = {\n tree: state.tree,\n renderedSearch: state.renderedSearch,\n data: null,\n head: null,\n }\n\n const now = Date.now()\n const result = navigateToSeededRoute(\n now,\n currentUrl,\n currentCanonicalUrl,\n navigationSeed,\n currentUrl,\n state.cache,\n currentFlightRouterState,\n freshnessPolicy,\n nextUrlForRefresh,\n shouldScroll\n )\n\n const mutable: Mutable = {}\n mutable.preserveCustomHistoryState = false\n\n return handleNavigationResult(currentUrl, state, mutable, false, result)\n}\n"],"names":["refreshDynamicData","refreshReducer","state","currentNextUrl","nextUrl","currentRouterState","tree","revalidateEntireCache","FreshnessPolicy","RefreshAll","freshnessPolicy","nextUrlForRefresh","hasInterceptionRouteInCurrentTree","previousNextUrl","currentCanonicalUrl","canonicalUrl","currentUrl","URL","location","origin","currentFlightRouterState","shouldScroll","navigationSeed","renderedSearch","data","head","now","Date","result","navigateToSeededRoute","cache","mutable","preserveCustomHistoryState","handleNavigationResult"],"mappings":";;;;;;;;;;;;;;;IAqBgBA,kBAAkB;eAAlBA;;IAVAC,cAAc;eAAdA;;;iCANuB;4BACD;uBACA;mDACY;gCAClB;AAEzB,SAASA,eAAeC,KAA2B;IACxD,0EAA0E;IAC1E,sEAAsE;IACtE,qEAAqE;IACrE,MAAMC,iBAAiBD,MAAME,OAAO;IACpC,MAAMC,qBAAqBH,MAAMI,IAAI;IACrCC,IAAAA,4BAAqB,EAACJ,gBAAgBE;IACtC,OAAOL,mBAAmBE,OAAOM,+BAAe,CAACC,UAAU;AAC7D;AAEO,SAAST,mBACdE,KAA2B,EAC3BQ,eAAwE;IAExE,MAAMP,iBAAiBD,MAAME,OAAO;IAEpC,8EAA8E;IAC9E,6EAA6E;IAC7E,8EAA8E;IAC9E,MAAMO,oBAAoBC,IAAAA,oEAAiC,EAACV,MAAMI,IAAI,IAClEJ,MAAMW,eAAe,IAAIV,iBACzB;IAEJ,yEAAyE;IACzE,qEAAqE;IACrE,MAAMW,sBAAsBZ,MAAMa,YAAY;IAC9C,MAAMC,aAAa,IAAIC,IAAIH,qBAAqBI,SAASC,MAAM;IAC/D,MAAMC,2BAA2BlB,MAAMI,IAAI;IAC3C,MAAMe,eAAe;IAErB,MAAMC,iBAAiB;QACrBhB,MAAMJ,MAAMI,IAAI;QAChBiB,gBAAgBrB,MAAMqB,cAAc;QACpCC,MAAM;QACNC,MAAM;IACR;IAEA,MAAMC,MAAMC,KAAKD,GAAG;IACpB,MAAME,SAASC,IAAAA,iCAAqB,EAClCH,KACAV,YACAF,qBACAQ,gBACAN,YACAd,MAAM4B,KAAK,EACXV,0BACAV,iBACAC,mBACAU;IAGF,MAAMU,UAAmB,CAAC;IAC1BA,QAAQC,0BAA0B,GAAG;IAErC,OAAOC,IAAAA,uCAAsB,EAACjB,YAAYd,OAAO6B,SAAS,OAAOH;AACnE","ignoreList":[0]}
@@ -0,0 +1,2 @@
import type { ReadonlyReducerState, ReducerState, RestoreAction } from '../router-reducer-types';
export declare function restoreReducer(state: ReadonlyReducerState, action: RestoreAction): ReducerState;
@@ -0,0 +1,78 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "restoreReducer", {
enumerable: true,
get: function() {
return restoreReducer;
}
});
const _createhreffromurl = require("../create-href-from-url");
const _computechangedpath = require("../compute-changed-path");
const _pprnavigations = require("../ppr-navigations");
const _navigatereducer = require("./navigate-reducer");
function restoreReducer(state, action) {
// This action is used to restore the router state from the history state.
// However, it's possible that the history state no longer contains the `FlightRouterState`.
// We will copy over the internal state on pushState/replaceState events, but if a history entry
// occurred before hydration, or if the user navigated to a hash using a regular anchor link,
// the history state will not contain the `FlightRouterState`.
// In this case, we'll continue to use the existing tree so the router doesn't get into an invalid state.
let treeToRestore;
let renderedSearch;
const historyState = action.historyState;
if (historyState) {
treeToRestore = historyState.tree;
renderedSearch = historyState.renderedSearch;
} else {
treeToRestore = state.tree;
renderedSearch = state.renderedSearch;
}
const currentUrl = new URL(state.canonicalUrl, location.origin);
const restoredUrl = action.url;
const restoredCanonicalUrl = (0, _createhreffromurl.createHrefFromUrl)(restoredUrl);
const restoredNextUrl = (0, _computechangedpath.extractPathFromFlightRouterState)(treeToRestore) ?? restoredUrl.pathname;
const now = Date.now();
const accumulation = {
scrollableSegments: null,
separateRefreshUrls: null
};
const task = (0, _pprnavigations.startPPRNavigation)(now, currentUrl, state.cache, state.tree, treeToRestore, _pprnavigations.FreshnessPolicy.HistoryTraversal, null, null, null, null, false, false, accumulation);
if (task === null) {
const mutable = {
preserveCustomHistoryState: true
};
return (0, _navigatereducer.handleExternalUrl)(state, mutable, restoredCanonicalUrl, false);
}
(0, _pprnavigations.spawnDynamicRequests)(task, restoredUrl, restoredNextUrl, _pprnavigations.FreshnessPolicy.HistoryTraversal, accumulation);
return {
// Set canonical url
canonicalUrl: restoredCanonicalUrl,
renderedSearch,
pushRef: {
pendingPush: false,
mpaNavigation: false,
// Ensures that the custom history state that was set is preserved when applying this update.
preserveCustomHistoryState: true
},
focusAndScrollRef: state.focusAndScrollRef,
cache: task.node,
// Restore provided tree
tree: treeToRestore,
nextUrl: restoredNextUrl,
// TODO: We need to restore previousNextUrl, too, which represents the
// Next-Url that was used to fetch the data. Anywhere we fetch using the
// canonical URL, there should be a corresponding Next-Url.
previousNextUrl: null,
debugInfo: null
};
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=restore-reducer.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
import type { ReadonlyReducerState, ReducerState, ServerActionAction } from '../router-reducer-types';
export declare function serverActionReducer(state: ReadonlyReducerState, action: ServerActionAction): ReducerState;
@@ -0,0 +1,292 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "serverActionReducer", {
enumerable: true,
get: function() {
return serverActionReducer;
}
});
const _appcallserver = require("../../../app-call-server");
const _appfindsourcemapurl = require("../../../app-find-source-map-url");
const _approuterheaders = require("../../app-router-headers");
const _unrecognizedactionerror = require("../../unrecognized-action-error");
const _client = require("react-server-dom-webpack/client");
const _assignlocation = require("../../../assign-location");
const _createhreffromurl = require("../create-href-from-url");
const _navigatereducer = require("./navigate-reducer");
const _hasinterceptionrouteincurrenttree = require("./has-interception-route-in-current-tree");
const _flightdatahelpers = require("../../../flight-data-helpers");
const _redirect = require("../../redirect");
const _redirecterror = require("../../redirect-error");
const _removebasepath = require("../../../remove-base-path");
const _hasbasepath = require("../../../has-base-path");
const _serverreferenceinfo = require("../../../../shared/lib/server-reference-info");
const _cache = require("../../segment-cache/cache");
const _deploymentid = require("../../../../shared/lib/deployment-id");
const _navigation = require("../../segment-cache/navigation");
const _actionrevalidationkind = require("../../../../shared/lib/action-revalidation-kind");
const _approuterutils = require("../../app-router-utils");
const _pprnavigations = require("../ppr-navigations");
const createFromFetch = _client.createFromFetch;
let createDebugChannel;
if (process.env.NODE_ENV !== 'production' && process.env.__NEXT_REACT_DEBUG_CHANNEL) {
createDebugChannel = require('../../../dev/debug-channel').createDebugChannel;
}
async function fetchServerAction(state, nextUrl, { actionId, actionArgs }) {
const temporaryReferences = (0, _client.createTemporaryReferenceSet)();
const info = (0, _serverreferenceinfo.extractInfoFromServerReferenceId)(actionId);
// TODO: Currently, we're only omitting unused args for the experimental "use
// cache" functions. Once the server reference info byte feature is stable, we
// should apply this to server actions as well.
const usedArgs = info.type === 'use-cache' ? (0, _serverreferenceinfo.omitUnusedArgs)(actionArgs, info) : actionArgs;
const body = await (0, _client.encodeReply)(usedArgs, {
temporaryReferences
});
const headers = {
Accept: _approuterheaders.RSC_CONTENT_TYPE_HEADER,
[_approuterheaders.ACTION_HEADER]: actionId,
[_approuterheaders.NEXT_ROUTER_STATE_TREE_HEADER]: (0, _flightdatahelpers.prepareFlightRouterStateForRequest)(state.tree)
};
const deploymentId = (0, _deploymentid.getDeploymentId)();
if (deploymentId) {
headers['x-deployment-id'] = deploymentId;
}
if (nextUrl) {
headers[_approuterheaders.NEXT_URL] = nextUrl;
}
if (process.env.NODE_ENV !== 'production') {
if (self.__next_r) {
headers[_approuterheaders.NEXT_HTML_REQUEST_ID_HEADER] = self.__next_r;
}
// Create a new request ID for the server action request. The server uses
// this to tag debug information sent via WebSocket to the client, which
// then routes those chunks to the debug channel associated with this ID.
headers[_approuterheaders.NEXT_REQUEST_ID_HEADER] = crypto.getRandomValues(new Uint32Array(1))[0].toString(16);
}
const res = await fetch(state.canonicalUrl, {
method: 'POST',
headers,
body
});
// Handle server actions that the server didn't recognize.
const unrecognizedActionHeader = res.headers.get(_approuterheaders.NEXT_ACTION_NOT_FOUND_HEADER);
if (unrecognizedActionHeader === '1') {
throw Object.defineProperty(new _unrecognizedactionerror.UnrecognizedActionError(`Server Action "${actionId}" was not found on the server. \nRead more: https://nextjs.org/docs/messages/failed-to-find-server-action`), "__NEXT_ERROR_CODE", {
value: "E715",
enumerable: false,
configurable: true
});
}
const redirectHeader = res.headers.get('x-action-redirect');
const [location1, _redirectType] = redirectHeader?.split(';') || [];
let redirectType;
switch(_redirectType){
case 'push':
redirectType = _redirecterror.RedirectType.push;
break;
case 'replace':
redirectType = _redirecterror.RedirectType.replace;
break;
default:
redirectType = undefined;
}
const isPrerender = !!res.headers.get(_approuterheaders.NEXT_IS_PRERENDER_HEADER);
let revalidationKind = _actionrevalidationkind.ActionDidNotRevalidate;
try {
const revalidationHeader = res.headers.get('x-action-revalidated');
if (revalidationHeader) {
const parsedKind = JSON.parse(revalidationHeader);
if (parsedKind === _actionrevalidationkind.ActionDidRevalidateStaticAndDynamic || parsedKind === _actionrevalidationkind.ActionDidRevalidateDynamicOnly) {
revalidationKind = parsedKind;
}
}
} catch {}
const redirectLocation = location1 ? (0, _assignlocation.assignLocation)(location1, new URL(state.canonicalUrl, window.location.href)) : undefined;
const contentType = res.headers.get('content-type');
const isRscResponse = !!(contentType && contentType.startsWith(_approuterheaders.RSC_CONTENT_TYPE_HEADER));
// Handle invalid server action responses.
// A valid response must have `content-type: text/x-component`, unless it's an external redirect.
// (external redirects have an 'x-action-redirect' header, but the body is an empty 'text/plain')
if (!isRscResponse && !redirectLocation) {
// The server can respond with a text/plain error message, but we'll fallback to something generic
// if there isn't one.
const message = res.status >= 400 && contentType === 'text/plain' ? await res.text() : 'An unexpected response was received from the server.';
throw Object.defineProperty(new Error(message), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
});
}
let actionResult;
let actionFlightData;
let actionFlightDataRenderedSearch;
let actionFlightDataCouldBeIntercepted;
if (isRscResponse) {
const response = await createFromFetch(Promise.resolve(res), {
callServer: _appcallserver.callServer,
findSourceMapURL: _appfindsourcemapurl.findSourceMapURL,
temporaryReferences,
debugChannel: createDebugChannel && createDebugChannel(headers)
});
// An internal redirect can send an RSC response, but does not have a useful `actionResult`.
actionResult = redirectLocation ? undefined : response.a;
const maybeFlightData = (0, _flightdatahelpers.normalizeFlightData)(response.f);
if (maybeFlightData !== '') {
actionFlightData = maybeFlightData;
actionFlightDataRenderedSearch = response.q;
actionFlightDataCouldBeIntercepted = response.i;
}
} else {
// An external redirect doesn't contain RSC data.
actionResult = undefined;
actionFlightData = undefined;
actionFlightDataRenderedSearch = undefined;
actionFlightDataCouldBeIntercepted = undefined;
}
return {
actionResult,
actionFlightData,
actionFlightDataRenderedSearch,
actionFlightDataCouldBeIntercepted,
redirectLocation,
redirectType,
revalidationKind,
isPrerender
};
}
function serverActionReducer(state, action) {
const { resolve, reject } = action;
const mutable = {};
mutable.preserveCustomHistoryState = false;
// only pass along the `nextUrl` param (used for interception routes) if the current route was intercepted.
// If the route has been intercepted, the action should be as well.
// Otherwise the server action might be intercepted with the wrong action id
// (ie, one that corresponds with the intercepted route)
const nextUrl = // We always send the last next-url, not the current when
// performing a dynamic request. This is because we update
// the next-url after a navigation, but we want the same
// interception route to be matched that used the last
// next-url.
(state.previousNextUrl || state.nextUrl) && (0, _hasinterceptionrouteincurrenttree.hasInterceptionRouteInCurrentTree)(state.tree) ? state.previousNextUrl || state.nextUrl : null;
return fetchServerAction(state, nextUrl, action).then(async ({ revalidationKind, actionResult, actionFlightData: flightData, actionFlightDataRenderedSearch: flightDataRenderedSearch, actionFlightDataCouldBeIntercepted: flightDataCouldBeIntercepted, redirectLocation, redirectType })=>{
if (revalidationKind !== _actionrevalidationkind.ActionDidNotRevalidate) {
// Store whether this action triggered any revalidation
// The action queue will use this information to potentially
// trigger a refresh action if the action was discarded
// (ie, due to a navigation, before the action completed)
action.didRevalidate = true;
// If there was a revalidation, evict the entire prefetch cache.
// TODO: Evict only segments with matching tags and/or paths.
if (revalidationKind === _actionrevalidationkind.ActionDidRevalidateStaticAndDynamic) {
(0, _cache.revalidateEntireCache)(nextUrl, state.tree);
}
}
const pendingPush = redirectType !== _redirecterror.RedirectType.replace;
state.pushRef.pendingPush = pendingPush;
mutable.pendingPush = pendingPush;
if (redirectLocation !== undefined) {
// If the action triggered a redirect, the action promise will be rejected with
// a redirect so that it's handled by RedirectBoundary as we won't have a valid
// action result to resolve the promise with. This will effectively reset the state of
// the component that called the action as the error boundary will remount the tree.
// The status code doesn't matter here as the action handler will have already sent
// a response with the correct status code.
const resolvedRedirectType = redirectType || _redirecterror.RedirectType.push;
if ((0, _approuterutils.isExternalURL)(redirectLocation)) {
// External redirect. Triggers an MPA navigation.
const redirectHref = redirectLocation.href;
const redirectError = createRedirectErrorForAction(redirectHref, resolvedRedirectType);
reject(redirectError);
return (0, _navigatereducer.handleExternalUrl)(state, mutable, redirectHref, pendingPush);
} else {
// Internal redirect. Triggers an SPA navigation.
const redirectWithBasepath = (0, _createhreffromurl.createHrefFromUrl)(redirectLocation, false);
const redirectHref = (0, _hasbasepath.hasBasePath)(redirectWithBasepath) ? (0, _removebasepath.removeBasePath)(redirectWithBasepath) : redirectWithBasepath;
const redirectError = createRedirectErrorForAction(redirectHref, resolvedRedirectType);
reject(redirectError);
}
} else {
// If there's no redirect, resolve the action with the result.
resolve(actionResult);
}
// Check if we can bail out without updating any state.
if (// Did the action trigger a redirect?
redirectLocation === undefined && // Did the action revalidate any data?
revalidationKind === _actionrevalidationkind.ActionDidNotRevalidate && // Did the server render new data?
flightData === undefined) {
// The action did not trigger any revalidations or redirects. No
// navigation is required.
return state;
}
if (flightData === undefined && redirectLocation !== undefined) {
// The server redirected, but did not send any Flight data. This implies
// an external redirect.
// TODO: We should refactor the action response type to be more explicit
// about the various response types.
return (0, _navigatereducer.handleExternalUrl)(state, mutable, redirectLocation.href, pendingPush);
}
if (typeof flightData === 'string') {
// If the flight data is just a string, something earlier in the
// response handling triggered an external redirect.
return (0, _navigatereducer.handleExternalUrl)(state, mutable, flightData, pendingPush);
}
// The action triggered a navigation — either a redirect, a revalidation,
// or both.
// If there was no redirect, then the target URL is the same as the
// current URL.
const currentUrl = new URL(state.canonicalUrl, location.origin);
const redirectUrl = redirectLocation !== undefined ? redirectLocation : currentUrl;
const currentFlightRouterState = state.tree;
const shouldScroll = true;
// If the action triggered a revalidation of the cache, we should also
// refresh all the dynamic data.
const freshnessPolicy = revalidationKind === _actionrevalidationkind.ActionDidNotRevalidate ? _pprnavigations.FreshnessPolicy.Default : _pprnavigations.FreshnessPolicy.RefreshAll;
// The server may have sent back new data. If so, we will perform a
// "seeded" navigation that uses the data from the response.
if (flightData !== undefined) {
const normalizedFlightData = flightData[0];
if (normalizedFlightData !== undefined && // TODO: Currently the server always renders from the root in
// response to a Server Action. In the case of a normal redirect
// with no revalidation, it should skip over the shared layouts.
normalizedFlightData.isRootRender && flightDataRenderedSearch !== undefined && flightDataCouldBeIntercepted !== undefined) {
// The server sent back new route data as part of the response. We
// will use this to render the new page. If this happens to be only a
// subset of the data needed to render the new page, we'll initiate a
// new fetch, like we would for a normal navigation.
const redirectCanonicalUrl = (0, _createhreffromurl.createHrefFromUrl)(redirectUrl);
const navigationSeed = {
tree: normalizedFlightData.tree,
renderedSearch: flightDataRenderedSearch,
data: normalizedFlightData.seedData,
head: normalizedFlightData.head
};
const now = Date.now();
const result = (0, _navigation.navigateToSeededRoute)(now, redirectUrl, redirectCanonicalUrl, navigationSeed, currentUrl, state.cache, currentFlightRouterState, freshnessPolicy, nextUrl, shouldScroll);
return (0, _navigatereducer.handleNavigationResult)(redirectUrl, state, mutable, pendingPush, result);
}
}
// The server did not send back new data. We'll perform a regular, non-
// seeded navigation — effectively the same as <Link> or router.push().
const result = (0, _navigation.navigate)(redirectUrl, currentUrl, state.cache, currentFlightRouterState, nextUrl, freshnessPolicy, shouldScroll, mutable);
return (0, _navigatereducer.handleNavigationResult)(redirectUrl, state, mutable, pendingPush, result);
}, (e)=>{
// When the server action is rejected we don't update the state and instead call the reject handler of the promise.
reject(e);
return state;
});
}
function createRedirectErrorForAction(redirectHref, resolvedRedirectType) {
const redirectError = (0, _redirect.getRedirectError)(redirectHref, resolvedRedirectType);
redirectError.handled = true;
return redirectError;
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=server-action-reducer.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
import type { ServerPatchAction, ReducerState, ReadonlyReducerState } from '../router-reducer-types';
export declare function serverPatchReducer(state: ReadonlyReducerState, action: ServerPatchAction): ReducerState;
@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "serverPatchReducer", {
enumerable: true,
get: function() {
return serverPatchReducer;
}
});
const _createhreffromurl = require("../create-href-from-url");
const _navigatereducer = require("./navigate-reducer");
const _navigation = require("../../segment-cache/navigation");
const _refreshreducer = require("./refresh-reducer");
const _pprnavigations = require("../ppr-navigations");
function serverPatchReducer(state, action) {
const mutable = {};
mutable.preserveCustomHistoryState = false;
// A "retry" is a navigation that happens due to a route mismatch. It's
// similar to a refresh, because we will omit any existing dynamic data on
// the page. But we seed the retry navigation with the exact tree that the
// server just responded with.
const retryMpa = action.mpa;
const retryUrl = new URL(action.url, location.origin);
const retrySeed = action.seed;
if (retryMpa || retrySeed === null) {
// If the server did not send back data during the mismatch, fall back to
// an MPA navigation.
return (0, _navigatereducer.handleExternalUrl)(state, mutable, retryUrl.href, false);
}
const currentUrl = new URL(state.canonicalUrl, location.origin);
if (action.previousTree !== state.tree) {
// There was another, more recent navigation since the once that
// mismatched. We can abort the retry, but we still need to refresh the
// page to evict any stale dynamic data.
return (0, _refreshreducer.refreshReducer)(state);
}
// There have been no new navigations since the mismatched one. Refresh,
// using the tree we just received from the server.
const retryCanonicalUrl = (0, _createhreffromurl.createHrefFromUrl)(retryUrl);
const retryNextUrl = action.nextUrl;
// A retry should not create a new history entry.
const pendingPush = false;
const shouldScroll = true;
const now = Date.now();
const result = (0, _navigation.navigateToSeededRoute)(now, retryUrl, retryCanonicalUrl, retrySeed, currentUrl, state.cache, state.tree, _pprnavigations.FreshnessPolicy.RefreshAll, retryNextUrl, shouldScroll);
return (0, _navigatereducer.handleNavigationResult)(retryUrl, state, mutable, pendingPush, result);
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=server-patch-reducer.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/client/components/router-reducer/reducers/server-patch-reducer.ts"],"sourcesContent":["import { createHrefFromUrl } from '../create-href-from-url'\nimport type {\n ServerPatchAction,\n ReducerState,\n ReadonlyReducerState,\n Mutable,\n} from '../router-reducer-types'\nimport { handleExternalUrl, handleNavigationResult } from './navigate-reducer'\nimport { navigateToSeededRoute } from '../../segment-cache/navigation'\nimport { refreshReducer } from './refresh-reducer'\nimport { FreshnessPolicy } from '../ppr-navigations'\n\nexport function serverPatchReducer(\n state: ReadonlyReducerState,\n action: ServerPatchAction\n): ReducerState {\n const mutable: Mutable = {}\n mutable.preserveCustomHistoryState = false\n\n // A \"retry\" is a navigation that happens due to a route mismatch. It's\n // similar to a refresh, because we will omit any existing dynamic data on\n // the page. But we seed the retry navigation with the exact tree that the\n // server just responded with.\n const retryMpa = action.mpa\n const retryUrl = new URL(action.url, location.origin)\n const retrySeed = action.seed\n if (retryMpa || retrySeed === null) {\n // If the server did not send back data during the mismatch, fall back to\n // an MPA navigation.\n return handleExternalUrl(state, mutable, retryUrl.href, false)\n }\n const currentUrl = new URL(state.canonicalUrl, location.origin)\n if (action.previousTree !== state.tree) {\n // There was another, more recent navigation since the once that\n // mismatched. We can abort the retry, but we still need to refresh the\n // page to evict any stale dynamic data.\n return refreshReducer(state)\n }\n // There have been no new navigations since the mismatched one. Refresh,\n // using the tree we just received from the server.\n const retryCanonicalUrl = createHrefFromUrl(retryUrl)\n const retryNextUrl = action.nextUrl\n // A retry should not create a new history entry.\n const pendingPush = false\n const shouldScroll = true\n const now = Date.now()\n const result = navigateToSeededRoute(\n now,\n retryUrl,\n retryCanonicalUrl,\n retrySeed,\n currentUrl,\n state.cache,\n state.tree,\n FreshnessPolicy.RefreshAll,\n retryNextUrl,\n shouldScroll\n )\n return handleNavigationResult(retryUrl, state, mutable, pendingPush, result)\n}\n"],"names":["serverPatchReducer","state","action","mutable","preserveCustomHistoryState","retryMpa","mpa","retryUrl","URL","url","location","origin","retrySeed","seed","handleExternalUrl","href","currentUrl","canonicalUrl","previousTree","tree","refreshReducer","retryCanonicalUrl","createHrefFromUrl","retryNextUrl","nextUrl","pendingPush","shouldScroll","now","Date","result","navigateToSeededRoute","cache","FreshnessPolicy","RefreshAll","handleNavigationResult"],"mappings":";;;;+BAYgBA;;;eAAAA;;;mCAZkB;iCAOwB;4BACpB;gCACP;gCACC;AAEzB,SAASA,mBACdC,KAA2B,EAC3BC,MAAyB;IAEzB,MAAMC,UAAmB,CAAC;IAC1BA,QAAQC,0BAA0B,GAAG;IAErC,uEAAuE;IACvE,0EAA0E;IAC1E,0EAA0E;IAC1E,8BAA8B;IAC9B,MAAMC,WAAWH,OAAOI,GAAG;IAC3B,MAAMC,WAAW,IAAIC,IAAIN,OAAOO,GAAG,EAAEC,SAASC,MAAM;IACpD,MAAMC,YAAYV,OAAOW,IAAI;IAC7B,IAAIR,YAAYO,cAAc,MAAM;QAClC,yEAAyE;QACzE,qBAAqB;QACrB,OAAOE,IAAAA,kCAAiB,EAACb,OAAOE,SAASI,SAASQ,IAAI,EAAE;IAC1D;IACA,MAAMC,aAAa,IAAIR,IAAIP,MAAMgB,YAAY,EAAEP,SAASC,MAAM;IAC9D,IAAIT,OAAOgB,YAAY,KAAKjB,MAAMkB,IAAI,EAAE;QACtC,gEAAgE;QAChE,uEAAuE;QACvE,wCAAwC;QACxC,OAAOC,IAAAA,8BAAc,EAACnB;IACxB;IACA,wEAAwE;IACxE,mDAAmD;IACnD,MAAMoB,oBAAoBC,IAAAA,oCAAiB,EAACf;IAC5C,MAAMgB,eAAerB,OAAOsB,OAAO;IACnC,iDAAiD;IACjD,MAAMC,cAAc;IACpB,MAAMC,eAAe;IACrB,MAAMC,MAAMC,KAAKD,GAAG;IACpB,MAAME,SAASC,IAAAA,iCAAqB,EAClCH,KACApB,UACAc,mBACAT,WACAI,YACAf,MAAM8B,KAAK,EACX9B,MAAMkB,IAAI,EACVa,+BAAe,CAACC,UAAU,EAC1BV,cACAG;IAEF,OAAOQ,IAAAA,uCAAsB,EAAC3B,UAAUN,OAAOE,SAASsB,aAAaI;AACvE","ignoreList":[0]}
@@ -0,0 +1,210 @@
import type { CacheNode } from '../../../shared/lib/app-router-types';
import type { FlightRouterState, FlightSegmentPath } from '../../../shared/lib/app-router-types';
import type { NavigationSeed } from '../segment-cache/navigation';
import type { FetchServerResponseResult } from './fetch-server-response';
export declare const ACTION_REFRESH = "refresh";
export declare const ACTION_NAVIGATE = "navigate";
export declare const ACTION_RESTORE = "restore";
export declare const ACTION_SERVER_PATCH = "server-patch";
export declare const ACTION_HMR_REFRESH = "hmr-refresh";
export declare const ACTION_SERVER_ACTION = "server-action";
export type RouterChangeByServerResponse = ({ navigatedAt, previousTree, serverResponse, }: {
navigatedAt: number;
previousTree: FlightRouterState;
serverResponse: FetchServerResponseResult;
}) => void;
export interface Mutable {
mpaNavigation?: boolean;
patchedTree?: FlightRouterState;
renderedSearch?: string;
canonicalUrl?: string;
scrollableSegments?: FlightSegmentPath[];
pendingPush?: boolean;
cache?: CacheNode;
hashFragment?: string;
shouldScroll?: boolean;
preserveCustomHistoryState?: boolean;
onlyHashChange?: boolean;
collectedDebugInfo?: Array<unknown>;
}
export interface ServerActionMutable extends Mutable {
inFlightServerAction?: Promise<any> | null;
}
/**
* Refresh triggers a refresh of the full page data.
* - fetches the Flight data and fills rsc at the root of the cache.
* - The router state is updated at the root.
*/
export interface RefreshAction {
type: typeof ACTION_REFRESH;
}
export interface HmrRefreshAction {
type: typeof ACTION_HMR_REFRESH;
}
export type ServerActionDispatcher = (args: Omit<ServerActionAction, 'type' | 'mutable' | 'navigate' | 'changeByServerResponse' | 'cache'>) => void;
export interface ServerActionAction {
type: typeof ACTION_SERVER_ACTION;
actionId: string;
actionArgs: any[];
resolve: (value: any) => void;
reject: (reason?: any) => void;
didRevalidate?: boolean;
}
/**
* Navigate triggers a navigation to the provided url. It supports two types: `push` and `replace`.
*
* `navigateType`:
* - `push` - pushes a new history entry in the browser history
* - `replace` - replaces the current history entry in the browser history
*
* Navigate has multiple cache heuristics:
* - page was prefetched
* - Apply router state tree from prefetch
* - Apply Flight data from prefetch to the cache
* - If Flight data is a string, it's a redirect and the state is updated to trigger a redirect
* - Check if hard navigation is needed
* - Hard navigation happens when a dynamic parameter below the common layout changed
* - When hard navigation is needed the cache is invalidated below the flightSegmentPath
* - The missing cache nodes of the page will be fetched in layout-router and trigger the SERVER_PATCH action
* - If hard navigation is not needed
* - The cache is reused
* - If any cache nodes are missing they'll be fetched in layout-router and trigger the SERVER_PATCH action
* - page was not prefetched
* - The navigate was called from `next/router` (`router.push()` / `router.replace()`) / `next/link` without prefetched data available (e.g. the prefetch didn't come back from the server before clicking the link)
* - Flight data is fetched in the reducer (suspends the reducer)
* - Router state tree is created based on Flight data
* - Cache is filled based on the Flight data
*
* Above steps explain 3 cases:
* - `soft` - Reuses the existing cache and fetches missing nodes in layout-router.
* - `hard` - Creates a new cache where cache nodes are removed below the common layout and fetches missing nodes in layout-router.
* - `optimistic` (explicit no prefetch) - Creates a new cache and kicks off the data fetch in the reducer. The data fetch is awaited in the layout-router.
*/
export interface NavigateAction {
type: typeof ACTION_NAVIGATE;
url: URL;
isExternalUrl: boolean;
locationSearch: Location['search'];
navigateType: 'push' | 'replace';
shouldScroll: boolean;
}
/**
* Restore applies the provided router state.
* - Used for `popstate` (back/forward navigation) where a known router state has to be applied.
* - Also used when syncing the router state with `pushState`/`replaceState` calls.
* - Router state is applied as-is from the history state, if available.
* - If the history state does not contain the router state, the existing router state is used.
* - If any cache node is missing it will be fetched in layout-router during rendering and the server-patch case.
* - If existing cache nodes match these are used.
*/
export interface RestoreAction {
type: typeof ACTION_RESTORE;
url: URL;
historyState: AppHistoryState | undefined;
}
export type AppHistoryState = {
tree: FlightRouterState;
renderedSearch: string;
};
/**
* Server-patch applies the provided Flight data to the cache and router tree.
*/
export interface ServerPatchAction {
type: typeof ACTION_SERVER_PATCH;
previousTree: FlightRouterState;
url: URL;
nextUrl: string | null;
seed: NavigationSeed | null;
mpa: boolean;
}
/**
* PrefetchKind defines the type of prefetching that should be done.
* - `auto` - if the page is dynamic, prefetch the page data partially, if static prefetch the page data fully.
* - `full` - prefetch the page data fully.
*/
export declare enum PrefetchKind {
AUTO = "auto",
FULL = "full"
}
/**
* Prefetch adds the provided FlightData to the prefetch cache
* - Creates the router state tree based on the patch in FlightData
* - Adds the FlightData to the prefetch cache
* - In ACTION_NAVIGATE the prefetch cache is checked and the router state tree and FlightData are applied.
*/
export interface PushRef {
/**
* If the app-router should push a new history entry in app-router's useEffect()
*/
pendingPush: boolean;
/**
* Multi-page navigation through location.href.
*/
mpaNavigation: boolean;
/**
* Skip applying the router state to the browser history state.
*/
preserveCustomHistoryState: boolean;
}
export type FocusAndScrollRef = {
/**
* If focus and scroll should be set in the layout-router's useEffect()
*/
apply: boolean;
/**
* The hash fragment that should be scrolled to.
*/
hashFragment: string | null;
/**
* The paths of the segments that should be focused.
*/
segmentPaths: FlightSegmentPath[];
/**
* If only the URLs hash fragment changed
*/
onlyHashChange: boolean;
};
/**
* Handles keeping the state of app-router.
*/
export type AppRouterState = {
/**
* The router state, this is written into the history state in app-router using replaceState/pushState.
* - Has to be serializable as it is written into the history state.
* - Holds which segments and parallel routes are shown on the screen.
*/
tree: FlightRouterState;
/**
* The cache holds React nodes for every segment that is shown on screen as well as previously shown segments.
* It also holds in-progress data requests.
*/
cache: CacheNode;
/**
* Decides if the update should create a new history entry and if the navigation has to trigger a browser navigation.
*/
pushRef: PushRef;
/**
* Decides if the update should apply scroll and focus management.
*/
focusAndScrollRef: FocusAndScrollRef;
/**
* The canonical url that is pushed/replaced.
* - This is the url you see in the browser.
*/
canonicalUrl: string;
renderedSearch: string;
/**
* The underlying "url" representing the UI state, which is used for intercepting routes.
*/
nextUrl: string | null;
/**
* The previous next-url that was used previous to a dynamic navigation.
*/
previousNextUrl: string | null;
debugInfo: Array<unknown> | null;
};
export type ReadonlyReducerState = Readonly<AppRouterState>;
export type ReducerState = (Promise<AppRouterState> & {
_debugInfo?: Array<unknown>;
}) | AppRouterState;
export type ReducerActions = Readonly<RefreshAction | NavigateAction | RestoreAction | ServerPatchAction | HmrRefreshAction | ServerActionAction>;
@@ -0,0 +1,61 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
ACTION_HMR_REFRESH: null,
ACTION_NAVIGATE: null,
ACTION_REFRESH: null,
ACTION_RESTORE: null,
ACTION_SERVER_ACTION: null,
ACTION_SERVER_PATCH: null,
PrefetchKind: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
ACTION_HMR_REFRESH: function() {
return ACTION_HMR_REFRESH;
},
ACTION_NAVIGATE: function() {
return ACTION_NAVIGATE;
},
ACTION_REFRESH: function() {
return ACTION_REFRESH;
},
ACTION_RESTORE: function() {
return ACTION_RESTORE;
},
ACTION_SERVER_ACTION: function() {
return ACTION_SERVER_ACTION;
},
ACTION_SERVER_PATCH: function() {
return ACTION_SERVER_PATCH;
},
PrefetchKind: function() {
return PrefetchKind;
}
});
const ACTION_REFRESH = 'refresh';
const ACTION_NAVIGATE = 'navigate';
const ACTION_RESTORE = 'restore';
const ACTION_SERVER_PATCH = 'server-patch';
const ACTION_HMR_REFRESH = 'hmr-refresh';
const ACTION_SERVER_ACTION = 'server-action';
var PrefetchKind = /*#__PURE__*/ function(PrefetchKind) {
PrefetchKind["AUTO"] = "auto";
PrefetchKind["FULL"] = "full";
return PrefetchKind;
}({});
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=router-reducer-types.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
import type { ReducerActions, ReducerState, ReadonlyReducerState } from './router-reducer-types';
declare function serverReducer(state: ReadonlyReducerState, _action: ReducerActions): ReducerState;
export declare const reducer: typeof serverReducer;
export {};
@@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "reducer", {
enumerable: true,
get: function() {
return reducer;
}
});
const _routerreducertypes = require("./router-reducer-types");
const _navigatereducer = require("./reducers/navigate-reducer");
const _serverpatchreducer = require("./reducers/server-patch-reducer");
const _restorereducer = require("./reducers/restore-reducer");
const _refreshreducer = require("./reducers/refresh-reducer");
const _hmrrefreshreducer = require("./reducers/hmr-refresh-reducer");
const _serveractionreducer = require("./reducers/server-action-reducer");
/**
* Reducer that handles the app-router state updates.
*/ function clientReducer(state, action) {
switch(action.type){
case _routerreducertypes.ACTION_NAVIGATE:
{
return (0, _navigatereducer.navigateReducer)(state, action);
}
case _routerreducertypes.ACTION_SERVER_PATCH:
{
return (0, _serverpatchreducer.serverPatchReducer)(state, action);
}
case _routerreducertypes.ACTION_RESTORE:
{
return (0, _restorereducer.restoreReducer)(state, action);
}
case _routerreducertypes.ACTION_REFRESH:
{
return (0, _refreshreducer.refreshReducer)(state);
}
case _routerreducertypes.ACTION_HMR_REFRESH:
{
return (0, _hmrrefreshreducer.hmrRefreshReducer)(state);
}
case _routerreducertypes.ACTION_SERVER_ACTION:
{
return (0, _serveractionreducer.serverActionReducer)(state, action);
}
// This case should never be hit as dispatch is strongly typed.
default:
throw Object.defineProperty(new Error('Unknown action'), "__NEXT_ERROR_CODE", {
value: "E295",
enumerable: false,
configurable: true
});
}
}
function serverReducer(state, _action) {
return state;
}
const reducer = typeof window === 'undefined' ? serverReducer : clientReducer;
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=router-reducer.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/router-reducer.ts"],"sourcesContent":["import {\n ACTION_NAVIGATE,\n ACTION_SERVER_PATCH,\n ACTION_RESTORE,\n ACTION_REFRESH,\n ACTION_HMR_REFRESH,\n ACTION_SERVER_ACTION,\n} from './router-reducer-types'\nimport type {\n ReducerActions,\n ReducerState,\n ReadonlyReducerState,\n} from './router-reducer-types'\nimport { navigateReducer } from './reducers/navigate-reducer'\nimport { serverPatchReducer } from './reducers/server-patch-reducer'\nimport { restoreReducer } from './reducers/restore-reducer'\nimport { refreshReducer } from './reducers/refresh-reducer'\nimport { hmrRefreshReducer } from './reducers/hmr-refresh-reducer'\nimport { serverActionReducer } from './reducers/server-action-reducer'\n\n/**\n * Reducer that handles the app-router state updates.\n */\nfunction clientReducer(\n state: ReadonlyReducerState,\n action: ReducerActions\n): ReducerState {\n switch (action.type) {\n case ACTION_NAVIGATE: {\n return navigateReducer(state, action)\n }\n case ACTION_SERVER_PATCH: {\n return serverPatchReducer(state, action)\n }\n case ACTION_RESTORE: {\n return restoreReducer(state, action)\n }\n case ACTION_REFRESH: {\n return refreshReducer(state)\n }\n case ACTION_HMR_REFRESH: {\n return hmrRefreshReducer(state)\n }\n case ACTION_SERVER_ACTION: {\n return serverActionReducer(state, action)\n }\n // This case should never be hit as dispatch is strongly typed.\n default:\n throw new Error('Unknown action')\n }\n}\n\nfunction serverReducer(\n state: ReadonlyReducerState,\n _action: ReducerActions\n): ReducerState {\n return state\n}\n\n// we don't run the client reducer on the server, so we use a noop function for better tree shaking\nexport const reducer =\n typeof window === 'undefined' ? serverReducer : clientReducer\n"],"names":["reducer","clientReducer","state","action","type","ACTION_NAVIGATE","navigateReducer","ACTION_SERVER_PATCH","serverPatchReducer","ACTION_RESTORE","restoreReducer","ACTION_REFRESH","refreshReducer","ACTION_HMR_REFRESH","hmrRefreshReducer","ACTION_SERVER_ACTION","serverActionReducer","Error","serverReducer","_action","window"],"mappings":";;;;+BA4DaA;;;eAAAA;;;oCArDN;iCAMyB;oCACG;gCACJ;gCACA;mCACG;qCACE;AAEpC;;CAEC,GACD,SAASC,cACPC,KAA2B,EAC3BC,MAAsB;IAEtB,OAAQA,OAAOC,IAAI;QACjB,KAAKC,mCAAe;YAAE;gBACpB,OAAOC,IAAAA,gCAAe,EAACJ,OAAOC;YAChC;QACA,KAAKI,uCAAmB;YAAE;gBACxB,OAAOC,IAAAA,sCAAkB,EAACN,OAAOC;YACnC;QACA,KAAKM,kCAAc;YAAE;gBACnB,OAAOC,IAAAA,8BAAc,EAACR,OAAOC;YAC/B;QACA,KAAKQ,kCAAc;YAAE;gBACnB,OAAOC,IAAAA,8BAAc,EAACV;YACxB;QACA,KAAKW,sCAAkB;YAAE;gBACvB,OAAOC,IAAAA,oCAAiB,EAACZ;YAC3B;QACA,KAAKa,wCAAoB;YAAE;gBACzB,OAAOC,IAAAA,wCAAmB,EAACd,OAAOC;YACpC;QACA,+DAA+D;QAC/D;YACE,MAAM,qBAA2B,CAA3B,IAAIc,MAAM,mBAAV,qBAAA;uBAAA;4BAAA;8BAAA;YAA0B;IACpC;AACF;AAEA,SAASC,cACPhB,KAA2B,EAC3BiB,OAAuB;IAEvB,OAAOjB;AACT;AAGO,MAAMF,UACX,OAAOoB,WAAW,cAAcF,gBAAgBjB","ignoreList":[0]}
@@ -0,0 +1,35 @@
import type { RequestHeaders } from './fetch-server-response';
/**
* Mutates the provided URL by adding a cache-busting search parameter for CDNs that don't
* support custom headers. This helps avoid caching conflicts by making each request unique.
*
* Rather than relying on the Vary header which some CDNs ignore, we append a search param
* to create a unique URL that forces a fresh request.
*
* Example:
* URL before: https://example.com/path?query=1
* URL after: https://example.com/path?query=1&_rsc=abc123
*
* Note: This function mutates the input URL directly and does not return anything.
*
* TODO: Since we need to use a search param anyway, we could simplify by removing the custom
* headers approach entirely and just use search params.
*/
export declare const setCacheBustingSearchParam: (url: URL, headers: RequestHeaders) => void;
/**
* Sets a cache-busting search parameter on a URL using a provided hash value.
*
* This function performs the same logic as `setCacheBustingSearchParam` but accepts
* a pre-computed hash instead of computing it from headers.
*
* Example:
* URL before: https://example.com/path?query=1
* hash: "abc123"
* URL after: https://example.com/path?query=1&_rsc=abc123
*
* If the hash is null, we will set `_rsc` search param without a value.
* Like this: https://example.com/path?query=1&_rsc
*
* Note: This function mutates the input URL directly and does not return anything.
*/
export declare const setCacheBustingSearchParamWithHash: (url: URL, hash: string) => void;
@@ -0,0 +1,60 @@
'use client';
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
setCacheBustingSearchParam: null,
setCacheBustingSearchParamWithHash: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
setCacheBustingSearchParam: function() {
return setCacheBustingSearchParam;
},
setCacheBustingSearchParamWithHash: function() {
return setCacheBustingSearchParamWithHash;
}
});
const _cachebustingsearchparam = require("../../../shared/lib/router/utils/cache-busting-search-param");
const _approuterheaders = require("../app-router-headers");
const setCacheBustingSearchParam = (url, headers)=>{
const uniqueCacheKey = (0, _cachebustingsearchparam.computeCacheBustingSearchParam)(headers[_approuterheaders.NEXT_ROUTER_PREFETCH_HEADER], headers[_approuterheaders.NEXT_ROUTER_SEGMENT_PREFETCH_HEADER], headers[_approuterheaders.NEXT_ROUTER_STATE_TREE_HEADER], headers[_approuterheaders.NEXT_URL]);
setCacheBustingSearchParamWithHash(url, uniqueCacheKey);
};
const setCacheBustingSearchParamWithHash = (url, hash)=>{
/**
* Note that we intentionally do not use `url.searchParams.set` here:
*
* const url = new URL('https://example.com/search?q=custom%20spacing');
* url.searchParams.set('_rsc', 'abc123');
* console.log(url.toString()); // Outputs: https://example.com/search?q=custom+spacing&_rsc=abc123
* ^ <--- this is causing confusion
* This is in fact intended based on https://url.spec.whatwg.org/#interface-urlsearchparams, but
* we want to preserve the %20 as %20 if that's what the user passed in, hence the custom
* logic below.
*/ const existingSearch = url.search;
const rawQuery = existingSearch.startsWith('?') ? existingSearch.slice(1) : existingSearch;
// Always remove any existing cache busting param and add a fresh one to ensure
// we have the correct value based on current request headers
const pairs = rawQuery.split('&').filter((pair)=>pair && !pair.startsWith(`${_approuterheaders.NEXT_RSC_UNION_QUERY}=`));
if (hash.length > 0) {
pairs.push(`${_approuterheaders.NEXT_RSC_UNION_QUERY}=${hash}`);
} else {
pairs.push(`${_approuterheaders.NEXT_RSC_UNION_QUERY}`);
}
url.search = pairs.length ? `?${pairs.join('&')}` : '';
};
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=set-cache-busting-search-param.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/set-cache-busting-search-param.ts"],"sourcesContent":["'use client'\n\nimport { computeCacheBustingSearchParam } from '../../../shared/lib/router/utils/cache-busting-search-param'\nimport {\n NEXT_ROUTER_PREFETCH_HEADER,\n NEXT_ROUTER_SEGMENT_PREFETCH_HEADER,\n NEXT_ROUTER_STATE_TREE_HEADER,\n NEXT_URL,\n NEXT_RSC_UNION_QUERY,\n} from '../app-router-headers'\nimport type { RequestHeaders } from './fetch-server-response'\n\n/**\n * Mutates the provided URL by adding a cache-busting search parameter for CDNs that don't\n * support custom headers. This helps avoid caching conflicts by making each request unique.\n *\n * Rather than relying on the Vary header which some CDNs ignore, we append a search param\n * to create a unique URL that forces a fresh request.\n *\n * Example:\n * URL before: https://example.com/path?query=1\n * URL after: https://example.com/path?query=1&_rsc=abc123\n *\n * Note: This function mutates the input URL directly and does not return anything.\n *\n * TODO: Since we need to use a search param anyway, we could simplify by removing the custom\n * headers approach entirely and just use search params.\n */\nexport const setCacheBustingSearchParam = (\n url: URL,\n headers: RequestHeaders\n): void => {\n const uniqueCacheKey = computeCacheBustingSearchParam(\n headers[NEXT_ROUTER_PREFETCH_HEADER],\n headers[NEXT_ROUTER_SEGMENT_PREFETCH_HEADER],\n headers[NEXT_ROUTER_STATE_TREE_HEADER],\n headers[NEXT_URL]\n )\n setCacheBustingSearchParamWithHash(url, uniqueCacheKey)\n}\n\n/**\n * Sets a cache-busting search parameter on a URL using a provided hash value.\n *\n * This function performs the same logic as `setCacheBustingSearchParam` but accepts\n * a pre-computed hash instead of computing it from headers.\n *\n * Example:\n * URL before: https://example.com/path?query=1\n * hash: \"abc123\"\n * URL after: https://example.com/path?query=1&_rsc=abc123\n *\n * If the hash is null, we will set `_rsc` search param without a value.\n * Like this: https://example.com/path?query=1&_rsc\n *\n * Note: This function mutates the input URL directly and does not return anything.\n */\nexport const setCacheBustingSearchParamWithHash = (\n url: URL,\n hash: string\n): void => {\n /**\n * Note that we intentionally do not use `url.searchParams.set` here:\n *\n * const url = new URL('https://example.com/search?q=custom%20spacing');\n * url.searchParams.set('_rsc', 'abc123');\n * console.log(url.toString()); // Outputs: https://example.com/search?q=custom+spacing&_rsc=abc123\n * ^ <--- this is causing confusion\n * This is in fact intended based on https://url.spec.whatwg.org/#interface-urlsearchparams, but\n * we want to preserve the %20 as %20 if that's what the user passed in, hence the custom\n * logic below.\n */\n const existingSearch = url.search\n const rawQuery = existingSearch.startsWith('?')\n ? existingSearch.slice(1)\n : existingSearch\n\n // Always remove any existing cache busting param and add a fresh one to ensure\n // we have the correct value based on current request headers\n const pairs = rawQuery\n .split('&')\n .filter((pair) => pair && !pair.startsWith(`${NEXT_RSC_UNION_QUERY}=`))\n\n if (hash.length > 0) {\n pairs.push(`${NEXT_RSC_UNION_QUERY}=${hash}`)\n } else {\n pairs.push(`${NEXT_RSC_UNION_QUERY}`)\n }\n url.search = pairs.length ? `?${pairs.join('&')}` : ''\n}\n"],"names":["setCacheBustingSearchParam","setCacheBustingSearchParamWithHash","url","headers","uniqueCacheKey","computeCacheBustingSearchParam","NEXT_ROUTER_PREFETCH_HEADER","NEXT_ROUTER_SEGMENT_PREFETCH_HEADER","NEXT_ROUTER_STATE_TREE_HEADER","NEXT_URL","hash","existingSearch","search","rawQuery","startsWith","slice","pairs","split","filter","pair","NEXT_RSC_UNION_QUERY","length","push","join"],"mappings":"AAAA;;;;;;;;;;;;;;;;IA4BaA,0BAA0B;eAA1BA;;IA6BAC,kCAAkC;eAAlCA;;;yCAvDkC;kCAOxC;AAmBA,MAAMD,6BAA6B,CACxCE,KACAC;IAEA,MAAMC,iBAAiBC,IAAAA,uDAA8B,EACnDF,OAAO,CAACG,6CAA2B,CAAC,EACpCH,OAAO,CAACI,qDAAmC,CAAC,EAC5CJ,OAAO,CAACK,+CAA6B,CAAC,EACtCL,OAAO,CAACM,0BAAQ,CAAC;IAEnBR,mCAAmCC,KAAKE;AAC1C;AAkBO,MAAMH,qCAAqC,CAChDC,KACAQ;IAEA;;;;;;;;;;GAUC,GACD,MAAMC,iBAAiBT,IAAIU,MAAM;IACjC,MAAMC,WAAWF,eAAeG,UAAU,CAAC,OACvCH,eAAeI,KAAK,CAAC,KACrBJ;IAEJ,+EAA+E;IAC/E,6DAA6D;IAC7D,MAAMK,QAAQH,SACXI,KAAK,CAAC,KACNC,MAAM,CAAC,CAACC,OAASA,QAAQ,CAACA,KAAKL,UAAU,CAAC,GAAGM,sCAAoB,CAAC,CAAC,CAAC;IAEvE,IAAIV,KAAKW,MAAM,GAAG,GAAG;QACnBL,MAAMM,IAAI,CAAC,GAAGF,sCAAoB,CAAC,CAAC,EAAEV,MAAM;IAC9C,OAAO;QACLM,MAAMM,IAAI,CAAC,GAAGF,sCAAoB,EAAE;IACtC;IACAlB,IAAIU,MAAM,GAAGI,MAAMK,MAAM,GAAG,CAAC,CAAC,EAAEL,MAAMO,IAAI,CAAC,MAAM,GAAG;AACtD","ignoreList":[0]}
@@ -0,0 +1,2 @@
import type { FlightRouterState, FlightDataPath } from '../../../shared/lib/app-router-types';
export declare function shouldHardNavigate(flightSegmentPath: FlightDataPath, flightRouterState: FlightRouterState): boolean;
@@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "shouldHardNavigate", {
enumerable: true,
get: function() {
return shouldHardNavigate;
}
});
const _flightdatahelpers = require("../../flight-data-helpers");
const _matchsegments = require("../match-segments");
function shouldHardNavigate(flightSegmentPath, flightRouterState) {
const [segment, parallelRoutes] = flightRouterState;
// TODO-APP: Check if `as` can be replaced.
const [currentSegment, parallelRouteKey] = flightSegmentPath;
// Check if current segment matches the existing segment.
if (!(0, _matchsegments.matchSegment)(currentSegment, segment)) {
// If dynamic parameter in tree doesn't match up with segment path a hard navigation is triggered.
if (Array.isArray(currentSegment)) {
return true;
}
// If the existing segment did not match soft navigation is triggered.
return false;
}
const lastSegment = flightSegmentPath.length <= 2;
if (lastSegment) {
return false;
}
return shouldHardNavigate((0, _flightdatahelpers.getNextFlightSegmentPath)(flightSegmentPath), parallelRoutes[parallelRouteKey]);
}
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
Object.defineProperty(exports.default, '__esModule', { value: true });
Object.assign(exports.default, exports);
module.exports = exports.default;
}
//# sourceMappingURL=should-hard-navigate.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/client/components/router-reducer/should-hard-navigate.ts"],"sourcesContent":["import type {\n FlightRouterState,\n FlightDataPath,\n Segment,\n} from '../../../shared/lib/app-router-types'\nimport { getNextFlightSegmentPath } from '../../flight-data-helpers'\nimport { matchSegment } from '../match-segments'\n\n// TODO-APP: flightSegmentPath will be empty in case of static response, needs to be handled.\nexport function shouldHardNavigate(\n flightSegmentPath: FlightDataPath,\n flightRouterState: FlightRouterState\n): boolean {\n const [segment, parallelRoutes] = flightRouterState\n // TODO-APP: Check if `as` can be replaced.\n const [currentSegment, parallelRouteKey] = flightSegmentPath as [\n Segment,\n string,\n ]\n\n // Check if current segment matches the existing segment.\n if (!matchSegment(currentSegment, segment)) {\n // If dynamic parameter in tree doesn't match up with segment path a hard navigation is triggered.\n if (Array.isArray(currentSegment)) {\n return true\n }\n\n // If the existing segment did not match soft navigation is triggered.\n return false\n }\n const lastSegment = flightSegmentPath.length <= 2\n\n if (lastSegment) {\n return false\n }\n\n return shouldHardNavigate(\n getNextFlightSegmentPath(flightSegmentPath),\n parallelRoutes[parallelRouteKey]\n )\n}\n"],"names":["shouldHardNavigate","flightSegmentPath","flightRouterState","segment","parallelRoutes","currentSegment","parallelRouteKey","matchSegment","Array","isArray","lastSegment","length","getNextFlightSegmentPath"],"mappings":";;;;+BASgBA;;;eAAAA;;;mCAJyB;+BACZ;AAGtB,SAASA,mBACdC,iBAAiC,EACjCC,iBAAoC;IAEpC,MAAM,CAACC,SAASC,eAAe,GAAGF;IAClC,2CAA2C;IAC3C,MAAM,CAACG,gBAAgBC,iBAAiB,GAAGL;IAK3C,yDAAyD;IACzD,IAAI,CAACM,IAAAA,2BAAY,EAACF,gBAAgBF,UAAU;QAC1C,kGAAkG;QAClG,IAAIK,MAAMC,OAAO,CAACJ,iBAAiB;YACjC,OAAO;QACT;QAEA,sEAAsE;QACtE,OAAO;IACT;IACA,MAAMK,cAAcT,kBAAkBU,MAAM,IAAI;IAEhD,IAAID,aAAa;QACf,OAAO;IACT;IAEA,OAAOV,mBACLY,IAAAA,2CAAwB,EAACX,oBACzBG,cAAc,CAACE,iBAAiB;AAEpC","ignoreList":[0]}