UNPKG

next

Version:

The React Framework

253 lines (252 loc) • 10.8 kB
import { byteLength } from './api-utils/web'; import BaseServer, { NoFallbackError } from './base-server'; import { generateETag } from './lib/etag'; import { addRequestMeta, getRequestMeta } from './request-meta'; import WebResponseCache from './response-cache/web'; import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash'; import { isDynamicRoute } from '../shared/lib/router/utils'; import { interpolateDynamicPath, normalizeVercelUrl, normalizeDynamicRouteParams } from './server-utils'; import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex'; import { getRouteMatcher } from '../shared/lib/router/utils/route-matcher'; import { IncrementalCache } from './lib/incremental-cache'; import { buildCustomRoute } from '../lib/build-custom-route'; import { UNDERSCORE_NOT_FOUND_ROUTE } from '../api/constants'; import { getEdgeInstrumentationModule } from './web/globals'; import { getEdgePreviewProps } from './web/get-edge-preview-props'; export default class NextWebServer extends BaseServer { constructor(options){ super(options), this.handleCatchallRenderRequest = async (req, res, parsedUrl)=>{ let { pathname, query } = parsedUrl; if (!pathname) { throw Object.defineProperty(new Error('pathname is undefined'), "__NEXT_ERROR_CODE", { value: "E408", enumerable: false, configurable: true }); } // interpolate query information into page for dynamic route // so that rewritten paths are handled properly const normalizedPage = this.serverOptions.webServerConfig.pathname; if (pathname !== normalizedPage) { pathname = normalizedPage; if (isDynamicRoute(pathname)) { const routeRegex = getNamedRouteRegex(pathname, { prefixRouteKeys: false }); const dynamicRouteMatcher = getRouteMatcher(routeRegex); const defaultRouteMatches = dynamicRouteMatcher(pathname); const paramsResult = normalizeDynamicRouteParams(query, routeRegex, defaultRouteMatches, false); const normalizedParams = paramsResult.hasValidParams ? paramsResult.params : query; pathname = interpolateDynamicPath(pathname, normalizedParams, routeRegex); normalizeVercelUrl(req, Object.keys(routeRegex.routeKeys), routeRegex); } } // next.js core assumes page path without trailing slash pathname = removeTrailingSlash(pathname); if (this.i18nProvider) { const { detectedLocale } = await this.i18nProvider.analyze(pathname); if (detectedLocale) { addRequestMeta(req, 'locale', detectedLocale); } } const bubbleNoFallback = getRequestMeta(req, 'bubbleNoFallback'); try { await this.render(req, res, pathname, query, parsedUrl, true); return true; } catch (err) { if (err instanceof NoFallbackError && bubbleNoFallback) { return false; } throw err; } }; // Extend `renderOpts`. Object.assign(this.renderOpts, options.webServerConfig.extendRenderOpts); } async getIncrementalCache({ requestHeaders }) { const dev = !!this.renderOpts.dev; // incremental-cache is request specific // although can have shared caches in module scope // per-cache handler return new IncrementalCache({ dev, requestHeaders, requestProtocol: 'https', allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys, minimalMode: this.minimalMode, fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix, maxMemoryCacheSize: this.nextConfig.cacheMaxMemorySize, flushToDisk: false, CurCacheHandler: this.serverOptions.webServerConfig.incrementalCacheHandler, getPrerenderManifest: ()=>this.getPrerenderManifest() }); } getResponseCache() { return new WebResponseCache(this.minimalMode); } async hasPage(page) { return page === this.serverOptions.webServerConfig.page; } getBuildId() { return this.serverOptions.buildId; } getEnabledDirectories() { return { app: this.serverOptions.webServerConfig.pagesType === 'app', pages: this.serverOptions.webServerConfig.pagesType === 'pages' }; } getPagesManifest() { return { // keep same theme but server path doesn't need to be accurate [this.serverOptions.webServerConfig.pathname]: `server${this.serverOptions.webServerConfig.page}.js` }; } getAppPathsManifest() { const page = this.serverOptions.webServerConfig.page; return { [this.serverOptions.webServerConfig.page]: `app${page}.js` }; } attachRequestMeta(req, parsedUrl) { addRequestMeta(req, 'initQuery', { ...parsedUrl.query }); } getPrerenderManifest() { return { version: -1, routes: {}, dynamicRoutes: {}, notFoundRoutes: [], preview: getEdgePreviewProps() }; } getNextFontManifest() { return this.serverOptions.webServerConfig.extendRenderOpts.nextFontManifest; } renderHTML(req, res, pathname, query, renderOpts) { const { renderToHTML } = this.serverOptions.webServerConfig; if (!renderToHTML) { throw Object.defineProperty(new Error('Invariant: routeModule should be configured when rendering pages'), "__NEXT_ERROR_CODE", { value: "E4", enumerable: false, configurable: true }); } // For edge runtime if the pathname hit as /_not-found entrypoint, // override the pathname to /404 for rendering if (pathname === UNDERSCORE_NOT_FOUND_ROUTE) { pathname = '/404'; } return renderToHTML(req, res, pathname, query, // Edge runtime does not support ISR/PPR, so we don't need to pass in // the unknown params. null, Object.assign(renderOpts, { disableOptimizedLoading: true, runtime: 'experimental-edge' }), undefined, false, { buildId: this.serverOptions.buildId }); } async sendRenderResult(_req, res, options) { res.setHeader('X-Edge-Runtime', '1'); // Add necessary headers. // @TODO: Share the isomorphic logic with server/send-payload.ts. if (options.poweredByHeader && options.type === 'html') { res.setHeader('X-Powered-By', 'Next.js'); } if (!res.getHeader('Content-Type')) { res.setHeader('Content-Type', options.result.contentType ? options.result.contentType : options.type === 'json' ? 'application/json' : 'text/html; charset=utf-8'); } let promise; if (options.result.isDynamic) { promise = options.result.pipeTo(res.transformStream.writable); } else { const payload = options.result.toUnchunkedString(); res.setHeader('Content-Length', String(byteLength(payload))); if (options.generateEtags) { res.setHeader('ETag', generateETag(payload)); } res.body(payload); } res.send(); // If we have a promise, wait for it to resolve. if (promise) await promise; } async findPageComponents({ page, query, params, url: _url }) { const result = await this.serverOptions.webServerConfig.loadComponent(page); if (!result) return null; return { query: { ...query || {}, ...params || {} }, components: result }; } // Below are methods that are not implemented by the web server as they are // handled by the upstream proxy (edge runtime or node server). async runApi() { // This web server does not need to handle API requests. return true; } async handleApiRequest() { // Edge API requests are handled separately in minimal mode. return false; } loadEnvConfig() { // The web server does not need to load the env config. This is done by the // runtime already. } getPublicDir() { // Public files are not handled by the web server. return ''; } getHasStaticDir() { return false; } getFontManifest() { return undefined; } handleCompression() { // For the web server layer, compression is automatically handled by the // upstream proxy (edge runtime or node server) and we can simply skip here. } async handleUpgrade() { // The web server does not support web sockets. } async getFallbackErrorComponents(_url) { // The web server does not need to handle fallback errors in production. return null; } getRoutesManifest() { // The web server does not need to handle rewrite rules. This is done by the // upstream proxy (edge runtime or node server). return undefined; } getMiddleware() { // The web server does not need to handle middleware. This is done by the // upstream proxy (edge runtime or node server). return Promise.resolve(undefined); } getFilesystemPaths() { return new Set(); } getinterceptionRoutePatterns() { var _this_serverOptions_webServerConfig_interceptionRouteRewrites; return ((_this_serverOptions_webServerConfig_interceptionRouteRewrites = this.serverOptions.webServerConfig.interceptionRouteRewrites) == null ? void 0 : _this_serverOptions_webServerConfig_interceptionRouteRewrites.map((rewrite)=>new RegExp(buildCustomRoute('rewrite', rewrite).regex))) ?? []; } async loadInstrumentationModule() { return await getEdgeInstrumentationModule(); } async instrumentationOnRequestError(...args) { await super.instrumentationOnRequestError(...args); const err = args[0]; if (process.env.NODE_ENV !== 'production' && typeof __next_log_error__ === 'function') { __next_log_error__(err); } else { console.error(err); } } } //# sourceMappingURL=web-server.js.map