Relevant source files
The following files were used as context for generating this wiki page:
- packages/next/src/server/next-server.ts
- packages/next/src/server/base-server.ts
- packages/next/src/server/next.ts
- packages/next/src/server/lib/router-server.ts
- packages/next/src/server/lib/start-server.ts
- packages/next/src/server/dev/hot-reloader-turbopack.ts
- packages/next/src/server/dev/next-dev-server.ts
- packages/next/src/server/base-http/index.ts
- packages/next/src/server/base-http/node.ts
- packages/next/src/server/web/spec-extension/adapters/next-request.ts
- packages/next/src/server/node-environment-baseline.ts
- packages/next/server.js
- packages/next/src/server/app-render/entry-base.ts
- packages/next/src/api/server.ts
- packages/next/src/lib/is-error.ts
- packages/next/src/server/request-meta.ts
Overview
The Next.js server architecture provides the foundational runtime environment, HTTP request processing pipeline, and modular routing infrastructure that drives both development and production servers. It handles programmatic instantiation via wrapper classes, orchestrates process socket binding and listener bootstrapping, and implements abstract base server contracts specialized for Node.js execution. By normalizing low-level Node.js streams into Web standard request and response abstractions, the server architecture unifies handling across disparate runtimes while managing robust error detection, bubbling, and request metadata propagation throughout the rendering lifecycle.
Sources: packages/next/src/server/next.ts:179-190, packages/next/src/server/lib/router-server.ts:131-145, packages/next/src/server/base-server.ts:477-492, packages/next/src/server/next-server.ts:198-202, packages/next/src/server/base-http/node.ts:19-27, packages/next/src/server/request-meta.ts:51-110
Public Server Entrypoints
Wrapper Server and Lifecycle Configuration
The NextServer wrapper class implements NextWrapperServer to serve as the programmatic public interface for custom servers and commands like next start. It manages asynchronous configuration loading via loadConfig, maps runtime phases to PHASE_DEVELOPMENT_SERVER or PHASE_PRODUCTION_SERVER, and initializes either NextNodeServer or NextDevServer depending on options.dev.
export class NextServer implements NextWrapperServer {
private serverPromise?: Promise<NextNodeServer>
private server?: NextNodeServer
private reqHandler?: NodeRequestHandler
private reqHandlerPromise?: Promise<NodeRequestHandler>
private preparedAssetPrefix?: string
public options: NextServerOptions
constructor(options: NextServerOptions) {
this.options = options
}Sources: packages/next/src/server/next.ts:179-190
private async [SYMBOL_LOAD_CONFIG]() {
const dir = path.resolve(
/* turbopackIgnore: true */ this.options.dir || '.'
)
const config = await loadConfig(
this.options.dev ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_SERVER,
dir,
{
customConfig: this.options.conf,
silent: true,
}
)
Sources: packages/next/src/server/next.ts:342-355
Warning
NextServer enforces strict checks on production output configurations during initialization. If conf.output === 'standalone' is set without __NEXT_PRIVATE_STANDALONE_CONFIG, a warning is logged, whereas conf.output === 'export' throws an immediate error instructing the user to use serve instead.
if (!this.options.dev) {
if (conf.output === 'standalone') {
if (!process.env.__NEXT_PRIVATE_STANDALONE_CONFIG) {
log.warn(
`"next start" does not work with "output: standalone" configuration. Use "node .next/standalone/server.js" instead.`
)
}
} else if (conf.output === 'export') {
throw new Error(
`"next start" does not work with "output: export" configuration. Use "npx serve@latest out" instead.`
)
}
}Sources: packages/next/src/server/next.ts:389-401
Deprecated Custom Server Methods
Custom server instances expose several legacy methods that are guarded with runtime warnings via warnDeprecatedCustomServerMethod. The mapping of deprecated methods to alternative guidance is maintained in DEPRECATED_CUSTOM_SERVER_METHOD_GUIDANCE.
| Deprecated Method | Guidance Message |
|---|---|
setAssetPrefix | Please configure assetPrefix in next.config.js instead. |
logError | Please use application logging instead. |
logErrorWithOriginalStack | Please use application logging instead. |
revalidate | Please use documented application revalidation APIs instead. |
render | Please use app.getRequestHandler() with an adjusted parsed URL instead. |
renderToHTML | Please use app.getRequestHandler() with an adjusted parsed URL instead. |
renderError | Please use app.getRequestHandler() with an adjusted parsed URL instead. |
renderErrorToHTML | Please use app.getRequestHandler() with an adjusted parsed URL instead. |
render404 | Please use app.getRequestHandler() with an adjusted parsed URL instead. |
const DEPRECATED_CUSTOM_SERVER_METHOD_GUIDANCE: Record<
DeprecatedCustomServerMethod,
string
> = {
setAssetPrefix: 'Please configure `assetPrefix` in `next.config.js` instead.',
logError: 'Please use application logging instead.',
logErrorWithOriginalStack: 'Please use application logging instead.',
revalidate: 'Please use documented application revalidation APIs instead.',
render:
'Please use `app.getRequestHandler()` with an adjusted parsed URL instead.',
renderToHTML:
'Please use `app.getRequestHandler()` with an adjusted parsed URL instead.',
renderError:
'Please use `app.getRequestHandler()` with an adjusted parsed URL instead.',
renderErrorToHTML:
'Please use `app.getRequestHandler()` with an adjusted parsed URL instead.',
render404:
'Please use `app.getRequestHandler()` with an adjusted parsed URL instead.',
}Sources: packages/next/src/server/next.ts:84-102
Server Initialization and Bootstrapping
Baseline Runtime Environment and Server Startup
Before loading any application modules or establishing network sockets, the server establishes its baseline runtime environment via node-environment-baseline.ts. This initialization step ensures that AsyncLocalStorage from async_hooks is exposed on globalThis for React context propagation if the host environment does not provide it natively. Additionally, it defines a getter property on globalThis for WebSocket, lazily requiring next/dist/compiled/ws when accessed.
if (typeof (globalThis as any).AsyncLocalStorage !== 'function') {
const { AsyncLocalStorage } =
require('async_hooks') as typeof import('async_hooks')
;(globalThis as any).AsyncLocalStorage = AsyncLocalStorage
}
if (typeof (globalThis as any).WebSocket !== 'function') {
Object.defineProperty(globalThis, 'WebSocket', {
configurable: true,
get() {
return (
require('next/dist/compiled/ws') as typeof import('next/dist/compiled/ws')
).WebSocket
},
set(value) {
Object.defineProperty(globalThis, 'WebSocket', {
configurable: true,
writable: true,
value,
})
},
})Sources: packages/next/src/server/node-environment-baseline.ts:5-26
The core bootstrapping function startServer in start-server.ts updates process.title to next-server (v${process.env.__NEXT_VERSION}) and instantiates the underlying HTTP listener. Depending on whether selfSignedCertificate options and production constraints are met, it invokes either https.createServer or http.createServer with a requestListener.
let { port } = serverOptions
process.title = `next-server (v${process.env.__NEXT_VERSION})`
let handlersReady = () => {}
let handlersError = () => {}
let handlersPromise: Promise<void> | undefined = new Promise<void>(
(resolve, reject) => {
handlersReady = resolve
handlersError = reject
}
)
let requestHandler: WorkerRequestHandler = async (
req: IncomingMessage,
res: ServerResponse
): Promise<void> => {
if (handlersPromise) {
await handlersPromise
return requestHandler(req, res)
}
throw new Error('Invariant request handler was not setup')
}
let upgradeHandler: WorkerUpgradeHandler = async (
req,
socket,
head
): Promise<void> => {
if (handlersPromise) {
await handlersPromise
return upgradeHandler(req, socket, head)
}
throw new Error('Invariant upgrade handler was not setup')
}
let nextServer: NextServer | undefined
let devMemoryThresholdRestart = true
// setup server listener as fast as possible
if (selfSignedCertificate && !isDev) {
throw new Error(
'Using a self signed certificate is only supported with `next dev`.'
)
}
async function requestListener(req: IncomingMessage, res: ServerResponse) {
try {
if (handlersPromise) {
await handlersPromise
handlersPromise = undefined
}
await requestHandler(req, res)
} catch (err) {
res.statusCode = 500
res.end('Internal Server Error')
Log.error(`Failed to handle request for ${req.url}`)
console.error(err)
} finally {
const memoryRestartStats = getMemoryRestartStats(
isDev,
devMemoryThresholdRestart,
v8.getHeapStatistics
)
if (memoryRestartStats) {
Log.warn(
`Server is approaching the used memory threshold, restarting...`
)
trace('server-restart-close-to-memory-threshold', undefined, {
'memory.heapSizeLimit': String(memoryRestartStats.heap_size_limit),
'memory.heapUsed': String(memoryRestartStats.used_heap_size),
}).stop()
flushAllTraces()
process.exit(RESTART_EXIT_CODE)
}
}
}
const server = selfSignedCertificate
? https.createServer(
{
key: fs.readFileSync(selfSignedCertificate.key),Sources: packages/next/src/server/lib/start-server.ts:204-288
Caution
Using a self-signed certificate via selfSignedCertificate is strictly restricted to development mode (isDev). Attempting to use a self-signed certificate when isDev is false causes startServer to throw an immediate error during listener setup.
if (selfSignedCertificate && !isDev) {
throw new Error(
'Using a self signed certificate is only supported with `next dev`.'
)
}Sources: packages/next/src/server/lib/start-server.ts:242-246
Socket Binding, Port Allocation, and Termination Signals
Once the server instance is configured with optional keepAliveTimeout and upgrade event handlers, it binds to the specified port and hostname. If allowRetry is enabled and an EADDRINUSE error occurs on error, the development server automatically increments the port by one and retries up to 10 times before logging a fatal startup failure and exiting the process with code 1.
if (keepAliveTimeout) {
server.keepAliveTimeout = keepAliveTimeout
}
server.on('upgrade', async (req, socket, head) => {
try {
await upgradeHandler(req, socket, head)
} catch (err) {
socket.destroy()
Log.error(`Failed to handle request for ${req.url}`)
console.error(err)
}
})
let portRetryCount = 0
const originalPort = port
server.on('error', (err: NodeJS.ErrnoException) => {
if (
allowRetry &&
port &&
isDev &&
err.code === 'EADDRINUSE' &&
portRetryCount < 10
) {
port += 1
portRetryCount += 1
server.listen(port, hostname)
} else {
Log.error(`Failed to start server`)
console.error(err)
process.exit(1)
}
})Sources: packages/next/src/server/lib/start-server.ts:290-322
Upon successfully emitting the listening event, the server registers signal handlers for graceful process termination unless NEXT_MANUAL_SIG_HANDLE is set in the environment.
| Signal | Exit Code Behavior |
|---|---|
SIGINT | Exits process with code 130 (signal termination) |
SIGTERM | Exits process with code 143 (signal termination) |
| Default / Unhandled | Exits process with code 128 |
switch (signal) {
case 'SIGINT':
process.exit(130)
break
case 'SIGTERM':
process.exit(143)
break
default:
// Make sure all handled signals have explicit exit codes.
// This is just a fallback to guard against unsound types.
signal satisfies never
process.exit(128)
}
})()
}
// Make sure commands gracefully respect termination signals (e.g. from Docker)
// Allow the graceful termination to be manually configurable
if (!process.env.NEXT_MANUAL_SIG_HANDLE) {
process.on('SIGINT', cleanup)
process.on('SIGTERM', cleanup)
}Sources: packages/next/src/server/lib/start-server.ts:471-492
BaseServer Architecture and Core Contracts
Abstract Server Responsibilities and Constructor Initialization
BaseServer serves as the abstract coordinator for Next.js HTTP request handling, environment configuration, and asset normalization across different execution runtimes. The constructor initializes configuration values, sets up path resolvers, and instantiates request normalizers based on whether app directories, pages directories, or internationalization settings are active.
public constructor(options: ServerOptions) {
const {
dir = '.',
quiet = false,
conf,
dev = false,
minimalMode = false,
hostname,
port,
experimentalTestProxy,
} = options
this.dev = dev
this.experimentalTestProxy = experimentalTestProxy
this.serverOptions = options
this.dir = path.resolve(/* turbopackIgnore: true */ dir)
this.quiet = quiet
this.loadEnvConfig({ dev, forceReload: false })
// TODO: should conf be normalized to prevent missing
// values from causing issues as this can be user provided
this.nextConfig = conf as NextConfigRuntime
if (
(dev || process.env.__NEXT_DEV_SERVER) &&
this.nextConfig.experimental.requestInsights
) {
process.env.__NEXT_REQUEST_INSIGHTS = 'true'
}
if (this.nextConfig.experimental.runtimeServerDeploymentId) {
if (!process.env.NEXT_DEPLOYMENT_ID) {
throw new Error(
'process.env.NEXT_DEPLOYMENT_ID is missing but runtimeServerDeploymentId is enabled'
)
}
this.deploymentId = process.env.NEXT_DEPLOYMENT_ID
} else {
let id = this.nextConfig.experimental.useSkewCookie
? ''
: this.nextConfig.deploymentId || ''
this.deploymentId = id
process.env.NEXT_DEPLOYMENT_ID = id
}
;(globalThis as any).NEXT_CLIENT_ASSET_SUFFIX =
this.nextConfig.supportsImmutableAssets || !this.deploymentId
? ''
: `?dpl=${this.deploymentId}`
this.hostname = hostname
if (this.hostname) {
// we format the hostname so that it can be fetched
this.fetchHostname = formatHostname(this.hostname)
}
this.port = port
this.distDir = path.join(
/* turbopackIgnore: true */ this.dir,
this.nextConfig.distDir
)
this.pagesNormalizers = new PagesNormalizers(this.distDir)
this.appNormalizers = new AppNormalizers(this.distDir)
this.publicDir = this.getPublicDir()
this.hasStaticDir = !minimalMode && this.getHasStaticDir()
this.i18nProvider = this.nextConfig.i18n?.locales
? new I18NProvider(this.nextConfig.i18n)
: undefined
// Configure the locale normalizer, it's used for routes inside `pages/`.
this.localeNormalizer = this.i18nProvider
? new LocaleRouteNormalizer(this.i18nProvider)
: undefined
const { assetPrefix, generateEtags } = this.nextConfig
this.buildId = this.getBuildId()
// this is a hack to avoid Webpack knowing this is equal to this.minimalMode
// because we replace this.minimalMode to true in production bundles.Sources: packages/next/src/server/base-server.ts:477-580
During initialization, the server resolves the root application directory (dir), configures deployment identifiers via process.env.NEXT_DEPLOYMENT_ID or runtimeServerDeploymentId, and sets up locale normalizers if internationalization is enabled. It configures path normalizers for Pages (PagesNormalizers) and App (AppNormalizers) routes within the distribution directory (distDir).
this.dir = path.resolve(/* turbopackIgnore: true */ dir)
this.quiet = quiet
this.loadEnvConfig({ dev, forceReload: false })
// TODO: should conf be normalized to prevent missing
// values from causing issues as this can be user provided
this.nextConfig = conf as NextConfigRuntime
if (
(dev || process.env.__NEXT_DEV_SERVER) &&
this.nextConfig.experimental.requestInsights
) {
process.env.__NEXT_REQUEST_INSIGHTS = 'true'
}
if (this.nextConfig.experimental.runtimeServerDeploymentId) {
if (!process.env.NEXT_DEPLOYMENT_ID) {
throw new Error(
'process.env.NEXT_DEPLOYMENT_ID is missing but runtimeServerDeploymentId is enabled'
)
}
this.deploymentId = process.env.NEXT_DEPLOYMENT_ID
} else {
let id = this.nextConfig.experimental.useSkewCookie
? ''
: this.nextConfig.deploymentId || ''
this.deploymentId = id
process.env.NEXT_DEPLOYMENT_ID = id
}
;(globalThis as any).NEXT_CLIENT_ASSET_SUFFIX =
this.nextConfig.supportsImmutableAssets || !this.deploymentId
? ''
: `?dpl=${this.deploymentId}`
this.hostname = hostname
if (this.hostname) {
// we format the hostname so that it can be fetched
this.fetchHostname = formatHostname(this.hostname)
}
this.port = port
this.distDir = path.join(
/* turbopackIgnore: true */ this.dir,
this.nextConfig.distDir
)
this.pagesNormalizers = new PagesNormalizers(this.distDir)
this.appNormalizers = new AppNormalizers(this.distDir)
this.publicDir = this.getPublicDir()
this.hasStaticDir = !minimalMode && this.getHasStaticDir()Sources: packages/next/src/server/base-server.ts:493-541
Caution
If runtimeServerDeploymentId is enabled in nextConfig.experimental, process.env.NEXT_DEPLOYMENT_ID must be defined beforehand. If the environment variable is missing during server startup, BaseServer throws an immediate error.
if (this.nextConfig.experimental.runtimeServerDeploymentId) {
if (!process.env.NEXT_DEPLOYMENT_ID) {
throw new Error(
'process.env.NEXT_DEPLOYMENT_ID is missing but runtimeServerDeploymentId is enabled'
)
}
this.deploymentId = process.env.NEXT_DEPLOYMENT_IDSources: packages/next/src/server/base-server.ts:508-514
Normalizers and Render Options Configuration
The constructor constructs a core set of request path normalizers and builds the internal renderOpts object passed down to rendering pipelines. The normalizers object routes incoming requests through specialized prefix checkers depending on whether the server operates in minimal mode or handles server components.
| Normalizer Property | Class Type / Condition |
|---|---|
rsc | RSCPathnameNormalizer (Active if enabledDirectories.app and minimalMode are true) |
segmentPrefetchRSC | SegmentPrefixRSCPathnameNormalizer (Active if minimalMode is true) |
data | NextDataPathnameNormalizer (Active if enabledDirectories.pages is true, initialized with buildId) |
this.normalizers = {
// We should normalize the pathname from the RSC prefix only in minimal
// mode as otherwise that route is not exposed external to the server as
// we instead only rely on the headers.
rsc:
this.enabledDirectories.app && this.minimalMode
? new RSCPathnameNormalizer()
: undefined,
segmentPrefetchRSC: this.minimalMode
? new SegmentPrefixRSCPathnameNormalizer()
: undefined,
data: this.enabledDirectories.pages
? new NextDataPathnameNormalizer(this.buildId)
: undefined,
}Sources: packages/next/src/server/base-server.ts:566-580
The server also compiles renderOpts using configuration options extracted from this.nextConfig, including base paths, asset prefixes, experimental feature flags like taint support, script workers, optimize CSS, and internationalized domain settings.
this.renderOpts = {
dir: this.dir,
supportsDynamicResponse: true,
trailingSlash: this.nextConfig.trailingSlash,
poweredByHeader: this.nextConfig.poweredByHeader,
generateEtags,
previewProps: this.getPreviewProps(),
basePath: this.nextConfig.basePath,
images: this.nextConfig.images,
optimizeCss: this.nextConfig.experimental.optimizeCss,
nextConfigOutput: this.nextConfig.output,
nextScriptWorkers: this.nextConfig.experimental.nextScriptWorkers,
disableOptimizedLoading:
this.nextConfig.experimental.disableOptimizedLoading,
domainLocales: this.nextConfig.i18n?.domains,
distDir: this.distDir,
serverComponents: this.enabledDirectories.app,
cacheLifeProfiles: this.nextConfig.cacheLife,
staticPageGenerationTimeout: this.nextConfig.staticPageGenerationTimeout,
enableTainting: this.nextConfig.experimental.taint,
crossOrigin: this.nextConfig.crossOrigin
? this.nextConfig.crossOrigin
: undefined,
largePageDataBytes: this.nextConfig.experimental.largePageDataBytes,
isExperimentalCompile: options.compileMode
? options.compileMode.isExperimentalCompile
: this.nextConfig.experimental.isExperimentalCompile,Sources: packages/next/src/server/base-server.ts:584-611
NextNodeServer Specialization and Execution
Node.js Platform Server Implementation and Request Normalization
NextNodeServer extends BaseServer to implement the Node.js production server runtime, inheriting platform-specific request routing, file system caching bindings, and custom server handlers. The server initializes request adapters that bridge incoming Node.js IncomingMessage and ServerResponse streams into NodeNextRequest and NodeNextResponse wrappers.
protected normalizeReq(
req: NodeNextRequest | IncomingMessage
): NodeNextRequest {
return !(req instanceof NodeNextRequest) ? new NodeNextRequest(req) : req
}
protected normalizeRes(
res: NodeNextResponse | ServerResponse
): NodeNextResponse {
return !(res instanceof NodeNextResponse) ? new NodeNextResponse(res) : res
}Sources: packages/next/src/server/next-server.ts:1339-1349
The request handler pipeline ensures that prepare() is fired eagerly upon request receipt while catching unhandled rejections prior to execution. When getRequestHandler() or getRequestHandlerWithMetadata() is invoked, incoming parameters and metadata are bound to request attributes via setRequestMeta before routing through the parent handler.
public getRequestHandler(): NodeRequestHandler {
const handler = this.makeRequestHandler()
if (this.serverOptions.experimentalTestProxy) {
const { wrapRequestHandlerNode } =
// eslint-disable-next-line @next/internal/typechecked-require -- experimental/testmode is not built ins next/dist/esm
require('next/dist/experimental/testmode/server') as typeof import('../experimental/testmode/server')
return wrapRequestHandlerNode(handler)
}
return handler
}
/**
* @internal - this method is internal to Next.js and should not be used directly by end-users
*/
public getRequestHandlerWithMetadata(meta: RequestMeta): NodeRequestHandler {
const handler = this.makeRequestHandler()
return (req, res, parsedUrl) => {
setRequestMeta(req, meta)
return handler(req, res, parsedUrl)
}
}Sources: packages/next/src/server/next-server.ts:1351-1371
Warning
Calling ensurePage directly on NextNodeServer outside of development environments throws an immediate invariant error, as on-demand compilation is restricted to development server instances.
throw new Error(
'Invariant: ensurePage can only be called on the development server'
)
}Sources: packages/next/src/server/next-server.ts:1315-1318
Caching Bridges and Incremental Cache Configuration
NextNodeServer manages caching bridges by integrating custom cache handlers and delegating file system operations to nodeFs. Custom cache handlers specified in the user configuration are dynamically imported via dynamicImportEsmDefault, interoped, and registered using setCacheHandler.
const { cacheMaxMemorySize, cacheHandlers } = this.nextConfig
if (!cacheHandlers) return
initializeCacheHandlers(cacheMaxMemorySize)
await registerCustomCacheHandlers(async () => {
for (const [kind, handler] of Object.entries(cacheHandlers)) {
if (!handler) continue
setCacheHandler(
kind,
interopDefault(
await dynamicImportEsmDefault(
formatDynamicImportPath(this.distDir, handler)
)
)
)
}
})
}Sources: packages/next/src/server/next-server.ts:431-449
The incremental cache setup method inspects this.nextConfig for a cacheHandler property, loads any custom handlers asynchronously, and returns the configured cache instance for downstream rendering workers.
protected async getIncrementalCache({
requestHeaders,
}: {
requestHeaders: IncrementalCache['requestHeaders']
}) {
const dev = !!this.dev
let CacheHandler: any
const { cacheHandler } = this.nextConfig
if (cacheHandler) {
CacheHandler = interopDefault(
await dynamicImportEsmDefault(
formatDynamicImportPath(this.distDir, cacheHandler)
)
)
}
await this.loadCustomCacheHandlers()Sources: packages/next/src/server/next-server.ts:451-468
| Method / Utility | Target Platform / Module | Purpose |
|---|---|---|
getCacheFilesystem() | CacheFs (nodeFs) | Supplies the Node.js file system interface for cache persistence |
normalizeReq() | NodeNextRequest | Wraps raw Node.js IncomingMessage objects into Next.js base request streams |
normalizeRes() | NodeNextResponse | Wraps raw Node.js ServerResponse objects into Next.js base response streams |
handleApiRequest() | PagesAPIRouteMatch | Dispatches legacy Pages API requests via runApi execution |
protected async handleApiRequest(
req: NodeNextRequest,
res: NodeNextResponse,
query: ParsedUrlQuery,
match: PagesAPIRouteMatch
): Promise<boolean> {
return this.runApi(req, res, query, match)
}
protected getCacheFilesystem(): CacheFs {
return nodeFs
}Sources: packages/next/src/server/next-server.ts:1326-1337
HTTP Abstractions and Request Adaptation
BaseNextRequest and BaseNextResponse Abstractions
Next.js decouples core request handling from platform-specific APIs by defining abstract base classes: BaseNextRequest and BaseNextResponse. The BaseNextRequest abstract class manages request properties including method, URL, body, and headers, while providing a lazy cookie parser utilizing getCookieParser configured with request headers.
export abstract class BaseNextRequest<Body = any> {
protected _cookies: NextApiRequestCookies | undefined
public abstract headers: IncomingHttpHeaders
public abstract fetchMetrics: FetchMetric[] | undefined
constructor(
public method: string,
public url: string,
public body: Body
) {}
// Utils implemented using the abstract methods above
public get cookies() {
if (this._cookies) return this._cookies
return (this._cookies = getCookieParser(this.headers)())
}
}Sources: packages/next/src/server/base-http/index.ts:28-45
BaseNextResponse defines abstract interfaces for status codes, headers, body setting, and response dispatching. It includes helper methods like redirect, which automatically sets the Location header, status code, and a fallback Refresh header if the status code is a permanent redirect.
export abstract class BaseNextResponse<Destination = any> {
abstract statusCode: number | undefined
abstract statusMessage: string | undefined
abstract get sent(): boolean
constructor(public destination: Destination) {}
/**
* Sets a value for the header overwriting existing values
*/
abstract setHeader(name: string, value: string | string[]): this
/**
* Removes a header
*/
abstract removeHeader(name: string): this
/**
* Appends value for the given header name
*/
abstract appendHeader(name: string, value: string): this
/**
* Get all values for a header as an array or undefined if no value is present
*/
abstract getHeaderValues(name: string): string[] | undefined
abstract hasHeader(name: string): boolean
/**
* Get values for a header concatenated using `,` or undefined if no value is present
*/
abstract getHeader(name: string): string | undefined
abstract getHeaders(): OutgoingHttpHeaders
abstract body(value: string): this
abstract send(): void
abstract onClose(callback: () => void): void
// Utils implemented using the abstract methods above
public redirect(destination: string, statusCode: number) {
this.setHeader('Location', destination)
this.statusCode = statusCode
// Since IE11 doesn't support the 308 header add backwards
// compatibility using refresh header
if (statusCode === RedirectStatusCode.PermanentRedirect) {
this.setHeader('Refresh', `0;url=${destination}`)
}
return this
}Sources: packages/next/src/server/base-http/index.ts:47-102
Node.js Platform Stream Implementation
The NodeNextRequest and NodeNextResponse classes extend the base abstractions to wrap Node.js IncomingMessage and ServerResponse objects. NodeNextRequest exposes a stream() method that converts the underlying Node.js readable stream into a Web ReadableStream, ensuring the stream can only be read once.
export class NodeNextRequest extends BaseNextRequest<Readable> {
public headers = this._req.headers
public fetchMetrics: FetchMetric[] | undefined = this._req?.fetchMetrics;
[NEXT_REQUEST_META]: RequestMeta = this._req[NEXT_REQUEST_META] || {}
constructor(private _req: Req) {
super(_req.method!.toUpperCase(), _req.url!, _req)
}
get originalRequest() {
// Need to mimic these changes to the original req object for places where we use it:
// render.tsx, api/ssg requests
this._req[NEXT_REQUEST_META] = this[NEXT_REQUEST_META]
this._req.url = this.url
this._req.cookies = this.cookies
return this._req
}
set originalRequest(value: Req) {
this._req = value
}
private streaming = false
/**
* Returns the request body as a Web Readable Stream. The body here can only
* be read once as the body will start flowing as soon as the data handler
* is attached.
*
* @internal
*/
public stream() {
if (this.streaming) {
throw new Error(
'Invariant: NodeNextRequest.stream() can only be called once'
)
}
this.streaming = true
return new ReadableStream({
start: (controller) => {
this._req.on('data', (chunk) => {
controller.enqueue(new Uint8Array(chunk))
})
this._req.on('end', () => {
controller.close()
})
this._req.on('error', (err) => {
controller.error(err)
})
},
})
}
}Sources: packages/next/src/server/base-http/node.ts:19-73
Warning
Calling NodeNextRequest.stream() more than once triggers an invariant error stating that the stream can only be called once, preventing multiple consumers from reading a consumed request body.
public stream() {
if (this.streaming) {
throw new Error(
'Invariant: NodeNextRequest.stream() can only be called once'
)
}
this.streaming = trueSources: packages/next/src/server/base-http/node.ts:51-57
NodeNextResponse delegates header manipulation, status code setting, and response finalization directly to the underlying Node.js ServerResponse instance.
export class NodeNextResponse extends BaseNextResponse<Writable> {
private textBody: string | undefined = undefined
public [SYMBOL_CLEARED_COOKIES]?: boolean
get originalResponse() {
if (SYMBOL_CLEARED_COOKIES in this) {
this._res[SYMBOL_CLEARED_COOKIES] = this[SYMBOL_CLEARED_COOKIES]
}
return this._res
}
constructor(
private _res: ServerResponse & { [SYMBOL_CLEARED_COOKIES]?: boolean }
) {
super(_res)
}
get sent() {
return this._res.finished || this._res.headersSent
}
get statusCode() {
return this._res.statusCode
}
set statusCode(value: number) {
this._res.statusCode = value
}
get statusMessage() {
return this._res.statusMessage
}
set statusMessage(value: string) {
this._res.statusMessage = value
}
setHeader(name: string, value: string | string[]): this {
this._res.setHeader(name, value)
return this
}
removeHeader(name: string): this {
this._res.removeHeader(name)
return this
}
getHeaderValues(name: string): string[] | undefined {
const values = this._res.getHeader(name)
if (values === undefined) return undefined
return (Array.isArray(values) ? values : [values]).map((value) =>
value.toString()
)
}
hasHeader(name: string): boolean {
return this._res.hasHeader(name)
}
getHeader(name: string): string | undefined {
const values = this.getHeaderValues(name)
return Array.isArray(values) ? values.join(',') : undefined
}
getHeaders(): OutgoingHttpHeaders {
return this._res.getHeaders()
}
appendHeader(name: string, value: string): this {
const currentValues = this.getHeaderValues(name) ?? []
if (!currentValues.includes(value)) {
this._res.setHeader(name, [...currentValues, value])
}
return thisSources: packages/next/src/server/base-http/node.ts:75-169
Bridging to Web NextRequest via NextRequestAdapter
NextRequestAdapter transforms BaseNextRequest instances into standard NextRequest objects depending on the active execution runtime environment. When running outside the edge runtime, fromNodeNodeNextRequest resolves relative URLs against request metadata and configures the request with a half-duplex body and an abort signal tied to the underlying response lifecycle.
export class NextRequestAdapter {
public static fromBaseNextRequest(
request: BaseNextRequest,
signal: AbortSignal
): NextRequest {
if (
// The type check here ensures that `req` is correctly typed, and the
// environment variable check provides dead code elimination.
process.env.NEXT_RUNTIME === 'edge' &&
isWebNextRequest(request)
) {
return NextRequestAdapter.fromWebNextRequest(request)
} else if (
// The type check here ensures that `req` is correctly typed, and the
// environment variable check provides dead code elimination.
process.env.NEXT_RUNTIME !== 'edge' &&
isNodeNextRequest(request)
) {
return NextRequestAdapter.fromNodeNextRequest(request, signal)
} else {
throw new Error('Invariant: Unsupported NextRequest type')
}
}
public static fromNodeNextRequest(
request: NodeNextRequest,
signal: AbortSignal
): NextRequest {
// HEAD and GET requests can not have a body.
let body: BodyInit | null = null
if (request.method !== 'GET' && request.method !== 'HEAD' && request.body) {
// @ts-expect-error - this is handled by undici, when streams/web land use it instead
body = request.body
}
let url: URL
if (request.url.startsWith('http')) {
url = new URL(request.url)
} else {
// Grab the full URL from the request metadata.
const base = getRequestMeta(request, 'initURL')
if (!base || !base.startsWith('http')) {
// Because the URL construction relies on the fact that the URL provided
// is absolute, we need to provide a base URL. We can't use the request
// URL because it's relative, so we use a dummy URL instead.
url = new URL(request.url, 'http://n')
} else {
url = new URL(request.url, base)
}
}
return new NextRequest(url, {
method: request.method,
headers: fromNodeOutgoingHttpHeaders(request.headers),
duplex: 'half',
signal,
// geo
// ip
// nextConfig
// body can not be passed if request was aborted
// or we get a Request body was disturbed error
...(signal.aborted
? {}
: {
body,
}),
})
}Sources: packages/next/src/server/web/spec-extension/adapters/next-request.ts:56-124
Important
If a request signal has already been aborted, NextRequestAdapter omits the request body entirely from the constructed NextRequest to prevent disturbing or consuming an aborted body stream.
// body can not be passed if request was aborted
// or we get a Request body was disturbed error
...(signal.aborted
? {}
: {
body,
}),
})Sources: packages/next/src/server/web/spec-extension/adapters/next-request.ts:116-123
| Adapter Method | Target Input Type | Output Type | Purpose |
|---|---|---|---|
fromBaseNextRequest() | BaseNextRequest | NextRequest | Runtime-dispatched entrypoint routing to node or web adapters |
fromNodeNextRequest() | NodeNextRequest | NextRequest | Converts Node.js incoming messages and metadata into standard NextRequest |
fromWebNextRequest() | WebNextRequest | NextRequest | Adapts web-native request wrappers for standard spec consumption |
export class NextRequestAdapter {
public static fromBaseNextRequest(
request: BaseNextRequest,
signal: AbortSignal
): NextRequest {
if (
// The type check here ensures that `req` is correctly typed, and the
// environment variable check provides dead code elimination.
process.env.NEXT_RUNTIME === 'edge' &&
isWebNextRequest(request)
) {
return NextRequestAdapter.fromWebNextRequest(request)
} else if (
// The type check here ensures that `req` is correctly typed, and the
// environment variable check provides dead code elimination.
process.env.NEXT_RUNTIME !== 'edge' &&
isNodeNextRequest(request)
) {
return NextRequestAdapter.fromNodeNextRequest(request, signal)
} else {
throw new Error('Invariant: Unsupported NextRequest type')
}
}Sources: packages/next/src/server/web/spec-extension/adapters/next-request.ts:56-78
Server Error Handling and Normalization
Error Types and Safe Stringification
Next.js defines the NextError interface extending standard errors to include optional properties like type, page, code, cancelled, and digest. The isError utility validates whether an unknown thrown value is an object containing name and message properties.
export interface NextError extends Error {
type?: string
page?: string
code?: string | number
cancelled?: boolean
digest?: string
}Sources: packages/next/src/lib/is-error.ts:4-10
export default function isError(err: unknown): err is NextError {
return (
typeof err === 'object' && err !== null && 'name' in err && 'message' in err
)
}Sources: packages/next/src/lib/is-error.ts:38-42
The safeStringifyLite helper serializes objects without requiring external production dependencies by tracking circular references with a WeakSet and replacing duplicates with "[Circular]".
function safeStringifyLite(obj: any) {
const seen = new WeakSet()
return JSON.stringify(obj, (_key, value) => {
// If value is an object and already seen, replace with "[Circular]"
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular]'
}
seen.add(value)
}
return value
})
}Sources: packages/next/src/lib/is-error.ts:19-32
Error Normalization and Development Diagnostics
The getProperError function normalizes thrown values into standard Error instances. In development mode, throwing undefined or null triggers explicit errors referencing documentation URLs for diagnostics.
export function getProperError(err: unknown): Error {
if (isError(err)) {
return err
}
if (process.env.NODE_ENV === 'development') {
// provide better error for case where `throw undefined`
// is called in development
if (typeof err === 'undefined') {
return new Error(
'An undefined error was thrown, ' +
'see here for more info: https://nextjs.org/docs/messages/threw-undefined'
)
}
if (err === null) {
return new Error(
'A null error was thrown, ' +
'see here for more info: https://nextjs.org/docs/messages/threw-undefined'
)
}
}Sources: packages/next/src/lib/is-error.ts:44-65
Warning
Plain objects passed as thrown values are converted to JSON strings using safeStringifyLite, while primitive values are coerced into strings before being wrapped in an Error.
return new Error(isPlainObject(err) ? safeStringifyLite(err) : err + '')Sources: packages/next/src/lib/is-error.ts:67-67
| Helper Function | Input Type | Output Type | Purpose |
|---|---|---|---|
isError() | unknown | err is NextError | Type guard checking for object structure with name and message |
getProperError() | unknown | Error | Normalizes non-error throwables, handling undefined, null, and plain objects |
safeStringifyLite() | any | string | Safe JSON serializer guarding against circular references using a WeakSet |
function safeStringifyLite(obj: any) {
const seen = new WeakSet()
return JSON.stringify(obj, (_key, value) => {
// If value is an object and already seen, replace with "[Circular]"
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular]'
}
seen.add(value)
}
return value
})
}
/**
* Checks whether the given value is a NextError.
* This can be used to print a more detailed error message with properties like `code` & `digest`.
*/
export default function isError(err: unknown): err is NextError {
return (
typeof err === 'object' && err !== null && 'name' in err && 'message' in err
)
}Sources: packages/next/src/lib/is-error.ts:19-42
Related
On This Page
OverviewPublic Server EntrypointsWrapper Server and Lifecycle ConfigurationDeprecated Custom Server MethodsServer Initialization and BootstrappingBaseline Runtime Environment and Server StartupSocket Binding, Port Allocation, and Termination SignalsBaseServer Architecture and Core ContractsAbstract Server Responsibilities and Constructor InitializationNormalizers and Render Options ConfigurationNextNodeServer Specialization and ExecutionNode.js Platform Server Implementation and Request NormalizationCaching Bridges and Incremental Cache ConfigurationHTTP Abstractions and Request AdaptationBaseNextRequest and BaseNextResponse AbstractionsNode.js Platform Stream ImplementationBridging to Web NextRequest via NextRequestAdapterServer Error Handling and NormalizationError Types and Safe StringificationError Normalization and Development DiagnosticsRelated