| | import type { FindComponentsResult, NodeRequestHandler } from '../next-server' |
| | import type { LoadComponentsReturnType } from '../load-components' |
| | import type { Options as ServerOptions } from '../next-server' |
| | import type { Params } from '../request/params' |
| | import type { ParsedUrl } from '../../shared/lib/router/utils/parse-url' |
| | import type { ParsedUrlQuery } from 'querystring' |
| | import type { UrlWithParsedQuery } from 'url' |
| | import type { MiddlewareRoutingItem } from '../base-server' |
| | import type { RouteDefinition } from '../route-definitions/route-definition' |
| | import type { RouteMatcherManager } from '../route-matcher-managers/route-matcher-manager' |
| | import { |
| | addRequestMeta, |
| | getRequestMeta, |
| | type NextParsedUrlQuery, |
| | type NextUrlWithParsedQuery, |
| | } from '../request-meta' |
| | import type { DevBundlerService } from '../lib/dev-bundler-service' |
| | import type { IncrementalCache } from '../lib/incremental-cache' |
| | import type { UnwrapPromise } from '../../lib/coalesced-function' |
| | import type { NodeNextResponse, NodeNextRequest } from '../base-http/node' |
| | import type { RouteEnsurer } from '../route-matcher-managers/dev-route-matcher-manager' |
| | import type { PagesManifest } from '../../build/webpack/plugins/pages-manifest-plugin' |
| |
|
| | import * as React from 'react' |
| | import fs from 'fs' |
| | import { Worker } from 'next/dist/compiled/jest-worker' |
| | import { join as pathJoin } from 'path' |
| | import { PUBLIC_DIR_MIDDLEWARE_CONFLICT } from '../../lib/constants' |
| | import { findPagesDir } from '../../lib/find-pages-dir' |
| | import { |
| | PHASE_DEVELOPMENT_SERVER, |
| | PAGES_MANIFEST, |
| | APP_PATHS_MANIFEST, |
| | COMPILER_NAMES, |
| | PRERENDER_MANIFEST, |
| | } from '../../shared/lib/constants' |
| | import Server, { WrappedBuildError } from '../next-server' |
| | import { normalizePagePath } from '../../shared/lib/page-path/normalize-page-path' |
| | import { pathHasPrefix } from '../../shared/lib/router/utils/path-has-prefix' |
| | import { removePathPrefix } from '../../shared/lib/router/utils/remove-path-prefix' |
| | import { Telemetry } from '../../telemetry/storage' |
| | import { type Span, setGlobal, trace } from '../../trace' |
| | import { traceGlobals } from '../../trace/shared' |
| | import { findPageFile } from '../lib/find-page-file' |
| | import { getFormattedNodeOptionsWithoutInspect } from '../lib/utils' |
| | import { withCoalescedInvoke } from '../../lib/coalesced-function' |
| | import { |
| | loadDefaultErrorComponents, |
| | type ErrorModule, |
| | } from '../load-default-error-components' |
| | import { DecodeError, MiddlewareNotFoundError } from '../../shared/lib/utils' |
| | import * as Log from '../../build/output/log' |
| | import isError, { getProperError } from '../../lib/is-error' |
| | import { defaultConfig, type NextConfigComplete } from '../config-shared' |
| | import { isMiddlewareFile } from '../../build/utils' |
| | import { formatServerError } from '../../lib/format-server-error' |
| | import { DevRouteMatcherManager } from '../route-matcher-managers/dev-route-matcher-manager' |
| | import { DevPagesRouteMatcherProvider } from '../route-matcher-providers/dev/dev-pages-route-matcher-provider' |
| | import { DevPagesAPIRouteMatcherProvider } from '../route-matcher-providers/dev/dev-pages-api-route-matcher-provider' |
| | import { DevAppPageRouteMatcherProvider } from '../route-matcher-providers/dev/dev-app-page-route-matcher-provider' |
| | import { DevAppRouteRouteMatcherProvider } from '../route-matcher-providers/dev/dev-app-route-route-matcher-provider' |
| | import { NodeManifestLoader } from '../route-matcher-providers/helpers/manifest-loaders/node-manifest-loader' |
| | import { BatchedFileReader } from '../route-matcher-providers/dev/helpers/file-reader/batched-file-reader' |
| | import { DefaultFileReader } from '../route-matcher-providers/dev/helpers/file-reader/default-file-reader' |
| | import { LRUCache } from '../lib/lru-cache' |
| | import { getMiddlewareRouteMatcher } from '../../shared/lib/router/utils/middleware-route-matcher' |
| | import { DetachedPromise } from '../../lib/detached-promise' |
| | import { isPostpone } from '../lib/router-utils/is-postpone' |
| | import { generateInterceptionRoutesRewrites } from '../../lib/generate-interception-routes-rewrites' |
| | import { buildCustomRoute } from '../../lib/build-custom-route' |
| | import { decorateServerError } from '../../shared/lib/error-source' |
| | import type { ServerOnInstrumentationRequestError } from '../app-render/types' |
| | import type { ServerComponentsHmrCache } from '../response-cache' |
| | import { logRequests } from './log-requests' |
| | import { FallbackMode, fallbackModeToFallbackField } from '../../lib/fallback' |
| | import type { PagesDevOverlayBridgeType } from '../../next-devtools/userspace/pages/pages-dev-overlay-setup' |
| | import { |
| | ensureInstrumentationRegistered, |
| | getInstrumentationModule, |
| | } from '../lib/router-utils/instrumentation-globals.external' |
| | import type { PrerenderManifest } from '../../build' |
| | import { getRouteRegex } from '../../shared/lib/router/utils/route-regex' |
| | import type { PrerenderedRoute } from '../../build/static-paths/types' |
| | import { HMR_MESSAGE_SENT_TO_BROWSER } from './hot-reloader-types' |
| |
|
| | |
| | let PagesDevOverlayBridgeImpl: PagesDevOverlayBridgeType |
| | const ReactDevOverlay: PagesDevOverlayBridgeType = (props) => { |
| | if (PagesDevOverlayBridgeImpl === undefined) { |
| | PagesDevOverlayBridgeImpl = ( |
| | require('../../next-devtools/userspace/pages/pages-dev-overlay-setup') as typeof import('../../next-devtools/userspace/pages/pages-dev-overlay-setup') |
| | ).PagesDevOverlayBridge |
| | } |
| | return React.createElement(PagesDevOverlayBridgeImpl, props) |
| | } |
| |
|
| | export interface Options extends ServerOptions { |
| | |
| | conf: NextConfigComplete |
| | |
| | |
| | |
| | isNextDevCommand?: boolean |
| |
|
| | |
| | |
| | |
| | bundlerService: DevBundlerService |
| |
|
| | |
| | |
| | |
| | startServerSpan: Span |
| | } |
| |
|
| | export default class DevServer extends Server { |
| | |
| | protected readonly nextConfig: NextConfigComplete |
| |
|
| | |
| | |
| | |
| | |
| | private ready? = new DetachedPromise<void>() |
| | protected sortedRoutes?: string[] |
| | private pagesDir?: string |
| | private appDir?: string |
| | private actualMiddlewareFile?: string |
| | private actualInstrumentationHookFile?: string |
| | private middleware?: MiddlewareRoutingItem |
| | private readonly bundlerService: DevBundlerService |
| | private staticPathsCache: LRUCache< |
| | UnwrapPromise<ReturnType<DevServer['getStaticPaths']>> |
| | > |
| | private startServerSpan: Span |
| | private readonly serverComponentsHmrCache: |
| | | ServerComponentsHmrCache |
| | | undefined |
| |
|
| | protected staticPathsWorker?: { [key: string]: any } & { |
| | loadStaticPaths: typeof import('./static-paths-worker').loadStaticPaths |
| | } |
| |
|
| | private getStaticPathsWorker(): { [key: string]: any } & { |
| | loadStaticPaths: typeof import('./static-paths-worker').loadStaticPaths |
| | } { |
| | const worker = new Worker(require.resolve('./static-paths-worker'), { |
| | maxRetries: 1, |
| | |
| | |
| | numWorkers: 1, |
| | enableWorkerThreads: this.nextConfig.experimental.workerThreads, |
| | forkOptions: { |
| | env: { |
| | ...process.env, |
| | |
| | |
| | |
| | |
| | NODE_OPTIONS: getFormattedNodeOptionsWithoutInspect(), |
| | }, |
| | }, |
| | }) as Worker & { |
| | loadStaticPaths: typeof import('./static-paths-worker').loadStaticPaths |
| | } |
| |
|
| | worker.getStdout().pipe(process.stdout) |
| | worker.getStderr().pipe(process.stderr) |
| |
|
| | return worker |
| | } |
| |
|
| | constructor(options: Options) { |
| | try { |
| | |
| | Error.stackTraceLimit = 50 |
| | } catch {} |
| | super({ ...options, dev: true }) |
| | this.nextConfig = options.conf |
| | this.bundlerService = options.bundlerService |
| | this.startServerSpan = |
| | options.startServerSpan ?? trace('start-next-dev-server') |
| | this.renderOpts.dev = true |
| | this.renderOpts.ErrorDebug = ReactDevOverlay |
| | this.staticPathsCache = new LRUCache( |
| | |
| | 5 * 1024 * 1024, |
| | function length(value) { |
| | return JSON.stringify(value.staticPaths)?.length ?? 0 |
| | } |
| | ) |
| |
|
| | const { pagesDir, appDir } = findPagesDir(this.dir) |
| | this.pagesDir = pagesDir |
| | this.appDir = appDir |
| |
|
| | if (this.nextConfig.experimental.serverComponentsHmrCache) { |
| | |
| | |
| | const hmrCacheSize = Math.max( |
| | this.nextConfig.cacheMaxMemorySize, |
| | defaultConfig.cacheMaxMemorySize |
| | ) |
| | this.serverComponentsHmrCache = new LRUCache( |
| | hmrCacheSize, |
| | function length(value) { |
| | return JSON.stringify(value).length |
| | } |
| | ) |
| | } |
| | } |
| |
|
| | protected override getServerComponentsHmrCache() { |
| | return this.serverComponentsHmrCache |
| | } |
| |
|
| | protected getRouteMatchers(): RouteMatcherManager { |
| | const { pagesDir, appDir } = findPagesDir(this.dir) |
| |
|
| | const ensurer: RouteEnsurer = { |
| | ensure: async (match, pathname) => { |
| | await this.ensurePage({ |
| | definition: match.definition, |
| | page: match.definition.page, |
| | clientOnly: false, |
| | url: pathname, |
| | }) |
| | }, |
| | } |
| |
|
| | const matchers = new DevRouteMatcherManager( |
| | super.getRouteMatchers(), |
| | ensurer, |
| | this.dir |
| | ) |
| | const extensions = this.nextConfig.pageExtensions |
| | const extensionsExpression = new RegExp(`\\.(?:${extensions.join('|')})$`) |
| |
|
| | |
| | if (pagesDir) { |
| | const fileReader = new BatchedFileReader( |
| | new DefaultFileReader({ |
| | |
| | pathnameFilter: (pathname) => extensionsExpression.test(pathname), |
| | }) |
| | ) |
| |
|
| | matchers.push( |
| | new DevPagesRouteMatcherProvider( |
| | pagesDir, |
| | extensions, |
| | fileReader, |
| | this.localeNormalizer |
| | ) |
| | ) |
| | matchers.push( |
| | new DevPagesAPIRouteMatcherProvider( |
| | pagesDir, |
| | extensions, |
| | fileReader, |
| | this.localeNormalizer |
| | ) |
| | ) |
| | } |
| |
|
| | if (appDir) { |
| | |
| | |
| | |
| | |
| | const fileReader = new BatchedFileReader( |
| | new DefaultFileReader({ |
| | |
| | ignorePartFilter: (part) => part.startsWith('_'), |
| | }) |
| | ) |
| |
|
| | |
| | const isTurbopack = !!process.env.TURBOPACK |
| | matchers.push( |
| | new DevAppPageRouteMatcherProvider( |
| | appDir, |
| | extensions, |
| | fileReader, |
| | isTurbopack |
| | ) |
| | ) |
| | matchers.push( |
| | new DevAppRouteRouteMatcherProvider( |
| | appDir, |
| | extensions, |
| | fileReader, |
| | isTurbopack |
| | ) |
| | ) |
| | } |
| |
|
| | return matchers |
| | } |
| |
|
| | protected getBuildId(): string { |
| | return 'development' |
| | } |
| |
|
| | protected async prepareImpl(): Promise<void> { |
| | setGlobal('distDir', this.distDir) |
| | setGlobal('phase', PHASE_DEVELOPMENT_SERVER) |
| |
|
| | |
| | |
| | |
| | const existingTelemetry = traceGlobals.get('telemetry') |
| | const telemetry = |
| | existingTelemetry || new Telemetry({ distDir: this.distDir }) |
| |
|
| | await super.prepareImpl() |
| | await this.matchers.reload() |
| |
|
| | this.ready?.resolve() |
| | this.ready = undefined |
| |
|
| | |
| | this.interceptionRoutePatterns = this.getinterceptionRoutePatterns() |
| |
|
| | |
| | setGlobal('appDir', this.appDir) |
| | setGlobal('pagesDir', this.pagesDir) |
| | |
| | if (!existingTelemetry) { |
| | setGlobal('telemetry', telemetry) |
| | } |
| |
|
| | process.on('unhandledRejection', (reason) => { |
| | if (isPostpone(reason)) { |
| | |
| | |
| | return |
| | } |
| | this.logErrorWithOriginalStack(reason, 'unhandledRejection') |
| | }) |
| | process.on('uncaughtException', (err) => { |
| | this.logErrorWithOriginalStack(err, 'uncaughtException') |
| | }) |
| | } |
| |
|
| | protected async hasPage(pathname: string): Promise<boolean> { |
| | let normalizedPath: string |
| | try { |
| | normalizedPath = normalizePagePath(pathname) |
| | } catch (err) { |
| | console.error(err) |
| | |
| | |
| | |
| | return false |
| | } |
| |
|
| | if (isMiddlewareFile(normalizedPath)) { |
| | return findPageFile( |
| | this.dir, |
| | normalizedPath, |
| | this.nextConfig.pageExtensions, |
| | false |
| | ).then(Boolean) |
| | } |
| |
|
| | let appFile: string | null = null |
| | let pagesFile: string | null = null |
| |
|
| | if (this.appDir) { |
| | appFile = await findPageFile( |
| | this.appDir, |
| | normalizedPath + '/page', |
| | this.nextConfig.pageExtensions, |
| | true |
| | ) |
| | } |
| |
|
| | if (this.pagesDir) { |
| | pagesFile = await findPageFile( |
| | this.pagesDir, |
| | normalizedPath, |
| | this.nextConfig.pageExtensions, |
| | false |
| | ) |
| | } |
| | if (appFile && pagesFile) { |
| | return false |
| | } |
| |
|
| | return Boolean(appFile || pagesFile) |
| | } |
| |
|
| | async runMiddleware(params: { |
| | request: NodeNextRequest |
| | response: NodeNextResponse |
| | parsedUrl: ParsedUrl |
| | parsed: UrlWithParsedQuery |
| | middlewareList: MiddlewareRoutingItem[] |
| | }) { |
| | try { |
| | const result = await super.runMiddleware({ |
| | ...params, |
| | onWarning: (warn) => { |
| | this.logErrorWithOriginalStack(warn, 'warning') |
| | }, |
| | }) |
| |
|
| | if ('finished' in result) { |
| | return result |
| | } |
| |
|
| | result.waitUntil.catch((error) => { |
| | this.logErrorWithOriginalStack(error, 'unhandledRejection') |
| | }) |
| | return result |
| | } catch (error) { |
| | if (error instanceof DecodeError) { |
| | throw error |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if (!(error instanceof MiddlewareNotFoundError)) { |
| | this.logErrorWithOriginalStack(error) |
| | } |
| |
|
| | const err = getProperError(error) |
| | decorateServerError(err, COMPILER_NAMES.edgeServer) |
| | const { request, response, parsedUrl } = params |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if ( |
| | request.url.includes('/_next/static') || |
| | request.url.includes('/__nextjs_attach-nodejs-inspector') || |
| | request.url.includes('/__nextjs_original-stack-frame') || |
| | request.url.includes('/__nextjs_source-map') || |
| | request.url.includes('/__nextjs_error_feedback') |
| | ) { |
| | return { finished: false } |
| | } |
| |
|
| | response.statusCode = 500 |
| | await this.renderError(err, request, response, parsedUrl.pathname) |
| | return { finished: true } |
| | } |
| | } |
| |
|
| | async runEdgeFunction(params: { |
| | req: NodeNextRequest |
| | res: NodeNextResponse |
| | query: ParsedUrlQuery |
| | params: Params | undefined |
| | page: string |
| | appPaths: string[] | null |
| | isAppPath: boolean |
| | }) { |
| | try { |
| | return super.runEdgeFunction({ |
| | ...params, |
| | onError: (err) => this.logErrorWithOriginalStack(err, 'app-dir'), |
| | onWarning: (warn) => { |
| | this.logErrorWithOriginalStack(warn, 'warning') |
| | }, |
| | }) |
| | } catch (error) { |
| | if (error instanceof DecodeError) { |
| | throw error |
| | } |
| | this.logErrorWithOriginalStack(error, 'warning') |
| | const err = getProperError(error) |
| | const { req, res, page } = params |
| |
|
| | res.statusCode = 500 |
| | await this.renderError(err, req, res, page) |
| | return null |
| | } |
| | } |
| |
|
| | public getRequestHandler(): NodeRequestHandler { |
| | const handler = super.getRequestHandler() |
| |
|
| | return (req, res, parsedUrl) => { |
| | const request = this.normalizeReq(req) |
| | const response = this.normalizeRes(res) |
| | const loggingConfig = this.nextConfig.logging |
| |
|
| | if (loggingConfig !== false) { |
| | |
| | |
| | if (!getRequestMeta(req, 'devRequestTimingStart')) { |
| | const requestStart = process.hrtime.bigint() |
| | addRequestMeta(req, 'devRequestTimingStart', requestStart) |
| | } |
| | const isMiddlewareRequest = |
| | getRequestMeta(req, 'middlewareInvoke') ?? false |
| |
|
| | if (!isMiddlewareRequest) { |
| | response.originalResponse.once('close', () => { |
| | |
| | |
| | |
| | const routeMatch = getRequestMeta(req).match |
| |
|
| | if (!routeMatch) { |
| | return |
| | } |
| |
|
| | |
| | |
| | const requestStart = getRequestMeta(req, 'devRequestTimingStart') |
| | if (!requestStart) { |
| | return |
| | } |
| | const requestEnd = process.hrtime.bigint() |
| | logRequests( |
| | request, |
| | response, |
| | loggingConfig, |
| | requestStart, |
| | requestEnd, |
| | getRequestMeta(req, 'devRequestTimingMiddlewareStart'), |
| | getRequestMeta(req, 'devRequestTimingMiddlewareEnd'), |
| | getRequestMeta(req, 'devRequestTimingInternalsEnd'), |
| | getRequestMeta(req, 'devGenerateStaticParamsDuration') |
| | ) |
| | }) |
| | } |
| | } |
| |
|
| | return handler(request, response, parsedUrl) |
| | } |
| | } |
| |
|
| | public async handleRequest( |
| | req: NodeNextRequest, |
| | res: NodeNextResponse, |
| | parsedUrl?: NextUrlWithParsedQuery |
| | ): Promise<void> { |
| | const span = trace('handle-request', undefined, { url: req.url }) |
| | const result = await span.traceAsyncFn(async () => { |
| | await this.ready?.promise |
| | addRequestMeta(req, 'PagesErrorDebug', this.renderOpts.ErrorDebug) |
| | return await super.handleRequest(req, res, parsedUrl) |
| | }) |
| | const memoryUsage = process.memoryUsage() |
| | span |
| | .traceChild('memory-usage', { |
| | url: req.url, |
| | 'memory.rss': String(memoryUsage.rss), |
| | 'memory.heapUsed': String(memoryUsage.heapUsed), |
| | 'memory.heapTotal': String(memoryUsage.heapTotal), |
| | }) |
| | .stop() |
| | return result |
| | } |
| |
|
| | async run( |
| | req: NodeNextRequest, |
| | res: NodeNextResponse, |
| | parsedUrl: UrlWithParsedQuery |
| | ): Promise<void> { |
| | await this.ready?.promise |
| |
|
| | const { basePath } = this.nextConfig |
| | let originalPathname: string | null = null |
| |
|
| | |
| | if (basePath && pathHasPrefix(parsedUrl.pathname || '/', basePath)) { |
| | |
| | |
| | originalPathname = parsedUrl.pathname |
| | parsedUrl.pathname = removePathPrefix(parsedUrl.pathname || '/', basePath) |
| | } |
| |
|
| | const { pathname } = parsedUrl |
| |
|
| | if (pathname!.startsWith('/_next')) { |
| | if (fs.existsSync(pathJoin(this.publicDir, '_next'))) { |
| | throw new Error(PUBLIC_DIR_MIDDLEWARE_CONFLICT) |
| | } |
| | } |
| |
|
| | if (originalPathname) { |
| | |
| | |
| | parsedUrl.pathname = originalPathname |
| | } |
| | try { |
| | return await super.run(req, res, parsedUrl) |
| | } catch (error) { |
| | const err = getProperError(error) |
| | formatServerError(err) |
| | this.logErrorWithOriginalStack(err) |
| | if (!res.sent) { |
| | res.statusCode = 500 |
| | try { |
| | return await this.renderError(err, req, res, pathname!, { |
| | __NEXT_PAGE: (isError(err) && err.page) || pathname || '', |
| | }) |
| | } catch (internalErr) { |
| | console.error(internalErr) |
| | res.body('Internal Server Error').send() |
| | } |
| | } |
| | } |
| | } |
| |
|
| | protected logErrorWithOriginalStack( |
| | err?: unknown, |
| | type?: 'unhandledRejection' | 'uncaughtException' | 'warning' | 'app-dir' |
| | ): void { |
| | this.bundlerService.logErrorWithOriginalStack(err, type) |
| | } |
| |
|
| | protected getPagesManifest(): PagesManifest | undefined { |
| | return ( |
| | NodeManifestLoader.require( |
| | pathJoin(this.serverDistDir, PAGES_MANIFEST) |
| | ) ?? undefined |
| | ) |
| | } |
| |
|
| | protected getAppPathsManifest(): PagesManifest | undefined { |
| | if (!this.enabledDirectories.app) return undefined |
| |
|
| | return ( |
| | NodeManifestLoader.require( |
| | pathJoin(this.serverDistDir, APP_PATHS_MANIFEST) |
| | ) ?? undefined |
| | ) |
| | } |
| |
|
| | protected getinterceptionRoutePatterns(): RegExp[] { |
| | const rewrites = generateInterceptionRoutesRewrites( |
| | Object.keys(this.appPathRoutes ?? {}), |
| | this.nextConfig.basePath |
| | ).map((route) => new RegExp(buildCustomRoute('rewrite', route).regex)) |
| |
|
| | if (this.nextConfig.output === 'export' && rewrites.length > 0) { |
| | Log.error( |
| | 'Intercepting routes are not supported with static export.\nRead more: https://nextjs.org/docs/app/building-your-application/deploying/static-exports#unsupported-features' |
| | ) |
| |
|
| | process.exit(1) |
| | } |
| |
|
| | return rewrites ?? [] |
| | } |
| |
|
| | protected async getMiddleware() { |
| | |
| | |
| | if (this.middleware?.match === null) { |
| | this.middleware.match = getMiddlewareRouteMatcher( |
| | this.middleware.matchers || [] |
| | ) |
| | } |
| | return this.middleware |
| | } |
| |
|
| | protected getNextFontManifest() { |
| | return undefined |
| | } |
| |
|
| | protected async hasMiddleware(): Promise<boolean> { |
| | return this.hasPage(this.actualMiddlewareFile!) |
| | } |
| |
|
| | protected async ensureMiddleware(url: string) { |
| | return this.ensurePage({ |
| | page: this.actualMiddlewareFile!, |
| | clientOnly: false, |
| | definition: undefined, |
| | url, |
| | }) |
| | } |
| |
|
| | protected async loadInstrumentationModule(): Promise<any> { |
| | let instrumentationModule: any |
| | if ( |
| | this.actualInstrumentationHookFile && |
| | (await this.ensurePage({ |
| | page: this.actualInstrumentationHookFile!, |
| | clientOnly: false, |
| | definition: undefined, |
| | }) |
| | .then(() => true) |
| | .catch(() => false)) |
| | ) { |
| | try { |
| | instrumentationModule = await getInstrumentationModule( |
| | this.dir, |
| | this.nextConfig.distDir |
| | ) |
| | } catch (err: any) { |
| | err.message = `An error occurred while loading instrumentation hook: ${err.message}` |
| | throw err |
| | } |
| | } |
| | return instrumentationModule |
| | } |
| |
|
| | protected async runInstrumentationHookIfAvailable() { |
| | await ensureInstrumentationRegistered(this.dir, this.nextConfig.distDir) |
| | } |
| |
|
| | protected async ensureEdgeFunction({ |
| | page, |
| | appPaths, |
| | url, |
| | }: { |
| | page: string |
| | appPaths: string[] | null |
| | url: string |
| | }) { |
| | return this.ensurePage({ |
| | page, |
| | appPaths, |
| | clientOnly: false, |
| | definition: undefined, |
| | url, |
| | }) |
| | } |
| |
|
| | generateRoutes(_dev?: boolean) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | } |
| |
|
| | protected async getStaticPaths({ |
| | pathname, |
| | urlPathname, |
| | requestHeaders, |
| | page, |
| | isAppPath, |
| | }: { |
| | pathname: string |
| | urlPathname: string |
| | requestHeaders: IncrementalCache['requestHeaders'] |
| | page: string |
| | isAppPath: boolean |
| | }): Promise<{ |
| | prerenderedRoutes?: PrerenderedRoute[] |
| | staticPaths?: string[] |
| | fallbackMode?: FallbackMode |
| | }> { |
| | |
| | |
| |
|
| | const __getStaticPaths = async () => { |
| | const { configFileName, httpAgentOptions } = this.nextConfig |
| | const { locales, defaultLocale } = this.nextConfig.i18n || {} |
| | const staticPathsWorker = this.getStaticPathsWorker() |
| |
|
| | try { |
| | const pathsResult = await staticPathsWorker.loadStaticPaths({ |
| | dir: this.dir, |
| | distDir: this.distDir, |
| | pathname, |
| | config: { |
| | pprConfig: this.nextConfig.experimental.ppr, |
| | configFileName, |
| | cacheComponents: Boolean(this.nextConfig.cacheComponents), |
| | }, |
| | httpAgentOptions, |
| | locales, |
| | defaultLocale, |
| | page, |
| | isAppPath, |
| | requestHeaders, |
| | cacheHandler: this.nextConfig.cacheHandler, |
| | cacheHandlers: this.nextConfig.cacheHandlers, |
| | cacheLifeProfiles: this.nextConfig.cacheLife, |
| | fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix, |
| | isrFlushToDisk: this.nextConfig.experimental.isrFlushToDisk, |
| | cacheMaxMemorySize: this.nextConfig.cacheMaxMemorySize, |
| | nextConfigOutput: this.nextConfig.output, |
| | buildId: this.buildId, |
| | authInterrupts: Boolean(this.nextConfig.experimental.authInterrupts), |
| | sriEnabled: Boolean(this.nextConfig.experimental.sri?.algorithm), |
| | }) |
| | return pathsResult |
| | } finally { |
| | |
| | staticPathsWorker.end() |
| | } |
| | } |
| | const result = this.staticPathsCache.get(pathname) |
| |
|
| | const nextInvoke = withCoalescedInvoke(__getStaticPaths)( |
| | `staticPaths-${pathname}`, |
| | [] |
| | ) |
| | .then(async (res) => { |
| | const { prerenderedRoutes, fallbackMode: fallback } = res.value |
| |
|
| | if (isAppPath) { |
| | if (this.nextConfig.output === 'export') { |
| | if (!prerenderedRoutes) { |
| | throw new Error( |
| | `Page "${page}" is missing exported function "generateStaticParams()", which is required with "output: export" config.` |
| | ) |
| | } |
| |
|
| | if ( |
| | !prerenderedRoutes.some((item) => item.pathname === urlPathname) |
| | ) { |
| | throw new Error( |
| | `Page "${page}" is missing param "${pathname}" in "generateStaticParams()", which is required with "output: export" config.` |
| | ) |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | if ( |
| | isAppPath && |
| | this.nextConfig.cacheComponents && |
| | |
| | result && |
| | |
| | result.prerenderedRoutes?.length !== prerenderedRoutes?.length |
| | ) { |
| | this.bundlerService.sendHmrMessage({ |
| | type: HMR_MESSAGE_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES, |
| | hash: `generateStaticParams-${Date.now()}`, |
| | }) |
| | } |
| | } |
| |
|
| | if (!isAppPath && this.nextConfig.output === 'export') { |
| | if (fallback === FallbackMode.BLOCKING_STATIC_RENDER) { |
| | throw new Error( |
| | 'getStaticPaths with "fallback: blocking" cannot be used with "output: export". See more info here: https://nextjs.org/docs/advanced-features/static-html-export' |
| | ) |
| | } else if (fallback === FallbackMode.PRERENDER) { |
| | throw new Error( |
| | 'getStaticPaths with "fallback: true" cannot be used with "output: export". See more info here: https://nextjs.org/docs/advanced-features/static-html-export' |
| | ) |
| | } |
| | } |
| |
|
| | const value: { |
| | staticPaths: string[] | undefined |
| | prerenderedRoutes: PrerenderedRoute[] | undefined |
| | fallbackMode: FallbackMode | undefined |
| | } = { |
| | staticPaths: prerenderedRoutes?.map((route) => route.pathname), |
| | prerenderedRoutes, |
| | fallbackMode: fallback, |
| | } |
| |
|
| | if ( |
| | res.value?.fallbackMode !== undefined && |
| | |
| | (!isAppPath || (prerenderedRoutes && prerenderedRoutes.length > 0)) |
| | ) { |
| | |
| | |
| | const rawExistingManifest = await fs.promises.readFile( |
| | pathJoin(this.distDir, PRERENDER_MANIFEST), |
| | 'utf8' |
| | ) |
| | const existingManifest: PrerenderManifest = |
| | JSON.parse(rawExistingManifest) |
| | for (const staticPath of value.staticPaths || []) { |
| | existingManifest.routes[staticPath] = {} as any |
| | } |
| |
|
| | existingManifest.dynamicRoutes[pathname] = { |
| | dataRoute: null, |
| | dataRouteRegex: null, |
| | fallback: fallbackModeToFallbackField(res.value.fallbackMode, page), |
| | fallbackRevalidate: false, |
| | fallbackExpire: undefined, |
| | fallbackHeaders: undefined, |
| | fallbackStatus: undefined, |
| | fallbackRootParams: undefined, |
| | fallbackRouteParams: undefined, |
| | fallbackSourceRoute: pathname, |
| | prefetchDataRoute: undefined, |
| | prefetchDataRouteRegex: undefined, |
| | routeRegex: getRouteRegex(pathname).re.source, |
| | experimentalPPR: undefined, |
| | renderingMode: undefined, |
| | allowHeader: [], |
| | } |
| |
|
| | const updatedManifest = JSON.stringify(existingManifest) |
| |
|
| | if (updatedManifest !== rawExistingManifest) { |
| | await fs.promises.writeFile( |
| | pathJoin(this.distDir, PRERENDER_MANIFEST), |
| | updatedManifest |
| | ) |
| | } |
| | } |
| | this.staticPathsCache.set(pathname, value) |
| | return value |
| | }) |
| | .catch((err) => { |
| | this.staticPathsCache.remove(pathname) |
| | if (!result) throw err |
| | Log.error(`Failed to generate static paths for ${pathname}:`) |
| | console.error(err) |
| | }) |
| |
|
| | if (result) { |
| | return result |
| | } |
| | return nextInvoke as NonNullable<typeof result> |
| | } |
| |
|
| | protected async ensurePage(opts: { |
| | page: string |
| | clientOnly: boolean |
| | appPaths?: ReadonlyArray<string> | null |
| | definition: RouteDefinition | undefined |
| | url?: string |
| | }): Promise<void> { |
| | await this.bundlerService.ensurePage(opts) |
| | } |
| |
|
| | protected async findPageComponents({ |
| | locale, |
| | page, |
| | query, |
| | params, |
| | isAppPath, |
| | appPaths = null, |
| | shouldEnsure, |
| | url, |
| | }: { |
| | locale: string | undefined |
| | page: string |
| | query: NextParsedUrlQuery |
| | params: Params |
| | isAppPath: boolean |
| | sriEnabled?: boolean |
| | appPaths?: ReadonlyArray<string> | null |
| | shouldEnsure: boolean |
| | url?: string |
| | }): Promise<FindComponentsResult | null> { |
| | await this.ready?.promise |
| |
|
| | const compilationErr = await this.getCompilationError(page) |
| | if (compilationErr) { |
| | |
| | throw new WrappedBuildError(compilationErr) |
| | } |
| | if (shouldEnsure || this.serverOptions.customServer) { |
| | await this.ensurePage({ |
| | page, |
| | appPaths, |
| | clientOnly: false, |
| | definition: undefined, |
| | url, |
| | }) |
| | } |
| |
|
| | this.nextFontManifest = super.getNextFontManifest() |
| |
|
| | return await super.findPageComponents({ |
| | page, |
| | query, |
| | params, |
| | locale, |
| | isAppPath, |
| | shouldEnsure, |
| | url, |
| | }) |
| | } |
| |
|
| | protected async getFallbackErrorComponents( |
| | url?: string |
| | ): Promise<LoadComponentsReturnType<ErrorModule> | null> { |
| | await this.bundlerService.getFallbackErrorComponents(url) |
| | return await loadDefaultErrorComponents(this.distDir) |
| | } |
| |
|
| | async getCompilationError(page: string): Promise<any> { |
| | return await this.bundlerService.getCompilationError(page) |
| | } |
| |
|
| | protected async instrumentationOnRequestError( |
| | ...args: Parameters<ServerOnInstrumentationRequestError> |
| | ) { |
| | await super.instrumentationOnRequestError(...args) |
| |
|
| | const [err, , , silenceLog] = args |
| | if (!silenceLog) { |
| | this.logErrorWithOriginalStack(err, 'app-dir') |
| | } |
| | } |
| | } |
| |
|