next
Version:
The React Framework
128 lines (127 loc) • 6.82 kB
JavaScript
import { addSearchParamsIfPageSegment, DEFAULT_SEGMENT_KEY, PAGE_SEGMENT_KEY } from '../shared/lib/segment';
import { ROOT_SEGMENT_REQUEST_KEY } from '../shared/lib/segment-cache/segment-value-encoding';
import { NEXT_REWRITTEN_PATH_HEADER, NEXT_REWRITTEN_QUERY_HEADER, NEXT_RSC_UNION_QUERY } from './components/app-router-headers';
export function getRenderedSearch(response) {
// If the server performed a rewrite, the search params used to render the
// page will be different from the params in the request URL. In this case,
// the response will include a header that gives the rewritten search query.
const rewrittenQuery = response.headers.get(NEXT_REWRITTEN_QUERY_HEADER);
if (rewrittenQuery !== null) {
return rewrittenQuery === '' ? '' : '?' + rewrittenQuery;
}
// If the header is not present, there was no rewrite, so we use the search
// query of the response URL.
return urlToUrlWithoutFlightMarker(new URL(response.url)).search;
}
export function getRenderedPathname(response) {
// If the server performed a rewrite, the pathname used to render the
// page will be different from the pathname in the request URL. In this case,
// the response will include a header that gives the rewritten pathname.
const rewrittenPath = response.headers.get(NEXT_REWRITTEN_PATH_HEADER);
return rewrittenPath != null ? rewrittenPath : urlToUrlWithoutFlightMarker(new URL(response.url)).pathname;
}
export function parseDynamicParamFromURLPart(paramType, pathnameParts, partIndex) {
// This needs to match the behavior in get-dynamic-param.ts.
switch(paramType){
// Catchalls
case 'c':
case 'ci':
{
// Catchalls receive all the remaining URL parts. If there are no
// remaining pathname parts, return an empty array.
return partIndex < pathnameParts.length ? pathnameParts.slice(partIndex).map((s)=>encodeURIComponent(s)) : [];
}
// Optional catchalls
case 'oc':
{
// Optional catchalls receive all the remaining URL parts, unless this is
// the end of the pathname, in which case they return null.
return partIndex < pathnameParts.length ? pathnameParts.slice(partIndex).map((s)=>encodeURIComponent(s)) : null;
}
// Dynamic
case 'd':
case 'di':
{
if (partIndex >= pathnameParts.length) {
// The route tree expected there to be more parts in the URL than there
// actually are. This could happen if the x-nextjs-rewritten-path header
// is incorrectly set, or potentially due to bug in Next.js. TODO:
// Should this be a hard error? During a prefetch, we can just abort.
// During a client navigation, we could trigger a hard refresh. But if
// it happens during initial render, we don't really have any
// recovery options.
return '';
}
return encodeURIComponent(pathnameParts[partIndex]);
}
default:
paramType;
return '';
}
}
export function doesStaticSegmentAppearInURL(segment) {
// This is not a parameterized segment; however, we need to determine
// whether or not this segment appears in the URL. For example, this route
// groups do not appear in the URL, so they should be skipped. Any other
// special cases must be handled here.
// TODO: Consider encoding this directly into the router tree instead of
// inferring it on the client based on the segment type. Something like
// a `doesAppearInURL` flag in FlightRouterState.
if (segment === ROOT_SEGMENT_REQUEST_KEY || // For some reason, the loader tree sometimes includes extra __PAGE__
// "layouts" when part of a parallel route. But it's not a leaf node.
// Otherwise, we wouldn't need this special case because pages are
// always leaf nodes.
// TODO: Investigate why the loader produces these fake page segments.
segment.startsWith(PAGE_SEGMENT_KEY) || // Route groups.
segment[0] === '(' && segment.endsWith(')') || segment === DEFAULT_SEGMENT_KEY || segment === '/_not-found') {
return false;
} else {
// All other segment types appear in the URL
return true;
}
}
export function getCacheKeyForDynamicParam(paramValue, renderedSearch) {
// This needs to match the logic in get-dynamic-param.ts, until we're able to
// unify the various implementations so that these are always computed on
// the client.
if (typeof paramValue === 'string') {
// TODO: Refactor or remove this helper function to accept a string rather
// than the whole segment type. Also we can probably just append the
// search string instead of turning it into JSON.
const pageSegmentWithSearchParams = addSearchParamsIfPageSegment(paramValue, Object.fromEntries(new URLSearchParams(renderedSearch)));
return pageSegmentWithSearchParams;
} else if (paramValue === null) {
return '';
} else {
return paramValue.join('/');
}
}
export function urlToUrlWithoutFlightMarker(url) {
const urlWithoutFlightParameters = new URL(url);
urlWithoutFlightParameters.searchParams.delete(NEXT_RSC_UNION_QUERY);
if (process.env.NODE_ENV === 'production') {
if (process.env.__NEXT_CONFIG_OUTPUT === 'export' && urlWithoutFlightParameters.pathname.endsWith('.txt')) {
const { pathname } = urlWithoutFlightParameters;
const length = pathname.endsWith('/index.txt') ? 10 : 4;
// Slice off `/index.txt` or `.txt` from the end of the pathname
urlWithoutFlightParameters.pathname = pathname.slice(0, -length);
}
}
return urlWithoutFlightParameters;
}
export function getParamValueFromCacheKey(paramCacheKey, paramType) {
// Turn the cache key string sent by the server (as part of FlightRouterState)
// into a value that can be passed to `useParams` and client components.
const isCatchAll = paramType === 'c' || paramType === 'oc';
if (isCatchAll) {
// Catch-all param keys are a concatenation of the path segments.
// See equivalent logic in `getSelectedParams`.
// TODO: We should just pass the array directly, rather than concatenate
// it to a string and then split it back to an array. It needs to be an
// array in some places, like when passing a key React, but we can convert
// it at runtime in those places.
return paramCacheKey.split('/');
}
return paramCacheKey;
}
//# sourceMappingURL=route-params.js.map