Relevant source files
The following files were used as context for generating this wiki page:
- packages/next/src/server/dev/next-dev-server.ts
- packages/next/src/server/dev/hot-reloader-turbopack.ts
- packages/next/src/server/dev/hot-reloader-webpack.ts
- packages/next/src/server/lib/router-server.ts
- packages/next/src/server/dev/on-demand-entry-handler.ts
- packages/next/src/server/next-server.ts
- packages/next/src/server/lib/router-utils/setup-dev-bundler.ts
- packages/next/src/cli/next-dev.ts
- packages/next/src/server/base-server.ts
- packages/next/src/server/lib/start-server.ts
- packages/next/src/server/lib/dev-bundler-service.ts
- packages/next/src/server/next.ts
- packages/next/src/server/dev/log-requests.ts
- packages/next/src/server/get-app-route-from-entrypoint.ts
- packages/next/src/server/get-route-from-entrypoint.ts
- packages/next/src/server/match-bundle.ts
- packages/next/src/shared/lib/page-path/ensure-leading-slash.ts
- packages/next/src/shared/lib/router/utils/app-paths.ts
- packages/next/src/shared/lib/router/utils/get-route-from-asset-path.ts
- packages/next/src/shared/lib/router/utils/interception-routes.ts
- packages/next/src/shared/lib/router/utils/is-dynamic.ts
Overview
The development server manages the runtime execution, on-demand compilation, routing, and hot module replacement loop for Next.js applications in development. Built around the core Server Architecture, DevServer extends Server (inheriting from NextNodeServer) to initialize development lifecycles, coordinate with compiler services, and serve virtual filesystem items packages/next/src/server/dev/next-dev-server.ts:38-127. It bridges incoming HTTP requests with bundler instances like Webpack and Turbopack via DevBundlerService and router middleware packages/next/src/server/dev/next-dev-server.ts:117-119, packages/next/src/server/lib/dev-bundler-service.ts:20-31. The development server solves the problem of rapid, lazy compilation and continuous feedback during local development by resolving dynamic route segments, watching file modifications, streaming compilation errors packages/next/src/server/dev/hot-reloader-turbopack.ts:1968-2032, and logging request metrics and stack frames packages/next/src/server/dev/log-requests.ts:43-82.
Sources: packages/next/src/server/dev/next-dev-server.ts:38-127, packages/next/src/server/dev/hot-reloader-turbopack.ts:1968-2032, packages/next/src/server/dev/log-requests.ts:43-82, packages/next/src/server/lib/dev-bundler-service.ts:20-31
DevServer architecture and class inheritance
DevServer Public Class Interface and Inheritance
DevServer is exported as the default class from packages/next/src/server/dev/next-dev-server.ts and extends Server, which corresponds to NextNodeServer. The class overrides the nextConfig property type to enforce the complete configuration type NextConfigComplete rather than the runtime-only subset. Its primary state includes a readiness deferred promise created via createPromiseWithResolvers<void>(), sorted route lists, path references for pages and app directories, actual middleware and instrumentation file paths, a bundlerService handle of type DevBundlerService, an LRU cache for static paths, a startup trace span, and cache structures for server component hot module replacement.
export interface Options extends ServerOptions {
// Override type to make the full config available instead of only NextConfigRuntime
conf: NextConfigComplete
/**
* Tells of Next.js is running from the `next dev` command
*/
isNextDevCommand?: boolean
/**
* Interface to the development bundler.
*/
bundlerService: DevBundlerService
/**
* Trace span for server startup.
*/
startServerSpan: Span
}
export default class DevServer extends Server {
// Override type to make the full config available instead of only NextConfigRuntime
protected readonly nextConfig: NextConfigComplete
/**
* The promise that resolves when the server is ready. When this is unset
* the server is ready.
*/
private ready? = createPromiseWithResolvers<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
| undefinedSources: packages/next/src/server/dev/next-dev-server.ts:108-149
Note
The ready promise field on DevServer acts as an internal gatekeeper: while unset or pending, incoming HTTP requests and execution runs await its resolution before proceeding through super.handleRequest and super.run.
Sources: packages/next/src/server/dev/next-dev-server.ts:135-135, packages/next/src/server/dev/next-dev-server.ts:525-525, packages/next/src/server/dev/next-dev-server.ts:546-546
Server Lifecycle Instantiation and Wrapper Coordination
The factory orchestration layer is governed by NextServer inside packages/next/src/server/next.ts. When next dev is executed, NextServer.createServer inspects the development flag (options.dev). If dev is true, it dynamically imports and instantiates the DevServer implementation exported by packages/next/src/server/dev/next-dev-server.ts.
private async createServer(
options: ServerOptions | DevServerOptions
): Promise<NextNodeServer> {
let ServerImplementation: typeof NextNodeServer
if (options.dev) {
ServerImplementation = (
require('./dev/next-dev-server') as typeof import('./dev/next-dev-server')
).default as typeof import('./dev/next-dev-server').default
} else {
ServerImplementation = await getServerImpl()
}
const server = new ServerImplementation(options)
return server
}Sources: packages/next/src/server/next.ts:326-340
Warning
Attempting to run next start with an output: 'export' or output: 'standalone' configuration triggers immediate validation failures or warnings in NextServer.getServer(), blocking production server instances from inadvertently bootstrapping development infrastructure.
Sources: packages/next/src/server/next.ts:390-401
Bundler integration and entry coordination
DevBundlerService Architecture
DevBundlerService acts as an intermediary layer linking DevServer to the underlying development bundler. It maintains an internal LRU cache for incremental static regeneration (ISR) manifests with a maximum capacity of $8,000$ entries, where each entry is assigned a fixed length weight of $16$. The service binds hot reloader methods such as setCacheStatus, setReactDebugChannel, and sendErrorsToBrowser directly from the bundler instance. When request insights are enabled via configuration, it subscribes to insight updates and dispatches them across WebSockets as REQUEST_INSIGHTS_UPDATE messages.
export class DevBundlerService {
public appIsrManifestInner: InstanceType<typeof LRUCache<boolean>>
public setCacheStatus: NextJsHotReloaderInterface['setCacheStatus']
public setReactDebugChannel: NextJsHotReloaderInterface['setReactDebugChannel']
public sendErrorsToBrowser: NextJsHotReloaderInterface['sendErrorsToBrowser']
private unsubscribeRequestInsights?: () => void
constructor(
private readonly bundler: DevBundler,
private readonly handler: WorkerRequestHandler,
requestInsightsEnabled: boolean
) {
this.appIsrManifestInner = new LRUCache(
8_000,
function length() {
return 16
}
)
const { hotReloader } = bundler
this.setCacheStatus = hotReloader.setCacheStatus.bind(hotReloader)
this.setReactDebugChannel =
hotReloader.setReactDebugChannel.bind(hotReloader)
this.sendErrorsToBrowser = hotReloader.sendErrorsToBrowser.bind(hotReloader)
if (requestInsightsEnabled) {
this.unsubscribeRequestInsights = subscribeRequestInsights((insight) => {
hotReloader.send({
type: HMR_MESSAGE_SENT_TO_BROWSER.REQUEST_INSIGHTS_UPDATE,
insight,
})
})
}
}Sources: packages/next/src/server/lib/dev-bundler-service.ts:20-55
Note
The ISR manifest map is serialized into a plain object when requested via the appIsrManifest getter, but transmission via setIsrStatus is restricted to legacy Pages Router clients or App Router clients with Cache Components disabled.
Sources: packages/next/src/server/lib/dev-bundler-service.ts:128-155
Setup Options and Virtual Manifests
The initialization procedure orchestrated by setupDevBundler accepts a SetupOpts configuration record and yields a set of server fields, a hot reloader interface, a request handler, and error-logging utilities. It registers virtual filesystem items into the file system checker (opts.fsChecker.devVirtualFsItems) to intercept requests targeting runtime development manifests.
| Virtual Manifest Path | Purpose / Description |
|---|---|
/_next/static/development/_devclient-pages-manifest.json | Exposes filtered sort routes excluding app directory files for legacy client consumption. |
/_next/static/development/_devmiddleware-manifest.json | Exposes active middleware matchers and routing rules. |
/_next/static/development/_turbopack-middleware-manifest.json | Exposes Turbopack-specific middleware matchers and routing patterns. |
export type SetupOpts = {
renderServer: LazyRenderServerInstance
dir: string
turbo?: boolean
appDir?: string
pagesDir?: string
telemetry: Telemetry
isCustomServer?: boolean
fsChecker: UnwrapPromise<
ReturnType<typeof import('./filesystem').setupFsCheck>
>
nextConfig: NextConfigComplete
port: number
onDevServerCleanup: ((listener: () => Promise<void>) => void) | undefined
resetFetch: () => void
serverFastRefresh?: boolean
hasVulnerabilityInsight: Promise<boolean>
}Sources: packages/next/src/server/lib/router-utils/setup-dev-bundler.ts:128-145, packages/next/src/server/lib/router-utils/setup-dev-bundler.ts:1496-1533
Warning
The virtual manifest request handler checks path inclusion via pathname.includes(...). Requests matching these paths terminate early with a 200 status code and a JSON_CONTENT_TYPE_HEADER header, bypassing standard disk reads.
Sources: packages/next/src/server/lib/router-utils/setup-dev-bundler.ts:1505-1533
Lifecycle Hooks and Error Formatting
DevBundlerService wraps bundler operations with tracing spans and error deobfuscation. Its ensurePage method wraps the hot reloader's page compilation call inside a DevBundlerServiceSpan.ensurePage trace span labeled with the name 'compile route'. Similarly, logErrorWithOriginalStack deobfuscates incoming error text strings using deobfuscateText before evaluating whether the error instance represents a ModuleBuildError or a TurbopackInternalError.
public ensurePage: typeof this.bundler.hotReloader.ensurePage = async (
definition
) => {
// TODO: remove after ensure is pulled out of server
return await getTracer().trace(
DevBundlerServiceSpan.ensurePage,
{ spanName: 'compile route' },
() => this.bundler.hotReloader.ensurePage(definition)
)
}Sources: packages/next/src/server/lib/dev-bundler-service.ts:62-71, packages/next/src/server/lib/router-utils/setup-dev-bundler.ts:1535-1557
On-demand compilation and page entry handling
Dynamic Entry Resolution and Route Key Generation
Next.js manages development compilation lazily by resolving entry points on demand when requests hit the server. The on-demand entry handler constructs unique keys for individual page bundles using getEntryKey, which combines the compiler type, page bundle type, and normalized page path to prevent naming collisions between the app/ and pages/ directories.
export function getEntryKey(
compilerType: CompilerNameValues,
pageBundleType: PAGE_TYPES,
page: string
) {
// TODO: handle the /children slot better
// this is a quick hack to handle when children is provided as children/page instead of /page
const pageKey = page.replace(/(@[^/]+)\/children/g, '$1')
return `${compilerType}@${pageBundleType}@${pageKey}`
}Sources: packages/next/src/server/dev/on-demand-entry-handler.ts:117-126
Entry resolution relies on matching bundle file paths against specific regex patterns to extract the target route. getRouteFromEntrypoint delegates to matchBundle across server route patterns, browser route patterns, and app route patterns.
| Function / Helper | Target Pattern Regex | Purpose |
|---|---|---|
SERVER_ROUTE_NAME_REGEX | ^pages[/\\](.*)$ | Matches server-side pages directory entry files. |
BROWSER_ROUTE_NAME_REGEX | ^static[/\\]pages[/\\](.*)$ | Matches client-side pages directory entry files. |
APP_ROUTE_NAME_REGEX | ^app[/\\](.*)$ | Matches app directory entry files via getAppRouteFromEntrypoint. |
// matches pages/:page*.js
const SERVER_ROUTE_NAME_REGEX = /^pages[/\\](.*)$/
// matches static/pages/:page*.js
const BROWSER_ROUTE_NAME_REGEX = /^static[/\\]pages[/\\](.*)$/
export default function getRouteFromEntrypoint(
entryFile: string,
app?: boolean
): string | null {
let pagePath = matchBundle(SERVER_ROUTE_NAME_REGEX, entryFile)
if (pagePath) {
return pagePath
}
if (app) {
pagePath = getAppRouteFromEntrypoint(entryFile)
if (pagePath) return pagePath
}
// Potentially the passed item is a browser bundle so we try to match that also
return matchBundle(BROWSER_ROUTE_NAME_REGEX, entryFile)Sources: packages/next/src/server/get-route-from-entrypoint.ts:4-26, packages/next/src/server/get-app-route-from-entrypoint.ts:4-16, packages/next/src/server/match-bundle.ts:3-14
Note
Caches and batchers ensure that concurrent requests targeting the same uncompiled page coalesce into a single compilation pass rather than triggering redundant worker tasks.
Sources: packages/next/src/server/dev/on-demand-entry-handler.ts:1040-1081
Lazy Compilation and ensurePage
The developer server triggers lazy compilation through the ensurePage method exposed by the entry handler. Incoming compilation options (EnsurePageOptions) are processed through a batcher utilizing scheduleOnNextTick to schedule execution and a serialization cache key function to prevent concurrent duplicate compilations for identical pages.
type EnsurePageOptions = {
page: string
appPaths?: ReadonlyArray<string> | null
definition?: RouteDefinition
isApp?: boolean
url?: string
}
// Make sure that we won't have multiple invalidations ongoing concurrently.
const batcher = Batcher.create<EnsurePageOptions, void, string>({
// The cache key here is composed of the elements that affect the
// compilation, namely, the page, whether it's client only, and whether
// it's an app page. This ensures that we don't have multiple compilations
// for the same page happening concurrently.
//
// We don't include the whole match because it contains match specific
// parameters (like route params) that would just bust this cache. Any
// details that would possibly bust the cache should be listed here.
cacheKeyFn: (options) => JSON.stringify(options),
// Schedule the invocation of the ensurePageImpl function on the next tick.
schedulerFn: scheduleOnNextTick,
})
return {
async ensurePage({
page,
appPaths = null,
definition,
isApp,
url,
}: EnsurePageOptions) {
// If the route is actually an app page route, then we should have access
// to the app route definition, and therefore, the appPaths from it.
if (!appPaths && definition && isAppPageRouteDefinition(definition)) {
appPaths = definition.appPaths
}
// Wrap the invocation of the ensurePageImpl function in the pending
// wrapper, which will ensure that we don't have multiple compilations
// for the same page happening concurrently.
return batcher.batch({ page, appPaths, definition, isApp }, async () => {
await ensurePageImpl({
page,
appPaths,
definition,
isApp,
url,
})
})
},Sources: packages/next/src/server/dev/on-demand-entry-handler.ts:1032-1081
Dynamic route matching and path resolution
Dev Route Normalization and App Path Transformation
Next.js normalizes file-system paths within the app/ and pages/ directories into clean request pathnames. The normalizeAppPath function processes app routes by iterating through path segments, stripping empty items, parallel route slots (segments starting with @), route groups, and leaf identifiers like page or route at the end of the array, while ensureConfiguredLeading or ensureLeadingSlash guarantees a leading slash on the result.
export function normalizeAppPath(route: string) {
return ensureLeadingSlash(
route.split('/').reduce((pathname, segment, index, segments) => {
if (!segment) return pathname
if (isGroupSegment(segment)) return pathname
if (segment[0] === '@') return pathname
if ((segment === 'page' || segment === 'route') && index === segments.length - 1) {
return pathname
}
return `${pathname}/${segment}`
}, '')
)
}Sources: packages/next/src/shared/lib/page-path/ensure-leading-slash.ts:5-7, packages/next/src/shared/lib/router/utils/app-paths.ts:23-52
Asset paths generated by the compiler are mapped back to logical routes via getRouteFromAssetPath, which strips extensions and cleans up /index prefix rules for non-dynamic assets. When sorting app paths to select a canonical entry point via compareAppPaths, paths containing parallel slots (/@) are prioritized over root page paths to prevent manifest resolution mismatches.
export default function getRouteFromAssetPath(
assetPath: string,
ext: string = ''
): string {
assetPath = assetPath.replace(/\\/g, '/')
assetPath =
ext && assetPath.endsWith(ext) ? assetPath.slice(0, -ext.length) : assetPath
if (assetPath.startsWith('/index/') && !isDynamicRoute(assetPath)) {
assetPath = assetPath.slice(6)
} else if (assetPath === '/index') {
assetPath = '/'
}
return assetPathSources: packages/next/src/shared/lib/router/utils/get-route-from-asset-path.ts:6-18, packages/next/src/shared/lib/router/utils/app-paths.ts:63-69
Dynamic Segment Detection
Route paths are evaluated for parameterization through isDynamicRoute, which inspects path strings using regular expression matchers. Depending on the strict flag parameter, it toggles between strict segment matching and loose wildcard parsing.
| Regular Expression Name | Expression Value | Matching Behavior |
|---|---|---|
TEST_STRICT_ROUTE | `//[[^/]+](?=/ | $)/` |
TEST_ROUTE | `//[^/][[^/]+][^/](?=/ | $)/` |
// Identify /.*[param].*/ in route string
const TEST_ROUTE = /\/[^/]*\[[^/]+\][^/]*(?=\/|$)/
// Identify /[param]/ in route string
const TEST_STRICT_ROUTE = /\/\[[^/]+\](?=\/|$)/
/**
* Check if a route is dynamic.
*
* @param route - The route to check.
* @param strict - Whether to use strict mode which prohibits segments with prefixes/suffixes (default: true).
* @returns Whether the route is dynamic.
*/
export function isDynamicRoute(route: string, strict: boolean = true): boolean {
if (isInterceptionRouteAppPath(route)) {
route = extractInterceptionRouteInformation(route).interceptedRoute
}
if (strict) {
return TEST_STRICT_ROUTE.test(route)
}
return TEST_ROUTE.test(route)Sources: packages/next/src/shared/lib/router/utils/is-dynamic.ts:6-28
Note
When checking interception route app paths, isDynamicRoute automatically unwraps the intercepted sub-route portion before running the regex test.
Sources: packages/next/src/shared/lib/router/utils/is-dynamic.ts:20-22
Interception Route Handling
Interception routes allow loading a component from another part of the app within the current layout context. The route parser recognizes four distinct structural markers defined in INTERCEPTION_ROUTE_MARKERS.
| Interception Marker | Navigation Scope | Target Transformation Rule |
|---|---|---|
(.) | Sibling directory | Appends the intercepted path directly to the normalized intercepting route. |
(..) | One level up | Removes the last segment of the intercepting route before appending. |
(...) | Root directory (/) | Prepends root-level path resolution to the intercepted segment. |
(..)(..) | Two levels up | Removes the last two segments of the intercepting route before appending. |
export const INTERCEPTION_ROUTE_MARKERS = [
'(..)(..)',
'(.)',
'(..)',
'(...)',
] as constSources: packages/next/src/shared/lib/router/utils/interception-routes.ts:4-9, packages/next/src/shared/lib/router/utils/interception-routes.ts:168-211
Sources: packages/next/src/shared/lib/router/utils/interception-routes.ts:119-214
Warning
Using the (..) marker at the root level or (..)(..)() at the root or one level up throws an immediate invariant error because insufficient parent segments exist for traversal.
Sources: packages/next/src/shared/lib/router/utils/interception-routes.ts:179-183, packages/next/src/shared/lib/router/utils/interception-routes.ts:198-202
Development request handling and logging
Request Execution and Routing Wrappers
Incoming HTTP requests in the development server flow through handleRequest and run methods on DevServer. The handleRequest implementation initializes a trace span named handle-request, awaits server readiness via this.ready?.promise, attaches PagesErrorDebug metadata to the request, and delegates execution to super.handleRequest. Immediately following execution, a child span records process memory usage metrics including memory.rss, memory.heapUsed, and memory.heapTotal before stopping.
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
}Sources: packages/next/src/server/dev/next-dev-server.ts:523-539
The run method handles basePath prefix stripping from parsedUrl.pathname before invoking parent execution routines, restoring the original pathname afterward so custom routes match correctly. It guards against public directory conflicts by verifying whether /_next exists inside this.publicDir. Any unhandled exceptions thrown during request processing are caught, formatted via formatServerError, passed to this.logErrorWithOriginalStack, and handled by rendering an error response with a 500 status code.
async run(
req: NodeNextRequest,
res: NodeNextResponse,
parsedUrl: UrlWithParsedQuery
): Promise<void> {
await this.ready?.promise
const { basePath } = this.nextConfig
let originalPathname: string | null = null
// TODO: see if we can remove this in the future
if (basePath && pathHasPrefix(parsedUrl.pathname || '/', basePath)) {
// strip basePath before handling dev bundles
// If replace ends up replacing the full url it'll be `undefined`, meaning we have to default it to `/`
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) {
// restore the path before continuing so that custom-routes can accurately determine
// if they should match against the basePath or not
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()
}
}
}
}Sources: packages/next/src/server/dev/next-dev-server.ts:541-590
Warning
If /_next exists as a physical directory inside the public directory, run throws an immediate PUBLIC_DIR_MIDDLEWARE_CONFLICT error to prevent asset routing collisions.
Sources: packages/next/src/server/dev/next-dev-server.ts:561-565
Dev Console Request-Response Logging
Development request logging is controlled by logRequests and ignoreLoggingIncomingRequests, which filter incoming requests against configured regular expression patterns in loggingConfig.incomingRequests.ignore.
export function ignoreLoggingIncomingRequests(
request: NodeNextRequest,
loggingConfig: LoggingConfig | undefined
): boolean {
// If it's boolean use the boolean value
if (typeof loggingConfig?.incomingRequests === 'boolean') {
return !loggingConfig.incomingRequests
}
// Any of the value on the chain is falsy, will not ignore the request.
const ignore = loggingConfig?.incomingRequests?.ignore
// If ignore is not set, don't ignore anything
if (!ignore) {
return false
}
// If array of RegExp, ignore if any pattern matches
return ignore.some((pattern) => pattern.test(request.url))
}Sources: packages/next/src/server/dev/log-requests.ts:22-41
Status codes are mapped to color formatters based on numeric thresholds to output status badges in the console.
| Status Code Range | Color Formatter |
|---|---|
< 200 | white |
200 to 299 | green |
300 to 399 | blue |
400 to 499 | yellow |
500+ | red |
const statusCodeColor =
statusCode < 200
? white
: statusCode < 300
? green
: statusCode < 400
? blue
: statusCode < 500
? yellow
: redSources: packages/next/src/server/dev/log-requests.ts:97-106
Timing breakdowns compute durations for middleware (proxy.ts), framework internals (next.js), parameter generation (generate-params), and user application code (application-code). Server action execution logs are written below the main request log when devServerActionLog metadata is present on the request. Fetch metrics attached to request.fetchMetrics are formatted according to cache status values.
| Cache Status Value | Formatted Output String |
|---|---|
hmr | (HMR cache) (green) |
hit | (cache hit) (green) |
miss | (cache miss) (yellow) |
skip | (cache skip) (yellow) |
function formatCacheStatus(cacheStatus: FetchMetric['cacheStatus']): string {
switch (cacheStatus) {
case 'hmr':
return green('(HMR cache)')
case 'hit':
return green('(cache hit)')
case 'miss':
case 'skip':
return yellow(`(cache ${cacheStatus})`)
default:
return cacheStatus satisfies never
}Sources: packages/next/src/server/dev/log-requests.ts:225-236
Compilation error collection and propagation
Compilation Error Retrieval and Hot Reloading
The DevBundlerService exposes helpers to retrieve compilation errors from the underlying hot reloader and propagate them to the browser over WebSockets. The getCompilationError method queries the hot reloader for errors associated with a specific page and returns the first reported error entry if present.
public async getCompilationError(page: string) {
const errors = await this.bundler.hotReloader.getCompilationErrors(page)
if (!errors) return
// Return the very first error we found.
return errors[0]
}Sources: packages/next/src/server/lib/dev-bundler-service.ts:92-98
WebSocket connections established via onHMR listen for incoming client messages and error events. When a new HMR server error is detected and not yet buffered, sendErrorsToBrowser formats the error using stringifyError and broadcasts a SERVER_ERROR message containing the JSON representation to the client.
try {
const error = getHmrServerError()
// New error occurred: buffered error is flushed and new error occurred
if (!bufferedHmrServerError && error) {
hotReloader.send({
type: HMR_MESSAGE_SENT_TO_BROWSER.SERVER_ERROR,
errorJSON: stringifyError(error),
})
bufferedHmrServerError = null
}
Sources: packages/next/src/server/dev/on-demand-entry-handler.ts:1101-1112
Warning
bufferedHmrServerError is reset to null whenever the WebSocket connection closes, ensuring that stale compilation or HMR errors do not persist across reconnections.
Sources: packages/next/src/server/dev/on-demand-entry-handler.ts:1096-1099
Fallback Error Component Generation
When a fatal compilation failure occurs or an invalid route is requested, the development server constructs fallback error components. The getFallbackErrorComponents method triggers buildFallbackError on the hot reloader and subsequently calls ensurePage for /_error to guarantee that the default error component and its fallback bundle are successfully compiled.
public async getFallbackErrorComponents(url?: string) {
await this.bundler.hotReloader.buildFallbackError()
// Build the error page to ensure the fallback is built too.
// TODO: See if this can be moved into hotReloader or removed.
await this.bundler.hotReloader.ensurePage({
page: '/_error',
clientOnly: false,
definition: undefined,
url,
})
}Sources: packages/next/src/server/lib/dev-bundler-service.ts:80-90
Server-side error logging uses logErrorWithOriginalStack to map transpiled stack traces back to original source code locations before printing output to the developer console or forwarding them through the dev bundler service.
protected logErrorWithOriginalStack(
err?: unknown,
type?: 'unhandledRejection' | 'uncaughtException' | 'warning' | 'app-dir'
): void {
this.bundlerService.logErrorWithOriginalStack(err, type)
}Sources: packages/next/src/server/dev/next-dev-server.ts:592-597
Related
On This Page
OverviewDevServer architecture and class inheritanceDevServer Public Class Interface and InheritanceServer Lifecycle Instantiation and Wrapper CoordinationBundler integration and entry coordinationDevBundlerService ArchitectureSetup Options and Virtual ManifestsLifecycle Hooks and Error FormattingOn-demand compilation and page entry handlingDynamic Entry Resolution and Route Key GenerationLazy Compilation and ensurePageDynamic route matching and path resolutionDev Route Normalization and App Path TransformationDynamic Segment DetectionInterception Route HandlingDevelopment request handling and loggingRequest Execution and Routing WrappersDev Console Request-Response LoggingCompilation error collection and propagationCompilation Error Retrieval and Hot ReloadingFallback Error Component GenerationRelated