UNPKG

next

Version:

The React Framework

274 lines (273 loc) • 13.2 kB
import { format as formatUrl, parse as parseUrl } from 'url'; import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'; import { getPathMatch } from '../shared/lib/router/utils/path-match'; import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex'; import { getRouteMatcher } from '../shared/lib/router/utils/route-matcher'; import { matchHas, prepareDestination } from '../shared/lib/router/utils/prepare-destination'; import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash'; import { normalizeRscURL } from '../shared/lib/router/utils/app-paths'; import { NEXT_INTERCEPTION_MARKER_PREFIX, NEXT_QUERY_PARAM_PREFIX } from '../lib/constants'; export function normalizeVercelUrl(req, trustQuery, paramKeys, pageIsDynamic, defaultRouteRegex) { // make sure to normalize req.url on Vercel to strip dynamic params // from the query which are added during routing if (pageIsDynamic && trustQuery && defaultRouteRegex) { const _parsedUrl = parseUrl(req.url, true); delete _parsedUrl.search; for (const key of Object.keys(_parsedUrl.query)){ const isNextQueryPrefix = key !== NEXT_QUERY_PARAM_PREFIX && key.startsWith(NEXT_QUERY_PARAM_PREFIX); const isNextInterceptionMarkerPrefix = key !== NEXT_INTERCEPTION_MARKER_PREFIX && key.startsWith(NEXT_INTERCEPTION_MARKER_PREFIX); if (isNextQueryPrefix || isNextInterceptionMarkerPrefix || (paramKeys || Object.keys(defaultRouteRegex.groups)).includes(key)) { delete _parsedUrl.query[key]; } } req.url = formatUrl(_parsedUrl); } } export function interpolateDynamicPath(pathname, params, defaultRouteRegex) { if (!defaultRouteRegex) return pathname; for (const param of Object.keys(defaultRouteRegex.groups)){ const { optional, repeat } = defaultRouteRegex.groups[param]; let builtParam = `[${repeat ? '...' : ''}${param}]`; if (optional) { builtParam = `[${builtParam}]`; } let paramValue; const value = params[param]; if (Array.isArray(value)) { paramValue = value.map((v)=>v && encodeURIComponent(v)).join('/'); } else if (value) { paramValue = encodeURIComponent(value); } else { paramValue = ''; } pathname = pathname.replaceAll(builtParam, paramValue); } return pathname; } export function normalizeDynamicRouteParams(params, ignoreOptional, defaultRouteRegex, defaultRouteMatches) { let hasValidParams = true; if (!defaultRouteRegex) return { params, hasValidParams: false }; params = Object.keys(defaultRouteRegex.groups).reduce((prev, key)=>{ let value = params[key]; if (typeof value === 'string') { value = normalizeRscURL(value); } if (Array.isArray(value)) { value = value.map((val)=>{ if (typeof val === 'string') { val = normalizeRscURL(val); } return val; }); } // if the value matches the default value we can't rely // on the parsed params, this is used to signal if we need // to parse x-now-route-matches or not const defaultValue = defaultRouteMatches[key]; const isOptional = defaultRouteRegex.groups[key].optional; const isDefaultValue = Array.isArray(defaultValue) ? defaultValue.some((defaultVal)=>{ return Array.isArray(value) ? value.some((val)=>val.includes(defaultVal)) : value == null ? void 0 : value.includes(defaultVal); }) : value == null ? void 0 : value.includes(defaultValue); if (isDefaultValue || typeof value === 'undefined' && !(isOptional && ignoreOptional)) { hasValidParams = false; } // non-provided optional values should be undefined so normalize // them to undefined if (isOptional && (!value || Array.isArray(value) && value.length === 1 && // fallback optional catch-all SSG pages have // [[...paramName]] for the root path on Vercel (value[0] === 'index' || value[0] === `[[...${key}]]`))) { value = undefined; delete params[key]; } // query values from the proxy aren't already split into arrays // so make sure to normalize catch-all values if (value && typeof value === 'string' && defaultRouteRegex.groups[key].repeat) { value = value.split('/'); } if (value) { prev[key] = value; } return prev; }, {}); return { params, hasValidParams }; } export function getUtils({ page, i18n, basePath, rewrites, pageIsDynamic, trailingSlash, caseSensitive }) { let defaultRouteRegex; let dynamicRouteMatcher; let defaultRouteMatches; if (pageIsDynamic) { defaultRouteRegex = getNamedRouteRegex(page, false); dynamicRouteMatcher = getRouteMatcher(defaultRouteRegex); defaultRouteMatches = dynamicRouteMatcher(page); } function handleRewrites(req, parsedUrl) { const rewriteParams = {}; let fsPathname = parsedUrl.pathname; const matchesPage = ()=>{ const fsPathnameNoSlash = removeTrailingSlash(fsPathname || ''); return fsPathnameNoSlash === removeTrailingSlash(page) || (dynamicRouteMatcher == null ? void 0 : dynamicRouteMatcher(fsPathnameNoSlash)); }; const checkRewrite = (rewrite)=>{ const matcher = getPathMatch(rewrite.source + (trailingSlash ? '(/)?' : ''), { removeUnnamedParams: true, strict: true, sensitive: !!caseSensitive }); let params = matcher(parsedUrl.pathname); if ((rewrite.has || rewrite.missing) && params) { const hasParams = matchHas(req, parsedUrl.query, rewrite.has, rewrite.missing); if (hasParams) { Object.assign(params, hasParams); } else { params = false; } } if (params) { const { parsedDestination, destQuery } = prepareDestination({ appendParamsToQuery: true, destination: rewrite.destination, params: params, query: parsedUrl.query }); // if the rewrite destination is external break rewrite chain if (parsedDestination.protocol) { return true; } Object.assign(rewriteParams, destQuery, params); Object.assign(parsedUrl.query, parsedDestination.query); delete parsedDestination.query; Object.assign(parsedUrl, parsedDestination); fsPathname = parsedUrl.pathname; if (basePath) { fsPathname = fsPathname.replace(new RegExp(`^${basePath}`), '') || '/'; } if (i18n) { const destLocalePathResult = normalizeLocalePath(fsPathname, i18n.locales); fsPathname = destLocalePathResult.pathname; parsedUrl.query.nextInternalLocale = destLocalePathResult.detectedLocale || params.nextInternalLocale; } if (fsPathname === page) { return true; } if (pageIsDynamic && dynamicRouteMatcher) { const dynamicParams = dynamicRouteMatcher(fsPathname); if (dynamicParams) { parsedUrl.query = { ...parsedUrl.query, ...dynamicParams }; return true; } } } return false; }; for (const rewrite of rewrites.beforeFiles || []){ checkRewrite(rewrite); } if (fsPathname !== page) { let finished = false; for (const rewrite of rewrites.afterFiles || []){ finished = checkRewrite(rewrite); if (finished) break; } if (!finished && !matchesPage()) { for (const rewrite of rewrites.fallback || []){ finished = checkRewrite(rewrite); if (finished) break; } } } return rewriteParams; } function getParamsFromRouteMatches(req, renderOpts, detectedLocale) { return getRouteMatcher(function() { const { groups, routeKeys } = defaultRouteRegex; return { re: { // Simulate a RegExp match from the \`req.url\` input exec: (str)=>{ const obj = Object.fromEntries(new URLSearchParams(str)); const matchesHasLocale = i18n && detectedLocale && obj['1'] === detectedLocale; for (const key of Object.keys(obj)){ const value = obj[key]; if (key !== NEXT_QUERY_PARAM_PREFIX && key.startsWith(NEXT_QUERY_PARAM_PREFIX)) { const normalizedKey = key.substring(NEXT_QUERY_PARAM_PREFIX.length); obj[normalizedKey] = value; delete obj[key]; } } // favor named matches if available const routeKeyNames = Object.keys(routeKeys || {}); const filterLocaleItem = (val)=>{ if (i18n) { // locale items can be included in route-matches // for fallback SSG pages so ensure they are // filtered const isCatchAll = Array.isArray(val); const _val = isCatchAll ? val[0] : val; if (typeof _val === 'string' && i18n.locales.some((item)=>{ if (item.toLowerCase() === _val.toLowerCase()) { detectedLocale = item; renderOpts.locale = detectedLocale; return true; } return false; })) { // remove the locale item from the match if (isCatchAll) { ; val.splice(0, 1); } // the value is only a locale item and // shouldn't be added return isCatchAll ? val.length === 0 : true; } } return false; }; if (routeKeyNames.every((name)=>obj[name])) { return routeKeyNames.reduce((prev, keyName)=>{ const paramName = routeKeys == null ? void 0 : routeKeys[keyName]; if (paramName && !filterLocaleItem(obj[keyName])) { prev[groups[paramName].pos] = obj[keyName]; } return prev; }, {}); } return Object.keys(obj).reduce((prev, key)=>{ if (!filterLocaleItem(obj[key])) { let normalizedKey = key; if (matchesHasLocale) { normalizedKey = parseInt(key, 10) - 1 + ''; } return Object.assign(prev, { [normalizedKey]: obj[key] }); } return prev; }, {}); } }, groups }; }())(req.headers['x-now-route-matches']); } return { handleRewrites, defaultRouteRegex, dynamicRouteMatcher, defaultRouteMatches, getParamsFromRouteMatches, normalizeDynamicRouteParams: (params, ignoreOptional)=>normalizeDynamicRouteParams(params, ignoreOptional, defaultRouteRegex, defaultRouteMatches), normalizeVercelUrl: (req, trustQuery, paramKeys)=>normalizeVercelUrl(req, trustQuery, paramKeys, pageIsDynamic, defaultRouteRegex), interpolateDynamicPath: (pathname, params)=>interpolateDynamicPath(pathname, params, defaultRouteRegex) }; } //# sourceMappingURL=server-utils.js.map