import "./node-environment"; import "./require-hook"; import "./node-polyfill-fetch"; import "./node-polyfill-form"; import "./node-polyfill-web-streams"; import "./node-polyfill-crypto"; import "../lib/polyfill-promise-with-resolvers"; import { DecodeError, PageNotFoundError, MiddlewareNotFoundError } from "../shared/lib/utils"; import { getRouteMatcher } from "../shared/lib/router/utils/route-matcher"; import fs from "fs"; import { join, resolve, isAbsolute } from "path"; import { addRequestMeta, getRequestMeta } from "./request-meta"; import { PAGES_MANIFEST, BUILD_ID_FILE, MIDDLEWARE_MANIFEST, PRERENDER_MANIFEST, ROUTES_MANIFEST, CLIENT_PUBLIC_FILES_PATH, APP_PATHS_MANIFEST, SERVER_DIRECTORY, NEXT_FONT_MANIFEST, PHASE_PRODUCTION_BUILD, INTERNAL_HEADERS } from "../shared/lib/constants"; import { findDir } from "../lib/find-pages-dir"; import { NodeNextRequest, NodeNextResponse } from "./base-http/node"; import { sendRenderResult } from "./send-payload"; import { parseUrl } from "../shared/lib/router/utils/parse-url"; import * as Log from "../build/output/log"; import BaseServer, { NoFallbackError } from "./base-server"; import { getMaybePagePath, getPagePath, requireFontManifest } from "./require"; import { denormalizePagePath } from "../shared/lib/page-path/denormalize-page-path"; import { normalizePagePath } from "../shared/lib/page-path/normalize-page-path"; import { loadComponents } from "./load-components"; import isError, { getProperError } from "../lib/is-error"; import { splitCookiesString, toNodeOutgoingHttpHeaders } from "./web/utils"; import { getMiddlewareRouteMatcher } from "../shared/lib/router/utils/middleware-route-matcher"; import { loadEnvConfig } from "@next/env"; import { urlQueryToSearchParams } from "../shared/lib/router/utils/querystring"; import { removeTrailingSlash } from "../shared/lib/router/utils/remove-trailing-slash"; import { getNextPathnameInfo } from "../shared/lib/router/utils/get-next-pathname-info"; import { getCloneableBody } from "./body-streams"; import { checkIsOnDemandRevalidate } from "./api-utils"; import ResponseCache from "./response-cache"; import { IncrementalCache } from "./lib/incremental-cache"; import { normalizeAppPath } from "../shared/lib/router/utils/app-paths"; import { setHttpClientAndAgentOptions } from "./setup-http-agent-env"; import { isPagesAPIRouteMatch } from "./future/route-matches/pages-api-route-match"; import { INSTRUMENTATION_HOOK_FILENAME } from "../lib/constants"; import { getTracer } from "./lib/trace/tracer"; import { NextNodeServerSpan } from "./lib/trace/constants"; import { nodeFs } from "./lib/node-fs-methods"; import { getRouteRegex } from "../shared/lib/router/utils/route-regex"; import { invokeRequest } from "./lib/server-ipc/invoke-request"; import { pipeReadable } from "./pipe-readable"; import { filterReqHeaders, ipcForbiddenHeaders } from "./lib/server-ipc/utils"; import { createRequestResponseMocks } from "./lib/mock-request"; import { NEXT_RSC_UNION_QUERY } from "../client/components/app-router-headers"; import { signalFromNodeResponse } from "./web/spec-extension/adapters/next-request"; import { RouteModuleLoader } from "./future/helpers/module-loader/route-module-loader"; import { loadManifest } from "./load-manifest"; import { lazyRenderAppPage } from "./future/route-modules/app-page/module.render"; import { lazyRenderPagesPage } from "./future/route-modules/pages/module.render"; export * from "./base-server"; const dynamicRequire = process.env.NEXT_MINIMAL ? __non_webpack_require__ : require; function writeStdoutLine(text) { process.stdout.write(" " + text + "\n"); } function formatRequestUrl(url, maxLength) { return maxLength !== undefined && url.length > maxLength ? url.substring(0, maxLength) + ".." : url; } const MiddlewareMatcherCache = new WeakMap(); function getMiddlewareMatcher(info) { const stored = MiddlewareMatcherCache.get(info); if (stored) { return stored; } if (!Array.isArray(info.matchers)) { throw new Error(`Invariant: invalid matchers for middleware ${JSON.stringify(info)}`); } const matcher = getMiddlewareRouteMatcher(info.matchers); MiddlewareMatcherCache.set(info, matcher); return matcher; } export default class NextNodeServer extends BaseServer { constructor(options){ // Initialize super class super(options); /** * This sets environment variable to be used at the time of SSR by head.tsx. * Using this from process.env allows targeting SSR by calling * `process.env.__NEXT_OPTIMIZE_CSS`. */ if (this.renderOpts.optimizeFonts) { process.env.__NEXT_OPTIMIZE_FONTS = JSON.stringify(this.renderOpts.optimizeFonts); } if (this.renderOpts.optimizeCss) { process.env.__NEXT_OPTIMIZE_CSS = JSON.stringify(true); } if (this.renderOpts.nextScriptWorkers) { process.env.__NEXT_SCRIPT_WORKERS = JSON.stringify(true); } if (this.nextConfig.experimental.deploymentId) { process.env.NEXT_DEPLOYMENT_ID = this.nextConfig.experimental.deploymentId; } if (!this.minimalMode) { this.imageResponseCache = new ResponseCache(this.minimalMode); } const { appDocumentPreloading } = this.nextConfig.experimental; const isDefaultEnabled = typeof appDocumentPreloading === "undefined"; if (!options.dev && (appDocumentPreloading === true || !(this.minimalMode && isDefaultEnabled))) { // pre-warm _document and _app as these will be // needed for most requests loadComponents({ distDir: this.distDir, page: "/_document", isAppPath: false }).catch(()=>{}); loadComponents({ distDir: this.distDir, page: "/_app", isAppPath: false }).catch(()=>{}); } if (!options.dev) { const { dynamicRoutes = [] } = this.getRoutesManifest() ?? {}; this.dynamicRoutes = dynamicRoutes.map((r)=>{ // TODO: can we just re-use the regex from the manifest? const regex = getRouteRegex(r.page); const match = getRouteMatcher(regex); return { match, page: r.page, re: regex.re }; }); } // ensure options are set when loadConfig isn't called setHttpClientAndAgentOptions(this.nextConfig); // Intercept fetch and other testmode apis. if (this.serverOptions.experimentalTestProxy) { const { interceptTestApis } = require("../experimental/testmode/server"); interceptTestApis(); } this.middlewareManifestPath = join(this.serverDistDir, MIDDLEWARE_MANIFEST); } async handleUpgrade() { // The web server does not support web sockets, it's only used for HMR in // development. } async prepareImpl() { await super.prepareImpl(); if (!this.serverOptions.dev && this.nextConfig.experimental.instrumentationHook) { try { const instrumentationHook = await dynamicRequire(resolve(this.serverOptions.dir || ".", this.serverOptions.conf.distDir, "server", INSTRUMENTATION_HOOK_FILENAME)); await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); } catch (err) { if (err.code !== "MODULE_NOT_FOUND") { err.message = `An error occurred while loading instrumentation hook: ${err.message}`; throw err; } } } } loadEnvConfig({ dev, forceReload, silent }) { loadEnvConfig(this.dir, dev, silent ? { info: ()=>{}, error: ()=>{} } : Log, forceReload); } getIncrementalCache({ requestHeaders, requestProtocol }) { const dev = !!this.renderOpts.dev; let CacheHandler; const { incrementalCacheHandlerPath } = this.nextConfig.experimental; if (incrementalCacheHandlerPath) { CacheHandler = dynamicRequire(isAbsolute(incrementalCacheHandlerPath) ? incrementalCacheHandlerPath : join(this.distDir, incrementalCacheHandlerPath)); CacheHandler = CacheHandler.default || CacheHandler; } // incremental-cache is request specific // although can have shared caches in module scope // per-cache handler return new IncrementalCache({ fs: this.getCacheFilesystem(), dev, requestHeaders, requestProtocol, appDir: this.hasAppDir, allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys, minimalMode: this.minimalMode, serverDistDir: this.serverDistDir, fetchCache: true, fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix, maxMemoryCacheSize: this.nextConfig.experimental.isrMemoryCacheSize, flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk, getPrerenderManifest: ()=>this.getPrerenderManifest(), CurCacheHandler: CacheHandler }); } getResponseCache() { return new ResponseCache(this.minimalMode); } getPublicDir() { return join(this.dir, CLIENT_PUBLIC_FILES_PATH); } getHasStaticDir() { return fs.existsSync(join(this.dir, "static")); } getPagesManifest() { return loadManifest(join(this.serverDistDir, PAGES_MANIFEST)); } getAppPathsManifest() { if (!this.hasAppDir) return undefined; return loadManifest(join(this.serverDistDir, APP_PATHS_MANIFEST)); } async hasPage(pathname) { var _this_nextConfig_i18n; return !!getMaybePagePath(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.hasAppDir); } getBuildId() { const buildIdFile = join(this.distDir, BUILD_ID_FILE); try { return fs.readFileSync(buildIdFile, "utf8").trim(); } catch (err) { if (err.code === "ENOENT") { throw new Error(`Could not find a production build in the '${this.distDir}' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id`); } throw err; } } getHasAppDir(dev) { return Boolean(findDir(dev ? this.dir : this.serverDistDir, "app")); } sendRenderResult(req, res, options) { return sendRenderResult({ req: req.originalRequest, res: res.originalResponse, ...options }); } async runApi(req, res, query, match) { const edgeFunctionsPages = this.getEdgeFunctionsPages(); for (const edgeFunctionsPage of edgeFunctionsPages){ if (edgeFunctionsPage === match.definition.pathname) { const handledAsEdgeFunction = await this.runEdgeFunction({ req, res, query, params: match.params, page: match.definition.pathname, appPaths: null }); if (handledAsEdgeFunction) { return true; } } } // The module supports minimal mode, load the minimal module. const module = await RouteModuleLoader.load(match.definition.filename); query = { ...query, ...match.params }; delete query.__nextLocale; delete query.__nextDefaultLocale; delete query.__nextInferredLocaleFromDefault; await module.render(req.originalRequest, res.originalResponse, { previewProps: this.renderOpts.previewProps, revalidate: this.revalidate.bind(this), trustHostHeader: this.nextConfig.experimental.trustHostHeader, allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys, hostname: this.fetchHostname, minimalMode: this.minimalMode, dev: this.renderOpts.dev === true, query, params: match.params, page: match.definition.pathname }); return true; } async renderHTML(req, res, pathname, query, renderOpts) { return getTracer().trace(NextNodeServerSpan.renderHTML, async ()=>this.renderHTMLImpl(req, res, pathname, query, renderOpts)); } async renderHTMLImpl(req, res, pathname, query, renderOpts) { if (process.env.NEXT_MINIMAL) { throw new Error("Invariant: renderHTML should not be called in minimal mode"); // the `else` branch is needed for tree-shaking } else { // Due to the way we pass data by mutating `renderOpts`, we can't extend the // object here but only updating its `nextFontManifest` field. // https://github.com/vercel/next.js/blob/df7cbd904c3bd85f399d1ce90680c0ecf92d2752/packages/next/server/render.tsx#L947-L952 renderOpts.nextFontManifest = this.nextFontManifest; if (this.hasAppDir && renderOpts.isAppPath) { return lazyRenderAppPage(req.originalRequest, res.originalResponse, pathname, query, renderOpts); } // TODO: re-enable this once we've refactored to use implicit matches // throw new Error('Invariant: render should have used routeModule') return lazyRenderPagesPage(req.originalRequest, res.originalResponse, pathname, query, renderOpts); } } async imageOptimizer(req, res, paramsResult) { if (process.env.NEXT_MINIMAL) { throw new Error("invariant: imageOptimizer should not be called in minimal mode"); } else { const { imageOptimizer } = require("./image-optimizer"); return imageOptimizer(req.originalRequest, res.originalResponse, paramsResult, this.nextConfig, this.renderOpts.dev, async (newReq, newRes)=>{ if (newReq.url === req.url) { throw new Error(`Invariant attempted to optimize _next/image itself`); } const protocol = this.serverOptions.experimentalHttpsServer ? "https" : "http"; const invokeRes = await invokeRequest(`${protocol}://${this.fetchHostname || "localhost"}:${this.port}${newReq.url || ""}`, { method: newReq.method || "GET", headers: newReq.headers, signal: signalFromNodeResponse(res.originalResponse) }); const filteredResHeaders = filterReqHeaders(toNodeOutgoingHttpHeaders(invokeRes.headers), ipcForbiddenHeaders); for (const key of Object.keys(filteredResHeaders)){ newRes.setHeader(key, filteredResHeaders[key] || ""); } newRes.statusCode = invokeRes.status || 200; if (invokeRes.body) { await pipeReadable(invokeRes.body, newRes); } else { res.send(); } return; }); } } getPagePath(pathname, locales) { return getPagePath(pathname, this.distDir, locales, this.hasAppDir); } async renderPageComponent(ctx, bubbleNoFallback) { const edgeFunctionsPages = this.getEdgeFunctionsPages() || []; if (edgeFunctionsPages.length) { const appPaths = this.getOriginalAppPaths(ctx.pathname); const isAppPath = Array.isArray(appPaths); let page = ctx.pathname; if (isAppPath) { // When it's an array, we need to pass all parallel routes to the loader. page = appPaths[0]; } for (const edgeFunctionsPage of edgeFunctionsPages){ if (edgeFunctionsPage === page) { await this.runEdgeFunction({ req: ctx.req, res: ctx.res, query: ctx.query, params: ctx.renderOpts.params, page, appPaths }); return null; } } } return super.renderPageComponent(ctx, bubbleNoFallback); } async findPageComponents({ page, query, params, isAppPath }) { return getTracer().trace(NextNodeServerSpan.findPageComponents, { spanName: `resolving page into components`, attributes: { "next.route": isAppPath ? normalizeAppPath(page) : page } }, ()=>this.findPageComponentsImpl({ page, query, params, isAppPath })); } async findPageComponentsImpl({ page, query, params, isAppPath }) { const pagePaths = [ page ]; if (query.amp) { // try serving a static AMP version first pagePaths.unshift((isAppPath ? normalizeAppPath(page) : normalizePagePath(page)) + ".amp"); } if (query.__nextLocale) { pagePaths.unshift(...pagePaths.map((path)=>`/${query.__nextLocale}${path === "/" ? "" : path}`)); } for (const pagePath of pagePaths){ try { const components = await loadComponents({ distDir: this.distDir, page: pagePath, isAppPath }); if (query.__nextLocale && typeof components.Component === "string" && !pagePath.startsWith(`/${query.__nextLocale}`)) { continue; } return { components, query: { ...!this.renderOpts.isExperimentalCompile && components.getStaticProps ? { amp: query.amp, __nextDataReq: query.__nextDataReq, __nextLocale: query.__nextLocale, __nextDefaultLocale: query.__nextDefaultLocale } : query, // For appDir params is excluded. ...(isAppPath ? {} : params) || {} } }; } catch (err) { // we should only not throw if we failed to find the page // in the pages-manifest if (!(err instanceof PageNotFoundError)) { throw err; } } } return null; } getFontManifest() { return requireFontManifest(this.distDir); } getNextFontManifest() { return loadManifest(join(this.distDir, "server", NEXT_FONT_MANIFEST + ".json")); } getFallback(page) { page = normalizePagePath(page); const cacheFs = this.getCacheFilesystem(); return cacheFs.readFile(join(this.serverDistDir, "pages", `${page}.html`), "utf8"); } async handleNextImageRequest(req, res, parsedUrl) { if (this.minimalMode || this.nextConfig.output === "export" || process.env.NEXT_MINIMAL) { res.statusCode = 400; res.body("Bad Request").send(); return { finished: true }; // the `else` branch is needed for tree-shaking } else { const { ImageOptimizerCache } = require("./image-optimizer"); const imageOptimizerCache = new ImageOptimizerCache({ distDir: this.distDir, nextConfig: this.nextConfig }); const { getHash, sendResponse, ImageError } = require("./image-optimizer"); if (!this.imageResponseCache) { throw new Error("invariant image optimizer cache was not initialized"); } const imagesConfig = this.nextConfig.images; if (imagesConfig.loader !== "default" || imagesConfig.unoptimized) { await this.render404(req, res); return { finished: true }; } const paramsResult = ImageOptimizerCache.validateParams(req.originalRequest, parsedUrl.query, this.nextConfig, !!this.renderOpts.dev); if ("errorMessage" in paramsResult) { res.statusCode = 400; res.body(paramsResult.errorMessage).send(); return { finished: true }; } const cacheKey = ImageOptimizerCache.getCacheKey(paramsResult); try { var _cacheEntry_value; const { getExtension } = require("./serve-static"); const cacheEntry = await this.imageResponseCache.get(cacheKey, async ()=>{ const { buffer, contentType, maxAge } = await this.imageOptimizer(req, res, paramsResult); const etag = getHash([ buffer ]); return { value: { kind: "IMAGE", buffer, etag, extension: getExtension(contentType) }, revalidate: maxAge }; }, { incrementalCache: imageOptimizerCache }); if ((cacheEntry == null ? void 0 : (_cacheEntry_value = cacheEntry.value) == null ? void 0 : _cacheEntry_value.kind) !== "IMAGE") { throw new Error("invariant did not get entry from image response cache"); } sendResponse(req.originalRequest, res.originalResponse, paramsResult.href, cacheEntry.value.extension, cacheEntry.value.buffer, paramsResult.isStatic, cacheEntry.isMiss ? "MISS" : cacheEntry.isStale ? "STALE" : "HIT", imagesConfig, cacheEntry.revalidate || 0, Boolean(this.renderOpts.dev)); } catch (err) { if (err instanceof ImageError) { res.statusCode = err.statusCode; res.body(err.message).send(); return { finished: true }; } throw err; } return { finished: true }; } } async handleCatchallRenderRequest(req, res, parsedUrl) { let { pathname, query } = parsedUrl; if (!pathname) { throw new Error("Invariant: pathname is undefined"); } // This is a catch-all route, there should be no fallbacks so mark it as // such. query._nextBubbleNoFallback = "1"; try { var _this_i18nProvider; // next.js core assumes page path without trailing slash pathname = removeTrailingSlash(pathname); const options = { i18n: (_this_i18nProvider = this.i18nProvider) == null ? void 0 : _this_i18nProvider.fromQuery(pathname, query) }; const match = await this.matchers.match(pathname, options); // If we don't have a match, try to render it anyways. if (!match) { await this.render(req, res, pathname, query, parsedUrl, true); return { finished: true }; } // Add the match to the request so we don't have to re-run the matcher // for the same request. addRequestMeta(req, "_nextMatch", match); // TODO-APP: move this to a route handler const edgeFunctionsPages = this.getEdgeFunctionsPages(); for (const edgeFunctionsPage of edgeFunctionsPages){ // If the page doesn't match the edge function page, skip it. if (edgeFunctionsPage !== match.definition.page) continue; if (this.nextConfig.output === "export") { await this.render404(req, res, parsedUrl); return { finished: true }; } delete query._nextBubbleNoFallback; delete query[NEXT_RSC_UNION_QUERY]; const handled = await this.runEdgeFunction({ req, res, query, params: match.params, page: match.definition.page, match, appPaths: null }); // If we handled the request, we can return early. if (handled) return { finished: true }; } // If the route was detected as being a Pages API route, then handle // it. // TODO: move this behavior into a route handler. if (isPagesAPIRouteMatch(match)) { if (this.nextConfig.output === "export") { await this.render404(req, res, parsedUrl); return { finished: true }; } delete query._nextBubbleNoFallback; const handled = await this.handleApiRequest(req, res, query, match); if (handled) return { finished: true }; } await this.render(req, res, pathname, query, parsedUrl, true); return { finished: true }; } catch (err) { if (err instanceof NoFallbackError) { throw err; } try { if (this.renderOpts.dev) { const { formatServerError } = require("../lib/format-server-error"); formatServerError(err); await this.logErrorWithOriginalStack(err); } else { this.logError(err); } res.statusCode = 500; await this.renderError(err, req, res, pathname, query); return { finished: true }; } catch {} throw err; } } // Used in development only, overloaded in next-dev-server async logErrorWithOriginalStack(_err, _type) { throw new Error("Invariant: logErrorWithOriginalStack can only be called on the development server"); } // Used in development only, overloaded in next-dev-server async ensurePage(_opts) { throw new Error("Invariant: ensurePage can only be called on the development server"); } /** * Resolves `API` request, in development builds on demand * @param req http request * @param res http response * @param pathname path of request */ async handleApiRequest(req, res, query, match) { return this.runApi(req, res, query, match); } getPrefetchRsc(pathname) { return this.getCacheFilesystem().readFile(join(this.serverDistDir, "app", `${pathname}.prefetch.rsc`), "utf8"); } getCacheFilesystem() { return nodeFs; } normalizeReq(req) { return !(req instanceof NodeNextRequest) ? new NodeNextRequest(req) : req; } normalizeRes(res) { return !(res instanceof NodeNextResponse) ? new NodeNextResponse(res) : res; } getRequestHandler() { const handler = this.makeRequestHandler(); if (this.serverOptions.experimentalTestProxy) { const { wrapRequestHandlerNode } = require("../experimental/testmode/server"); return wrapRequestHandlerNode(handler); } return handler; } makeRequestHandler() { // This is just optimization to fire prepare as soon as possible // It will be properly awaited later void this.prepare(); const handler = super.getRequestHandler(); return (req, res, parsedUrl)=>{ var _this_nextConfig_experimental_logging, _this_nextConfig_experimental_logging1; const normalizedReq = this.normalizeReq(req); const normalizedRes = this.normalizeRes(res); const enabledVerboseLogging = ((_this_nextConfig_experimental_logging = this.nextConfig.experimental.logging) == null ? void 0 : _this_nextConfig_experimental_logging.level) === "verbose"; const shouldTruncateUrl = !((_this_nextConfig_experimental_logging1 = this.nextConfig.experimental.logging) == null ? void 0 : _this_nextConfig_experimental_logging1.fullUrl); if (this.renderOpts.dev) { const { bold, green, yellow, red, gray, white } = require("../lib/picocolors"); const _req = req; const _res = res; const origReq = "originalRequest" in _req ? _req.originalRequest : _req; const origRes = "originalResponse" in _res ? _res.originalResponse : _res; const reqStart = Date.now(); const reqCallback = ()=>{ // if we already logged in a render worker // don't log again in the router worker. // we also don't log for middleware alone if (normalizedReq.didInvokePath || origReq.headers["x-middleware-invoke"]) { return; } const reqEnd = Date.now(); const fetchMetrics = normalizedReq.fetchMetrics || []; const reqDuration = reqEnd - reqStart; const getDurationStr = (duration)=>{ let durationStr = duration.toString(); if (duration < 500) { durationStr = green(duration + "ms"); } else if (duration < 2000) { durationStr = yellow(duration + "ms"); } else { durationStr = red(duration + "ms"); } return durationStr; }; if (Array.isArray(fetchMetrics) && fetchMetrics.length) { if (enabledVerboseLogging) { writeStdoutLine(`${white(bold(req.method || "GET"))} ${req.url} ${res.statusCode} in ${getDurationStr(reqDuration)}`); } const calcNestedLevel = (prevMetrics, start)=>{ let nestedLevel = 0; for(let i = 0; i < prevMetrics.length; i++){ const metric = prevMetrics[i]; const prevMetric = prevMetrics[i - 1]; if (metric.end <= start && !(prevMetric && prevMetric.start < metric.end)) { nestedLevel += 1; } } return `${" │ ".repeat(nestedLevel)}`; }; for(let i = 0; i < fetchMetrics.length; i++){ const metric = fetchMetrics[i]; let { cacheStatus, cacheReason } = metric; let cacheReasonStr = ""; const duration = metric.end - metric.start; if (cacheStatus === "hit") { cacheStatus = green("HIT"); } else if (cacheStatus === "skip") { cacheStatus = `${yellow("SKIP")}`; cacheReasonStr = `${gray(`Cache missed reason: (${white(cacheReason)})`)}`; } else { cacheStatus = yellow("MISS"); } let url = metric.url; if (url.length > 48) { const parsed = new URL(url); const truncatedHost = formatRequestUrl(parsed.host, shouldTruncateUrl ? 16 : undefined); const truncatedPath = formatRequestUrl(parsed.pathname, shouldTruncateUrl ? 24 : undefined); const truncatedSearch = formatRequestUrl(parsed.search, shouldTruncateUrl ? 16 : undefined); url = parsed.protocol + "//" + truncatedHost + truncatedPath + truncatedSearch; } if (enabledVerboseLogging) { const newLineLeadingChar = "│"; const nestedIndent = calcNestedLevel(fetchMetrics.slice(0, i), metric.start); writeStdoutLine(`${`${newLineLeadingChar}${nestedIndent}${i === 0 ? " " : ""}${white(bold(metric.method))} ${gray(url)} ${metric.status} in ${getDurationStr(duration)} (cache: ${cacheStatus})`}`); if (cacheReasonStr) { const nextNestedIndent = calcNestedLevel(fetchMetrics.slice(0, i + 1), metric.start); writeStdoutLine(newLineLeadingChar + nextNestedIndent + (i > 0 ? " " : " ") + newLineLeadingChar + " " + cacheReasonStr); } } } } else { if (enabledVerboseLogging) { writeStdoutLine(`${white(bold(req.method || "GET"))} ${req.url} ${res.statusCode} in ${getDurationStr(reqDuration)}`); } } origRes.off("close", reqCallback); }; origRes.on("close", reqCallback); } return handler(normalizedReq, normalizedRes, parsedUrl); }; } async revalidate({ urlPath, revalidateHeaders, opts }) { const mocked = createRequestResponseMocks({ url: urlPath, headers: revalidateHeaders }); const handler = this.getRequestHandler(); await handler(new NodeNextRequest(mocked.req), new NodeNextResponse(mocked.res)); await mocked.res.hasStreamed; if (mocked.res.getHeader("x-nextjs-cache") !== "REVALIDATED" && !(mocked.res.statusCode === 404 && opts.unstable_onlyGenerated)) { throw new Error(`Invalid response ${mocked.res.statusCode}`); } } async render(req, res, pathname, query, parsedUrl, internal = false) { return super.render(this.normalizeReq(req), this.normalizeRes(res), pathname, query, parsedUrl, internal); } async renderToHTML(req, res, pathname, query) { return super.renderToHTML(this.normalizeReq(req), this.normalizeRes(res), pathname, query); } async renderErrorToResponseImpl(ctx, err) { const { req, res, query } = ctx; const is404 = res.statusCode === 404; if (is404 && this.hasAppDir) { const notFoundPathname = this.renderOpts.dev ? "/not-found" : "/_not-found"; if (this.renderOpts.dev) { await this.ensurePage({ page: notFoundPathname, clientOnly: false }).catch(()=>{}); } if (this.getEdgeFunctionsPages().includes(notFoundPathname)) { await this.runEdgeFunction({ req: req, res: res, query: query || {}, params: {}, page: notFoundPathname, appPaths: null }); return null; } } return super.renderErrorToResponseImpl(ctx, err); } async renderError(err, req, res, pathname, query, setHeaders) { return super.renderError(err, this.normalizeReq(req), this.normalizeRes(res), pathname, query, setHeaders); } async renderErrorToHTML(err, req, res, pathname, query) { return super.renderErrorToHTML(err, this.normalizeReq(req), this.normalizeRes(res), pathname, query); } async render404(req, res, parsedUrl, setHeaders) { return super.render404(this.normalizeReq(req), this.normalizeRes(res), parsedUrl, setHeaders); } getMiddlewareManifest() { if (this.minimalMode) return null; const manifest = require(this.middlewareManifestPath); return manifest; } /** Returns the middleware routing item if there is one. */ getMiddleware() { var _manifest_middleware; const manifest = this.getMiddlewareManifest(); const middleware = manifest == null ? void 0 : (_manifest_middleware = manifest.middleware) == null ? void 0 : _manifest_middleware["/"]; if (!middleware) { return; } return { match: getMiddlewareMatcher(middleware), page: "/" }; } getEdgeFunctionsPages() { const manifest = this.getMiddlewareManifest(); if (!manifest) { return []; } return Object.keys(manifest.functions); } /** * Get information for the edge function located in the provided page * folder. If the edge function info can't be found it will throw * an error. */ getEdgeFunctionInfo(params) { const manifest = this.getMiddlewareManifest(); if (!manifest) { return null; } let foundPage; try { foundPage = denormalizePagePath(normalizePagePath(params.page)); } catch (err) { return null; } let pageInfo = params.middleware ? manifest.middleware[foundPage] : manifest.functions[foundPage]; if (!pageInfo) { if (!params.middleware) { throw new PageNotFoundError(foundPage); } return null; } return { name: pageInfo.name, paths: pageInfo.files.map((file)=>join(this.distDir, file)), wasm: (pageInfo.wasm ?? []).map((binding)=>({ ...binding, filePath: join(this.distDir, binding.filePath) })), assets: (pageInfo.assets ?? []).map((binding)=>{ return { ...binding, filePath: join(this.distDir, binding.filePath) }; }) }; } /** * Checks if a middleware exists. This method is useful for the development * server where we need to check the filesystem. Here we just check the * middleware manifest. */ async hasMiddleware(pathname) { const info = this.getEdgeFunctionInfo({ page: pathname, middleware: true }); return Boolean(info && info.paths.length > 0); } /** * A placeholder for a function to be defined in the development server. * It will make sure that the root middleware or an edge function has been compiled * so that we can run it. */ async ensureMiddleware() {} async ensureEdgeFunction(_params) {} /** * This method gets all middleware matchers and execute them when the request * matches. It will make sure that each middleware exists and is compiled and * ready to be invoked. The development server will decorate it to add warns * and errors with rich traces. */ async runMiddleware(params) { if (process.env.NEXT_MINIMAL) { throw new Error("invariant: runMiddleware should not be called in minimal mode"); } // Middleware is skipped for on-demand revalidate requests if (checkIsOnDemandRevalidate(params.request, this.renderOpts.previewProps).isOnDemandRevalidate) { return { response: new Response(null, { headers: { "x-middleware-next": "1" } }) }; } let url; if (this.nextConfig.skipMiddlewareUrlNormalize) { url = getRequestMeta(params.request, "__NEXT_INIT_URL"); } else { // For middleware to "fetch" we must always provide an absolute URL const query = urlQueryToSearchParams(params.parsed.query).toString(); const locale = params.parsed.query.__nextLocale; url = `${getRequestMeta(params.request, "_protocol")}://${this.fetchHostname || "localhost"}:${this.port}${locale ? `/${locale}` : ""}${params.parsed.pathname}${query ? `?${query}` : ""}`; } if (!url.startsWith("http")) { throw new Error("To use middleware you must provide a `hostname` and `port` to the Next.js Server"); } const page = {}; const middleware = this.getMiddleware(); if (!middleware) { return { finished: false }; } if (!await this.hasMiddleware(middleware.page)) { return { finished: false }; } await this.ensureMiddleware(); const middlewareInfo = this.getEdgeFunctionInfo({ page: middleware.page, middleware: true }); if (!middlewareInfo) { throw new MiddlewareNotFoundError(); } const method = (params.request.method || "GET").toUpperCase(); const { run } = require("./web/sandbox"); const result = await run({ distDir: this.distDir, name: middlewareInfo.name, paths: middlewareInfo.paths, edgeFunctionEntry: middlewareInfo, request: { headers: params.request.headers, method, nextConfig: { basePath: this.nextConfig.basePath, i18n: this.nextConfig.i18n, trailingSlash: this.nextConfig.trailingSlash }, url: url, page, body: getRequestMeta(params.request, "__NEXT_CLONABLE_BODY"), signal: signalFromNodeResponse(params.response.originalResponse) }, useCache: true, onWarning: params.onWarning }); if (!this.renderOpts.dev) { result.waitUntil.catch((error)=>{ console.error(`Uncaught: middleware waitUntil errored`, error); }); } if (!result) { this.render404(params.request, params.response, params.parsed); return { finished: true }; } for (let [key, value] of result.response.headers){ if (key.toLowerCase() !== "set-cookie") continue; // Clear existing header. result.response.headers.delete(key); // Append each cookie individually. const cookies = splitCookiesString(value); for (const cookie of cookies){ result.response.headers.append(key, cookie); } // Add cookies to request meta. addRequestMeta(params.request, "_nextMiddlewareCookie", cookies); } return result; } async handleCatchallMiddlewareRequest(req, res, parsed) { const isMiddlewareInvoke = req.headers["x-middleware-invoke"]; const handleFinished = (finished = false)=>{ if (isMiddlewareInvoke && !finished) { res.setHeader("x-middleware-invoke", "1"); res.body("").send(); return { finished: true }; } return { finished }; }; if (!isMiddlewareInvoke) { return { finished: false }; } const middleware = this.getMiddleware(); if (!middleware) { return handleFinished(); } const initUrl = getRequestMeta(req, "__NEXT_INIT_URL"); const parsedUrl = parseUrl(initUrl); const pathnameInfo = getNextPathnameInfo(parsedUrl.pathname, { nextConfig: this.nextConfig, i18nProvider: this.i18nProvider }); parsedUrl.pathname = pathnameInfo.pathname; const normalizedPathname = removeTrailingSlash(parsed.pathname || ""); if (!middleware.match(normalizedPathname, req, parsedUrl.query)) { return handleFinished(); } let result; let bubblingResult = false; for (const key of INTERNAL_HEADERS){ delete req.headers[key]; } // Strip the internal headers. this.stripInternalHeaders(req); try { await this.ensureMiddleware(); result = await this.runMiddleware({ request: req, response: res, parsedUrl: parsedUrl, parsed: parsed }); if ("response" in result) { if (isMiddlewareInvoke) { bubblingResult = true; const err = new Error(); err.result = result; err.bubble = true; throw err; } for (const [key, value] of Object.entries(toNodeOutgoingHttpHeaders(result.response.headers))){ if (key !== "content-encoding" && value !== undefined) { res.setHeader(key, value); } } res.statusCode = result.response.status; const { originalResponse } = res; if (result.response.body) { await pipeReadable(result.response.body, originalResponse); } else { originalResponse.end(); } return { finished: true }; } } catch (err) { if (bubblingResult) { throw err; } if (isError(err) && err.code === "ENOENT") { await this.render404(req, res, parsed); return { finished: true }; } if (err instanceof DecodeError) { res.statusCode = 400; await this.renderError(err, req, res, parsed.pathname || ""); return { finished: true }; } const error = getProperError(err); console.error(error); res.statusCode = 500; await this.renderError(error, req, res, parsed.pathname || ""); return { finished: true }; } if ("finished" in result) { return result; } return { finished: false }; } getPrerenderManifest() { var _this_renderOpts, _this_serverOptions; if (this._cachedPreviewManifest) { return this._cachedPreviewManifest; } if (((_this_renderOpts = this.renderOpts) == null ? void 0 : _this_renderOpts.dev) || ((_this_serverOptions = this.serverOptions) == null ? void 0 : _this_serverOptions.dev) || process.env.NODE_ENV === "development" || process.env.NEXT_PHASE === PHASE_PRODUCTION_BUILD) { this._cachedPreviewManifest = { version: 4, routes: {}, dynamicRoutes: {}, notFoundRoutes: [], preview: { previewModeId: require("crypto").randomBytes(16).toString("hex"), previewModeSigningKey: require("crypto").randomBytes(32).toString("hex"), previewModeEncryptionKey: require("crypto").randomBytes(32).toString("hex") } }; return this._cachedPreviewManifest; } const manifest = loadManifest(join(this.distDir, PRERENDER_MANIFEST)); return this._cachedPreviewManifest = manifest; } getRoutesManifest() { return getTracer().trace(NextNodeServerSpan.getRoutesManifest, ()=>{ const manifest = loadManifest(join(this.distDir, ROUTES_MANIFEST)); let rewrites = manifest.rewrites ?? { beforeFiles: [], afterFiles: [], fallback: [] }; if (Array.isArray(rewrites)) { rewrites = { beforeFiles: [], afterFiles: rewrites, fallback: [] }; } return { ...manifest, rewrites }; }); } attachRequestMeta(req, parsedUrl, isUpgradeReq) { var _this, _req_originalRequest, _req_headers_xforwardedproto; const protocol = ((_this = (_req_originalRequest = req.originalRequest) == null ? void 0 : _req_originalRequest.socket) == null ? void 0 : _this.encrypted) || ((_req_headers_xforwardedproto = req.headers["x-forwarded-proto"]) == null ? void 0 : _req_headers_xforwardedproto.includes("https")) ? "https" : "http"; // When there are hostname and port we build an absolute URL const initUrl = this.fetchHostname && this.port ? `${protocol}://${this.fetchHostname}:${this.port}${req.url}` : this.nextConfig.experimental.trustHostHeader ? `https://${req.headers.host || "localhost"}${req.url}` : req.url; addRequestMeta(req, "__NEXT_INIT_URL", initUrl); addRequestMeta(req, "__NEXT_INIT_QUERY", { ...parsedUrl.query }); addRequestMeta(req, "_protocol", protocol); if (!isUpgradeReq) { addRequestMeta(req, "__NEXT_CLONABLE_BODY", getCloneableBody(req.body)); } } async runEdgeFunction(params) { if (process.env.NEXT_MINIMAL) { throw new Error("Middleware is not supported in minimal mode. Please remove the `NEXT_MINIMAL` environment variable."); } let edgeInfo; const { query, page, match } = params; if (!match) await this.ensureEdgeFunction({ page, appPaths: params.appPaths }); edgeInfo = this.getEdgeFunctionInfo({ page, middleware: false }); if (!edgeInfo) { return null; } // For edge to "fetch" we must always provide an absolute URL const isDataReq = !!query.__nextDataReq; const initialUrl = new URL(getRequestMeta(params.req, "__NEXT_INIT_URL") || "/", "http://n"); const queryString = urlQueryToSearchParams({ ...Object.fromEntries(initialUrl.searchParams), ...query, ...params.params }).toString(); if (isDataReq) { params.req.headers["x-nextjs-data"] = "1"; } initialUrl.search = queryString; const url = initialUrl.toString(); if (!url.startsWith("http")) { throw new Error("To use middleware you must provide a `hostname` and `port` to the Next.js Server"); } const { run } = require("./web/sandbox"); const result = await run({ distDir: this.distDir, name: edgeInfo.name, paths: edgeInfo.paths, edgeFunctionEntry: edgeInfo, request: { headers: params.req.headers, method: params.req.method, nextConfig: { basePath: this.nextConfig.basePath, i18n: this.nextConfig.i18n, trailingSlash: this.nextConfig.trailingSlash }, url, page: { name: params.page, ...params.params && { params: params.params } }, body: getRequestMeta(params.req, "__NEXT_CLONABLE_BODY"), signal: signalFromNodeResponse(params.res.originalResponse) }, useCache: true, onWarning: params.onWarning, incrementalCache: globalThis.__incrementalCache || getRequestMeta(params.req, "_nextIncrementalCache") }); if (result.fetchMetrics) { params.req.fetchMetrics = result.fetchMetrics; } if (!params.res.statusCode || params.res.statusCode < 400) { params.res.statusCode = result.response.status; params.res.statusMessage = result.response.statusText; } // TODO: (wyattjoh) investigate improving this result.response.headers.forEach((value, key)=>{ // The append handling is special cased for `set-cookie`. if (key.toLowerCase() === "set-cookie") { // TODO: (wyattjoh) replace with native response iteration when we can upgrade undici for (const cookie of splitCookiesString(value)){ params.res.appendHeader(key, cookie); } } else { params.res.appendHeader(key, value); } }); const nodeResStream = params.res.originalResponse; if (result.response.body) { await pipeReadable(result.response.body, nodeResStream); } else { nodeResStream.end(); } return result; } get serverDistDir() { if (this._serverDistDir) { return this._serverDistDir; } const serverDistDir = join(this.distDir, SERVER_DIRECTORY); this._serverDistDir = serverDistDir; return serverDistDir; } async getFallbackErrorComponents() { // Not implemented for production use cases, this is implemented on the // development server. return null; } } //# sourceMappingURL=next-server.js.map