UNPKG

next

Version:

The React Framework

811 lines • 148 kB
import { getFallbackRouteParams } from './request/fallback-params'; import { CachedRouteKind } from './response-cache'; import { NormalizeError, DecodeError, normalizeRepeatedSlashes, MissingStaticPage } from '../shared/lib/utils'; import { format as formatUrl, parse as parseUrl } from 'url'; import { formatHostname } from './lib/format-hostname'; import { getRedirectStatus } from '../lib/redirect-status'; import { isEdgeRuntime } from '../lib/is-edge-runtime'; import { APP_PATHS_MANIFEST, NEXT_BUILTIN_DOCUMENT, PAGES_MANIFEST, STATIC_STATUS_PAGES, UNDERSCORE_NOT_FOUND_ROUTE, UNDERSCORE_NOT_FOUND_ROUTE_ENTRY } from '../shared/lib/constants'; import { isDynamicRoute } from '../shared/lib/router/utils'; import { checkIsOnDemandRevalidate } from './api-utils'; import { setConfig } from '../shared/lib/runtime-config.external'; import { getCacheControlHeader } from './lib/cache-control'; import { execOnce } from '../shared/lib/utils'; import { isBlockedPage } from './utils'; import { getBotType, isBot } from '../shared/lib/router/utils/is-bot'; import RenderResult from './render-result'; import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash'; import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-path'; import * as Log from '../build/output/log'; import { getPreviouslyRevalidatedTags, getUtils } from './server-utils'; import isError, { getProperError } from '../lib/is-error'; import { addRequestMeta, getRequestMeta, removeRequestMeta, setRequestMeta } from './request-meta'; import { removePathPrefix } from '../shared/lib/router/utils/remove-path-prefix'; import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'; import { getHostname } from '../shared/lib/get-hostname'; import { parseUrl as parseUrlUtil } from '../shared/lib/router/utils/parse-url'; import { getNextPathnameInfo } from '../shared/lib/router/utils/get-next-pathname-info'; import { RSC_HEADER, NEXT_RSC_UNION_QUERY, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_DID_POSTPONE_HEADER, NEXT_URL, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_IS_PRERENDER_HEADER } from '../client/components/app-router-headers'; import { LocaleRouteNormalizer } from './normalizers/locale-route-normalizer'; import { DefaultRouteMatcherManager } from './route-matcher-managers/default-route-matcher-manager'; import { AppPageRouteMatcherProvider } from './route-matcher-providers/app-page-route-matcher-provider'; import { AppRouteRouteMatcherProvider } from './route-matcher-providers/app-route-route-matcher-provider'; import { PagesAPIRouteMatcherProvider } from './route-matcher-providers/pages-api-route-matcher-provider'; import { PagesRouteMatcherProvider } from './route-matcher-providers/pages-route-matcher-provider'; import { ServerManifestLoader } from './route-matcher-providers/helpers/manifest-loaders/server-manifest-loader'; import { getTracer, isBubbledError, SpanKind } from './lib/trace/tracer'; import { BaseServerSpan } from './lib/trace/constants'; import { I18NProvider } from './lib/i18n-provider'; import { sendResponse } from './send-response'; import { fromNodeOutgoingHttpHeaders, normalizeNextQueryParam, toNodeOutgoingHttpHeaders } from './web/utils'; import { CACHE_ONE_YEAR, INFINITE_CACHE, MATCHED_PATH_HEADER, NEXT_CACHE_TAGS_HEADER, NEXT_RESUME_HEADER } from '../lib/constants'; import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'; import { NextRequestAdapter, signalFromNodeResponse } from './web/spec-extension/adapters/next-request'; import { matchNextDataPathname } from './lib/match-next-data-pathname'; import getRouteFromAssetPath from '../shared/lib/router/utils/get-route-from-asset-path'; import { decodePathParams } from './lib/router-utils/decode-path-params'; import { RSCPathnameNormalizer } from './normalizers/request/rsc'; import { stripFlightHeaders } from './app-render/strip-flight-headers'; import { isAppPageRouteModule, isAppRouteRouteModule, isPagesRouteModule } from './route-modules/checks'; import { PrefetchRSCPathnameNormalizer } from './normalizers/request/prefetch-rsc'; import { NextDataPathnameNormalizer } from './normalizers/request/next-data'; import { getIsPossibleServerAction } from './lib/server-action-request-meta'; import { isInterceptionRouteAppPath } from '../shared/lib/router/utils/interception-routes'; import { toRoute } from './lib/to-route'; import { isNodeNextRequest, isNodeNextResponse } from './base-http/helpers'; import { patchSetHeaderWithCookieSupport } from './lib/patch-set-header'; import { checkIsAppPPREnabled } from './lib/experimental/ppr'; import { getBuiltinRequestContext } from './after/builtin-request-context'; import { ENCODED_TAGS } from './stream-utils/encodedTags'; import { NextRequestHint } from './web/adapter'; import { getRevalidateReason } from './instrumentation/utils'; import { RouteKind } from './route-kind'; import { FallbackMode, parseFallbackField } from '../lib/fallback'; import { toResponseCacheEntry } from './response-cache/utils'; import { scheduleOnNextTick } from '../lib/scheduler'; import { SegmentPrefixRSCPathnameNormalizer } from './normalizers/request/segment-prefix-rsc'; import { shouldServeStreamingMetadata, isHtmlBotRequest } from './lib/streaming-metadata'; import { InvariantError } from '../shared/lib/invariant-error'; import { decodeQueryPathParameter } from './lib/decode-query-path-parameter'; import { getCacheHandlers } from './use-cache/handlers'; export class NoFallbackError extends Error { } // Internal wrapper around build errors at development // time, to prevent us from propagating or logging them export class WrappedBuildError extends Error { constructor(innerError){ super(); this.innerError = innerError; } } export default class Server { getServerComponentsHmrCache() { return this.nextConfig.experimental.serverComponentsHmrCache ? globalThis.__serverComponentsHmrCache : undefined; } /** * This is used to persist cache scopes across * prefetch -> full route requests for dynamic IO * it's only fully used in dev */ constructor(options){ var _this_nextConfig_i18n, _this_nextConfig_experimental_amp, _this_nextConfig_i18n1; this.handleRSCRequest = (req, _res, parsedUrl)=>{ var _this_normalizers_segmentPrefetchRSC, _this_normalizers_prefetchRSC, _this_normalizers_rsc; if (!parsedUrl.pathname) return false; if ((_this_normalizers_segmentPrefetchRSC = this.normalizers.segmentPrefetchRSC) == null ? void 0 : _this_normalizers_segmentPrefetchRSC.match(parsedUrl.pathname)) { const result = this.normalizers.segmentPrefetchRSC.extract(parsedUrl.pathname); if (!result) return false; const { originalPathname, segmentPath } = result; parsedUrl.pathname = originalPathname; // Mark the request as a router prefetch request. req.headers[RSC_HEADER.toLowerCase()] = '1'; req.headers[NEXT_ROUTER_PREFETCH_HEADER.toLowerCase()] = '1'; req.headers[NEXT_ROUTER_SEGMENT_PREFETCH_HEADER.toLowerCase()] = segmentPath; addRequestMeta(req, 'isRSCRequest', true); addRequestMeta(req, 'isPrefetchRSCRequest', true); addRequestMeta(req, 'segmentPrefetchRSCRequest', segmentPath); } else if ((_this_normalizers_prefetchRSC = this.normalizers.prefetchRSC) == null ? void 0 : _this_normalizers_prefetchRSC.match(parsedUrl.pathname)) { parsedUrl.pathname = this.normalizers.prefetchRSC.normalize(parsedUrl.pathname, true); // Mark the request as a router prefetch request. req.headers[RSC_HEADER.toLowerCase()] = '1'; req.headers[NEXT_ROUTER_PREFETCH_HEADER.toLowerCase()] = '1'; addRequestMeta(req, 'isRSCRequest', true); addRequestMeta(req, 'isPrefetchRSCRequest', true); } else if ((_this_normalizers_rsc = this.normalizers.rsc) == null ? void 0 : _this_normalizers_rsc.match(parsedUrl.pathname)) { parsedUrl.pathname = this.normalizers.rsc.normalize(parsedUrl.pathname, true); // Mark the request as a RSC request. req.headers[RSC_HEADER.toLowerCase()] = '1'; addRequestMeta(req, 'isRSCRequest', true); } else if (req.headers['x-now-route-matches']) { // If we didn't match, return with the flight headers stripped. If in // minimal mode we didn't match based on the path, this can't be a RSC // request. This is because Vercel only sends this header during // revalidation requests and we want the cache to instead depend on the // request path for flight information. stripFlightHeaders(req.headers); return false; } else if (req.headers[RSC_HEADER.toLowerCase()] === '1') { addRequestMeta(req, 'isRSCRequest', true); if (req.headers[NEXT_ROUTER_PREFETCH_HEADER.toLowerCase()] === '1') { addRequestMeta(req, 'isPrefetchRSCRequest', true); const segmentPrefetchRSCRequest = req.headers[NEXT_ROUTER_SEGMENT_PREFETCH_HEADER.toLowerCase()]; if (typeof segmentPrefetchRSCRequest === 'string') { addRequestMeta(req, 'segmentPrefetchRSCRequest', segmentPrefetchRSCRequest); } } } else { // Otherwise just return without doing anything. return false; } if (req.url) { const parsed = parseUrl(req.url); parsed.pathname = parsedUrl.pathname; req.url = formatUrl(parsed); } return false; }; this.handleNextDataRequest = async (req, res, parsedUrl)=>{ const middleware = await this.getMiddleware(); const params = matchNextDataPathname(parsedUrl.pathname); // ignore for non-next data URLs if (!params || !params.path) { return false; } if (params.path[0] !== this.buildId) { // Ignore if its a middleware request when we aren't on edge. if (process.env.NEXT_RUNTIME !== 'edge' && getRequestMeta(req, 'middlewareInvoke')) { return false; } // Make sure to 404 if the buildId isn't correct await this.render404(req, res, parsedUrl); return true; } // remove buildId from URL params.path.shift(); const lastParam = params.path[params.path.length - 1]; // show 404 if it doesn't end with .json if (typeof lastParam !== 'string' || !lastParam.endsWith('.json')) { await this.render404(req, res, parsedUrl); return true; } // re-create page's pathname let pathname = `/${params.path.join('/')}`; pathname = getRouteFromAssetPath(pathname, '.json'); // ensure trailing slash is normalized per config if (middleware) { if (this.nextConfig.trailingSlash && !pathname.endsWith('/')) { pathname += '/'; } if (!this.nextConfig.trailingSlash && pathname.length > 1 && pathname.endsWith('/')) { pathname = pathname.substring(0, pathname.length - 1); } } if (this.i18nProvider) { var _req_headers_host; // Remove the port from the hostname if present. const hostname = req == null ? void 0 : (_req_headers_host = req.headers.host) == null ? void 0 : _req_headers_host.split(':', 1)[0].toLowerCase(); const domainLocale = this.i18nProvider.detectDomainLocale(hostname); const defaultLocale = (domainLocale == null ? void 0 : domainLocale.defaultLocale) ?? this.i18nProvider.config.defaultLocale; const localePathResult = this.i18nProvider.analyze(pathname); // If the locale is detected from the path, we need to remove it // from the pathname. if (localePathResult.detectedLocale) { pathname = localePathResult.pathname; } // Update the query with the detected locale and default locale. addRequestMeta(req, 'locale', localePathResult.detectedLocale); addRequestMeta(req, 'defaultLocale', defaultLocale); // If the locale is not detected from the path, we need to mark that // it was not inferred from default. if (!localePathResult.detectedLocale) { removeRequestMeta(req, 'localeInferredFromDefault'); } // If no locale was detected and we don't have middleware, we need // to render a 404 page. if (!localePathResult.detectedLocale && !middleware) { addRequestMeta(req, 'locale', defaultLocale); await this.render404(req, res, parsedUrl); return true; } } parsedUrl.pathname = pathname; addRequestMeta(req, 'isNextDataReq', true); return false; }; this.handleNextImageRequest = ()=>false; this.handleCatchallRenderRequest = ()=>false; this.handleCatchallMiddlewareRequest = ()=>false; /** * Normalizes a pathname without attaching any metadata from any matched * normalizer. * * @param pathname the pathname to normalize * @returns the normalized pathname */ this.normalize = (pathname)=>{ const normalizers = []; if (this.normalizers.data) { normalizers.push(this.normalizers.data); } // We have to put the segment prefetch normalizer before the RSC normalizer // because the RSC normalizer will match the prefetch RSC routes too. if (this.normalizers.segmentPrefetchRSC) { normalizers.push(this.normalizers.segmentPrefetchRSC); } // We have to put the prefetch normalizer before the RSC normalizer // because the RSC normalizer will match the prefetch RSC routes too. if (this.normalizers.prefetchRSC) { normalizers.push(this.normalizers.prefetchRSC); } if (this.normalizers.rsc) { normalizers.push(this.normalizers.rsc); } for (const normalizer of normalizers){ if (!normalizer.match(pathname)) continue; return normalizer.normalize(pathname, true); } return pathname; }; this.normalizeAndAttachMetadata = async (req, res, url)=>{ let finished = await this.handleNextImageRequest(req, res, url); if (finished) return true; if (this.enabledDirectories.pages) { finished = await this.handleNextDataRequest(req, res, url); if (finished) return true; } return false; }; this.prepared = false; this.preparedPromise = null; this.customErrorNo404Warn = execOnce(()=>{ Log.warn(`You have added a custom /_error page without a custom /404 page. This prevents the 404 page from being auto statically optimized.\nSee here for info: https://nextjs.org/docs/messages/custom-error-no-custom-404`); }); const { dir = '.', quiet = false, conf, dev = false, minimalMode = false, hostname, port, experimentalTestProxy } = options; this.experimentalTestProxy = experimentalTestProxy; this.serverOptions = options; this.dir = process.env.NEXT_RUNTIME === 'edge' ? dir : require('path').resolve(dir); this.quiet = quiet; this.loadEnvConfig({ dev }); // TODO: should conf be normalized to prevent missing // values from causing issues as this can be user provided this.nextConfig = conf; this.hostname = hostname; if (this.hostname) { // we format the hostname so that it can be fetched this.fetchHostname = formatHostname(this.hostname); } this.port = port; this.distDir = process.env.NEXT_RUNTIME === 'edge' ? this.nextConfig.distDir : require('path').join(this.dir, this.nextConfig.distDir); this.publicDir = this.getPublicDir(); this.hasStaticDir = !minimalMode && this.getHasStaticDir(); this.i18nProvider = ((_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales) ? new I18NProvider(this.nextConfig.i18n) : undefined; // Configure the locale normalizer, it's used for routes inside `pages/`. this.localeNormalizer = this.i18nProvider ? new LocaleRouteNormalizer(this.i18nProvider) : undefined; // Only serverRuntimeConfig needs the default // publicRuntimeConfig gets it's default in client/index.js const { serverRuntimeConfig = {}, publicRuntimeConfig, assetPrefix, generateEtags } = this.nextConfig; this.buildId = this.getBuildId(); // this is a hack to avoid Webpack knowing this is equal to this.minimalMode // because we replace this.minimalMode to true in production bundles. const minimalModeKey = 'minimalMode'; this[minimalModeKey] = minimalMode || !!process.env.NEXT_PRIVATE_MINIMAL_MODE; this.enabledDirectories = this.getEnabledDirectories(dev); this.isAppPPREnabled = this.enabledDirectories.app && checkIsAppPPREnabled(this.nextConfig.experimental.ppr); this.isAppSegmentPrefetchEnabled = this.enabledDirectories.app && this.nextConfig.experimental.clientSegmentCache === true; this.normalizers = { // We should normalize the pathname from the RSC prefix only in minimal // mode as otherwise that route is not exposed external to the server as // we instead only rely on the headers. rsc: this.enabledDirectories.app && this.minimalMode ? new RSCPathnameNormalizer() : undefined, prefetchRSC: this.isAppPPREnabled && this.minimalMode ? new PrefetchRSCPathnameNormalizer() : undefined, segmentPrefetchRSC: this.isAppSegmentPrefetchEnabled && this.minimalMode ? new SegmentPrefixRSCPathnameNormalizer() : undefined, data: this.enabledDirectories.pages ? new NextDataPathnameNormalizer(this.buildId) : undefined }; this.nextFontManifest = this.getNextFontManifest(); if (process.env.NEXT_RUNTIME !== 'edge') { process.env.NEXT_DEPLOYMENT_ID = this.nextConfig.deploymentId || ''; } this.renderOpts = { supportsDynamicResponse: true, trailingSlash: this.nextConfig.trailingSlash, deploymentId: this.nextConfig.deploymentId, strictNextHead: this.nextConfig.experimental.strictNextHead ?? true, poweredByHeader: this.nextConfig.poweredByHeader, canonicalBase: this.nextConfig.amp.canonicalBase || '', generateEtags, previewProps: this.getPrerenderManifest().preview, ampOptimizerConfig: (_this_nextConfig_experimental_amp = this.nextConfig.experimental.amp) == null ? void 0 : _this_nextConfig_experimental_amp.optimizer, basePath: this.nextConfig.basePath, images: this.nextConfig.images, optimizeCss: this.nextConfig.experimental.optimizeCss, nextConfigOutput: this.nextConfig.output, nextScriptWorkers: this.nextConfig.experimental.nextScriptWorkers, disableOptimizedLoading: this.nextConfig.experimental.disableOptimizedLoading, domainLocales: (_this_nextConfig_i18n1 = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n1.domains, distDir: this.distDir, serverComponents: this.enabledDirectories.app, cacheLifeProfiles: this.nextConfig.experimental.cacheLife, enableTainting: this.nextConfig.experimental.taint, crossOrigin: this.nextConfig.crossOrigin ? this.nextConfig.crossOrigin : undefined, largePageDataBytes: this.nextConfig.experimental.largePageDataBytes, // Only the `publicRuntimeConfig` key is exposed to the client side // It'll be rendered as part of __NEXT_DATA__ on the client side runtimeConfig: Object.keys(publicRuntimeConfig).length > 0 ? publicRuntimeConfig : undefined, // @ts-expect-error internal field not publicly exposed isExperimentalCompile: this.nextConfig.experimental.isExperimentalCompile, // `htmlLimitedBots` is passed to server as serialized config in string format htmlLimitedBots: this.nextConfig.htmlLimitedBots, experimental: { expireTime: this.nextConfig.expireTime, staleTimes: this.nextConfig.experimental.staleTimes, clientTraceMetadata: this.nextConfig.experimental.clientTraceMetadata, dynamicIO: this.nextConfig.experimental.dynamicIO ?? false, clientSegmentCache: this.nextConfig.experimental.clientSegmentCache === 'client-only' ? 'client-only' : Boolean(this.nextConfig.experimental.clientSegmentCache), dynamicOnHover: this.nextConfig.experimental.dynamicOnHover ?? false, inlineCss: this.nextConfig.experimental.inlineCss ?? false, authInterrupts: !!this.nextConfig.experimental.authInterrupts }, onInstrumentationRequestError: this.instrumentationOnRequestError.bind(this), reactMaxHeadersLength: this.nextConfig.reactMaxHeadersLength }; // Initialize next/config with the environment configuration setConfig({ serverRuntimeConfig, publicRuntimeConfig }); this.pagesManifest = this.getPagesManifest(); this.appPathsManifest = this.getAppPathsManifest(); this.appPathRoutes = this.getAppPathRoutes(); this.interceptionRoutePatterns = this.getinterceptionRoutePatterns(); // Configure the routes. this.matchers = this.getRouteMatchers(); // Start route compilation. We don't wait for the routes to finish loading // because we use the `waitTillReady` promise below in `handleRequest` to // wait. Also we can't `await` in the constructor. void this.matchers.reload(); this.setAssetPrefix(assetPrefix); this.responseCache = this.getResponseCache({ dev }); } reloadMatchers() { return this.matchers.reload(); } getRouteMatchers() { // Create a new manifest loader that get's the manifests from the server. const manifestLoader = new ServerManifestLoader((name)=>{ switch(name){ case PAGES_MANIFEST: return this.getPagesManifest() ?? null; case APP_PATHS_MANIFEST: return this.getAppPathsManifest() ?? null; default: return null; } }); // Configure the matchers and handlers. const matchers = new DefaultRouteMatcherManager(); // Match pages under `pages/`. matchers.push(new PagesRouteMatcherProvider(this.distDir, manifestLoader, this.i18nProvider)); // Match api routes under `pages/api/`. matchers.push(new PagesAPIRouteMatcherProvider(this.distDir, manifestLoader, this.i18nProvider)); // If the app directory is enabled, then add the app matchers and handlers. if (this.enabledDirectories.app) { // Match app pages under `app/`. matchers.push(new AppPageRouteMatcherProvider(this.distDir, manifestLoader)); matchers.push(new AppRouteRouteMatcherProvider(this.distDir, manifestLoader)); } return matchers; } async instrumentationOnRequestError(...args) { const [err, req, ctx] = args; if (this.instrumentation) { try { await (this.instrumentation.onRequestError == null ? void 0 : this.instrumentation.onRequestError.call(this.instrumentation, err, { path: req.url || '', method: req.method || 'GET', // Normalize middleware headers and other server request headers headers: req instanceof NextRequestHint ? Object.fromEntries(req.headers.entries()) : req.headers }, ctx)); } catch (handlerErr) { // Log the soft error and continue, since errors can thrown from react stream handler console.error('Error in instrumentation.onRequestError:', handlerErr); } } } logError(err) { if (this.quiet) return; Log.error(err); } async handleRequest(req, res, parsedUrl) { await this.prepare(); const method = req.method.toUpperCase(); const tracer = getTracer(); return tracer.withPropagatedContext(req.headers, ()=>{ return tracer.trace(BaseServerSpan.handleRequest, { spanName: `${method} ${req.url}`, kind: SpanKind.SERVER, attributes: { 'http.method': method, 'http.target': req.url } }, async (span)=>this.handleRequestImpl(req, res, parsedUrl).finally(()=>{ if (!span) return; const isRSCRequest = getRequestMeta(req, 'isRSCRequest') ?? false; span.setAttributes({ 'http.status_code': res.statusCode, 'next.rsc': isRSCRequest }); const rootSpanAttributes = tracer.getRootSpanAttributes(); // We were unable to get attributes, probably OTEL is not enabled if (!rootSpanAttributes) return; if (rootSpanAttributes.get('next.span_type') !== BaseServerSpan.handleRequest) { console.warn(`Unexpected root span type '${rootSpanAttributes.get('next.span_type')}'. Please report this Next.js issue https://github.com/vercel/next.js`); return; } const route = rootSpanAttributes.get('next.route'); if (route) { const name = isRSCRequest ? `RSC ${method} ${route}` : `${method} ${route}`; span.setAttributes({ 'next.route': route, 'http.route': route, 'next.span_name': name }); span.updateName(name); } else { span.updateName(isRSCRequest ? `RSC ${method} ${req.url}` : `${method} ${req.url}`); } })); }); } async handleRequestImpl(req, res, parsedUrl) { try { var _originalRequest_socket, _originalRequest_socket1, _this_i18nProvider, _this_nextConfig_i18n; // Wait for the matchers to be ready. await this.matchers.waitTillReady(); // ensure cookies set in middleware are merged and // not overridden by API routes/getServerSideProps patchSetHeaderWithCookieSupport(req, isNodeNextResponse(res) ? res.originalResponse : res); const urlParts = (req.url || '').split('?', 1); const urlNoQuery = urlParts[0]; // this normalizes repeated slashes in the path e.g. hello//world -> // hello/world or backslashes to forward slashes, this does not // handle trailing slash as that is handled the same as a next.config.js // redirect if (urlNoQuery == null ? void 0 : urlNoQuery.match(/(\\|\/\/)/)) { const cleanUrl = normalizeRepeatedSlashes(req.url); res.redirect(cleanUrl, 308).body(cleanUrl).send(); return; } // Parse url if parsedUrl not provided if (!parsedUrl || typeof parsedUrl !== 'object') { if (!req.url) { throw Object.defineProperty(new Error('Invariant: url can not be undefined'), "__NEXT_ERROR_CODE", { value: "E123", enumerable: false, configurable: true }); } parsedUrl = parseUrl(req.url, true); } if (!parsedUrl.pathname) { throw Object.defineProperty(new Error("Invariant: pathname can't be empty"), "__NEXT_ERROR_CODE", { value: "E412", enumerable: false, configurable: true }); } // Parse the querystring ourselves if the user doesn't handle querystring parsing if (typeof parsedUrl.query === 'string') { parsedUrl.query = Object.fromEntries(new URLSearchParams(parsedUrl.query)); } // Update the `x-forwarded-*` headers. const { originalRequest = null } = isNodeNextRequest(req) ? req : {}; const xForwardedProto = originalRequest == null ? void 0 : originalRequest.headers['x-forwarded-proto']; const isHttps = xForwardedProto ? xForwardedProto === 'https' : !!(originalRequest == null ? void 0 : (_originalRequest_socket = originalRequest.socket) == null ? void 0 : _originalRequest_socket.encrypted); req.headers['x-forwarded-host'] ??= req.headers['host'] ?? this.hostname; req.headers['x-forwarded-port'] ??= this.port ? this.port.toString() : isHttps ? '443' : '80'; req.headers['x-forwarded-proto'] ??= isHttps ? 'https' : 'http'; req.headers['x-forwarded-for'] ??= originalRequest == null ? void 0 : (_originalRequest_socket1 = originalRequest.socket) == null ? void 0 : _originalRequest_socket1.remoteAddress; // This should be done before any normalization of the pathname happens as // it captures the initial URL. this.attachRequestMeta(req, parsedUrl); let finished = await this.handleRSCRequest(req, res, parsedUrl); if (finished) return; const domainLocale = (_this_i18nProvider = this.i18nProvider) == null ? void 0 : _this_i18nProvider.detectDomainLocale(getHostname(parsedUrl, req.headers)); const defaultLocale = (domainLocale == null ? void 0 : domainLocale.defaultLocale) || ((_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.defaultLocale); addRequestMeta(req, 'defaultLocale', defaultLocale); const url = parseUrlUtil(req.url.replace(/^\/+/, '/')); const pathnameInfo = getNextPathnameInfo(url.pathname, { nextConfig: this.nextConfig, i18nProvider: this.i18nProvider }); url.pathname = pathnameInfo.pathname; if (pathnameInfo.basePath) { req.url = removePathPrefix(req.url, this.nextConfig.basePath); } const useMatchedPathHeader = this.minimalMode && typeof req.headers[MATCHED_PATH_HEADER] === 'string'; // TODO: merge handling with invokePath if (useMatchedPathHeader) { try { var _this_normalizers_data, _this_i18nProvider1, _this_getRoutesManifest; if (this.enabledDirectories.app) { // ensure /index path is normalized for prerender // in minimal mode if (req.url.match(/^\/index($|\?)/)) { req.url = req.url.replace(/^\/index/, '/'); } parsedUrl.pathname = parsedUrl.pathname === '/index' ? '/' : parsedUrl.pathname; } // x-matched-path is the source of truth, it tells what page // should be rendered because we don't process rewrites in minimalMode let { pathname: matchedPath } = new URL(req.headers[MATCHED_PATH_HEADER], 'http://localhost'); let { pathname: urlPathname } = new URL(req.url, 'http://localhost'); // For ISR the URL is normalized to the prerenderPath so if // it's a data request the URL path will be the data URL, // basePath is already stripped by this point if ((_this_normalizers_data = this.normalizers.data) == null ? void 0 : _this_normalizers_data.match(urlPathname)) { addRequestMeta(req, 'isNextDataReq', true); } else if (this.isAppPPREnabled && this.minimalMode && req.headers[NEXT_RESUME_HEADER] === '1' && req.method === 'POST') { // Decode the postponed state from the request body, it will come as // an array of buffers, so collect them and then concat them to form // the string. const body = []; for await (const chunk of req.body){ body.push(chunk); } const postponed = Buffer.concat(body).toString('utf8'); addRequestMeta(req, 'postponed', postponed); } matchedPath = this.normalize(matchedPath); const normalizedUrlPath = this.stripNextDataPath(urlPathname); // Perform locale detection and normalization. const localeAnalysisResult = (_this_i18nProvider1 = this.i18nProvider) == null ? void 0 : _this_i18nProvider1.analyze(matchedPath, { defaultLocale }); // The locale result will be defined even if the locale was not // detected for the request because it will be inferred from the // default locale. if (localeAnalysisResult) { addRequestMeta(req, 'locale', localeAnalysisResult.detectedLocale); // If the detected locale was inferred from the default locale, we // need to modify the metadata on the request to indicate that. if (localeAnalysisResult.inferredFromDefault) { addRequestMeta(req, 'localeInferredFromDefault', true); } else { removeRequestMeta(req, 'localeInferredFromDefault'); } } // TODO: check if this is needed any more? matchedPath = denormalizePagePath(matchedPath); let srcPathname = matchedPath; let pageIsDynamic = isDynamicRoute(srcPathname); if (!pageIsDynamic) { const match = await this.matchers.match(srcPathname, { i18n: localeAnalysisResult }); // Update the source pathname to the matched page's pathname. if (match) { srcPathname = match.definition.pathname; // The page is dynamic if the params are defined. pageIsDynamic = typeof match.params !== 'undefined'; } } // The rest of this function can't handle i18n properly, so ensure we // restore the pathname with the locale information stripped from it // now that we're done matching if we're using i18n. if (localeAnalysisResult) { matchedPath = localeAnalysisResult.pathname; } const utils = getUtils({ pageIsDynamic, page: srcPathname, i18n: this.nextConfig.i18n, basePath: this.nextConfig.basePath, rewrites: ((_this_getRoutesManifest = this.getRoutesManifest()) == null ? void 0 : _this_getRoutesManifest.rewrites) || { beforeFiles: [], afterFiles: [], fallback: [] }, caseSensitive: !!this.nextConfig.experimental.caseSensitiveRoutes }); // Ensure parsedUrl.pathname includes locale before processing // rewrites or they won't match correctly. if (defaultLocale && !pathnameInfo.locale) { parsedUrl.pathname = `/${defaultLocale}${parsedUrl.pathname}`; } const pathnameBeforeRewrite = parsedUrl.pathname; const rewriteParamKeys = Object.keys(utils.handleRewrites(req, parsedUrl)); const didRewrite = pathnameBeforeRewrite !== parsedUrl.pathname; if (didRewrite && parsedUrl.pathname) { addRequestMeta(req, 'rewroteURL', parsedUrl.pathname); } // Create a copy of the query params to avoid mutating the original // object. This prevents any overlapping query params that have the // same normalized key from causing issues. const queryParams = { ...parsedUrl.query }; for (const [key, value] of Object.entries(parsedUrl.query)){ const normalizedKey = normalizeNextQueryParam(key); if (!normalizedKey) continue; // Remove the prefixed key from the query params because we want // to consume it for the dynamic route matcher. delete parsedUrl.query[key]; if (typeof value === 'undefined') continue; queryParams[normalizedKey] = Array.isArray(value) ? value.map((v)=>decodeQueryPathParameter(v)) : decodeQueryPathParameter(value); } // interpolate dynamic params and normalize URL if needed if (pageIsDynamic) { let params = {}; let paramsResult = utils.normalizeDynamicRouteParams(queryParams, false); // for prerendered ISR paths we attempt parsing the route // params from the URL directly as route-matches may not // contain the correct values due to the filesystem path // matching before the dynamic route has been matched if (!paramsResult.hasValidParams && !isDynamicRoute(normalizedUrlPath)) { let matcherParams = utils.dynamicRouteMatcher == null ? void 0 : utils.dynamicRouteMatcher.call(utils, normalizedUrlPath); if (matcherParams) { utils.normalizeDynamicRouteParams(matcherParams, false); Object.assign(paramsResult.params, matcherParams); paramsResult.hasValidParams = true; } } // if an action request is bypassing a prerender and we // don't have the params in the URL since it was prerendered // and matched during handle: 'filesystem' rather than dynamic route // resolving we need to parse the params from the matched-path. // Note: this is similar to above case but from match-path instead // of from the request URL since a rewrite could cause that to not // match the src pathname if (// we can have a collision with /index and a top-level /[slug] matchedPath !== '/index' && !paramsResult.hasValidParams && !isDynamicRoute(matchedPath)) { let matcherParams = utils.dynamicRouteMatcher == null ? void 0 : utils.dynamicRouteMatcher.call(utils, matchedPath); if (matcherParams) { const curParamsResult = utils.normalizeDynamicRouteParams(matcherParams, false); if (curParamsResult.hasValidParams) { Object.assign(params, matcherParams); paramsResult = curParamsResult; } } } if (paramsResult.hasValidParams) { params = paramsResult.params; } const routeMatchesHeader = req.headers['x-now-route-matches']; if (typeof routeMatchesHeader === 'string' && routeMatchesHeader && isDynamicRoute(matchedPath) && !paramsResult.hasValidParams) { const routeMatches = utils.getParamsFromRouteMatches(routeMatchesHeader); if (routeMatches) { paramsResult = utils.normalizeDynamicRouteParams(routeMatches, true); if (paramsResult.hasValidParams) { params = paramsResult.params; } } } // Try to parse the params from the query if we couldn't parse them // from the route matches but ignore missing optional params. if (!paramsResult.hasValidParams) { paramsResult = utils.normalizeDynamicRouteParams(queryParams, true); if (paramsResult.hasValidParams) { params = paramsResult.params; } } // handle the actual dynamic route name being requested if (utils.defaultRouteMatches && normalizedUrlPath === srcPathname && !paramsResult.hasValidParams && !utils.normalizeDynamicRouteParams({ ...params }, true).hasValidParams) { params = utils.defaultRouteMatches; // Mark that the default route matches were set on the request // during routing. addRequestMeta(req, 'didSetDefaultRouteMatches', true); } if (params) { matchedPath = utils.interpolateDynamicPath(srcPathname, params); req.url = utils.interpolateDynamicPath(req.url, params); // If the request is for a segment prefetch, we need to update the // segment prefetch request path to include the interpolated // params. let segmentPrefetchRSCRequest = getRequestMeta(req, 'segmentPrefetchRSCRequest'); if (segmentPrefetchRSCRequest && isDynamicRoute(segmentPrefetchRSCRequest, false)) { segmentPrefetchRSCRequest = utils.interpolateDynamicPath(segmentPrefetchRSCRequest, params); req.headers[NEXT_ROUTER_SEGMENT_PREFETCH_HEADER.toLowerCase()] = segmentPrefetchRSCRequest; addRequestMeta(req, 'segmentPrefetchRSCRequest', segmentPrefetchRSCRequest); } } } if (pageIsDynamic || didRewrite) { var _utils_defaultRouteRegex; utils.normalizeVercelUrl(req, [ ...rewriteParamKeys, ...Object.keys(((_utils_defaultRouteRegex = utils.defaultRouteRegex) == null ? void 0 : _utils_defaultRouteRegex.groups) || {}) ]); } parsedUrl.pathname = matchedPath; url.pathname = parsedUrl.pathname; finished = await this.normalizeAndAttachMetadata(req, res, parsedUrl); if (finished) return; } catch (err) { if (err instanceof DecodeError || err instanceof NormalizeError) { res.statusCode = 400; return this.renderError(null, req, res, '/_error', {}); } throw err; } } addRequestMeta(req, 'isLocaleDomain', Boolean(domainLocale)); if (pathnameInfo.locale) { req.url = formatUrl(url); addRequestMeta(req, 'didStripLocale', true); } // If we aren't in minimal mode or there is no locale in the query // string, add the locale to the query string. if (!this.minimalMode || !getRequestMeta(req, 'locale')) { // If the locale is in the pathname, add it to the query string. if (pathnameInfo.locale) { addRequestMeta(req, 'locale', pathnameInfo.locale); } else if (defaultLocale) { addRequestMeta(req, 'locale', defaultLocale); addRequestMeta(req, 'localeInferredFromDefault', true); } } // set incremental cache to request meta so it can // be passed down for edge functions and the fetch disk // cache can be leveraged locally if (!this.serverOptions.webServerConfig && !getRequestMeta(req, 'incrementalCache')) { let protocol = 'https:'; try { const parsedFullUrl = new URL(getRequestMeta(req, 'initURL') || '/', 'http://n'); protocol = parsedFullUrl.protocol; } catch {} const incrementalCache = await this.getIncrementalCache({ requestHeaders: Object.assign({}, req.headers), requestProtocol: protocol.substring(0, protocol.length - 1) }); incrementalCache.resetRequestCache(); addRequestMeta(req, 'incrementalCache', incrementalCache); globalThis.__incrementalCache = incrementalCache; } const cacheHandlers = getCacheHandlers(); if (cacheHandlers) { await Promise.all([ ...cacheHandlers ].map(async (cacheHandler)=>{ if ('refreshTags' in cacheHandler) { // Note: cacheHandler.refreshTags() is called lazily before the // first cache entry is retrieved. It allows us to skip the // refresh request if no caches are read at all. } else { const previouslyRevalidatedTags = getPreviouslyRevalidatedTags(req.headers, this.getPrerenderManifest().preview.previewModeId); await cacheHandler.receiveExpiredTags(...previouslyRevalidatedTags); } })); } // set server components HMR cache to request meta so it can be passed // down for edge functions if (!getRequestMeta(req, 'serverComponentsHmrCache')) { addRequestMeta(req, 'serverComponentsHmrCache', this.getServerComponentsHmrCache()); } // when invokePath is specified we can short short circuit resolving // we only honor this header if we are inside of a render worker to // prevent external users coercing the routing path const invokePath = getRequestMeta(req, 'invokePath'); const useInvokePath = !useMatchedPathHeader && process.env.NEXT_RUNTIME !== 'edge' && invokePath; if (useInvokePath) { var _this_nextConfig_i18n1; const invokeStatus = getRequestMeta(req, 'invokeStatus'); if (invokeStatus) { const invokeQuery = getRequestMeta(req, 'invokeQuery'); if (invokeQuery) { Object.assign(parsedUrl.query, invokeQuery); } res.statusCode = invokeStatus; let err = getRequestMeta(req, 'invokeError') || null; return this.renderError(err, req, res, '/_error', parsedUrl.query); } const parsedMatchedPath = new URL(invokePath || '/', 'http://n'); const invokePathnameInfo = getNextPathnameInfo(parsedMatchedPath.pathname, { nextConfig: this.nextConfig, parseData: false }); if (invokePathnameInfo.locale) { addRequestMeta(req, 'locale', invokePathnameInfo.l