Relevant source files
The following files were used as context for generating this wiki page:
- packages/next/src/build/index.ts
- packages/next/src/export/index.ts
- packages/next/src/build/adapter/build-complete.ts
- packages/next/src/export/worker.ts
- packages/next/src/build/static-paths/app.ts
- packages/next/src/build/utils.ts
- packages/next/src/server/base-server.ts
- packages/next/src/build/templates/app-page-runtime.ts
- packages/next/src/server/render.tsx
- packages/next/src/build/entries.ts
- packages/next/src/export/routes/pages.ts
- packages/next/src/build/templates/pages.ts
- packages/next/src/build/static-paths/pages.ts
- packages/next/src/build/static-paths/app/extract-pathname-route-param-segments-from-loader-tree.ts
- packages/next/src/build/static-paths/utils.ts
- packages/next/src/export/helpers/create-incremental-cache.ts
- packages/next/src/lib/format-dynamic-import-path.ts
- packages/next/src/server/app-render/async-local-storage.ts
- packages/next/src/server/async-storage/work-store.ts
- packages/next/src/shared/lib/router/utils/parse-loader-tree.ts
Overview
Static Generation in Next.js pre-renders pages at build time across both the App Router and Pages Router, producing static HTML, React Server Component payloads, and cache artifacts that eliminate per-request server computation for eligible routes.
Sources: packages/next/src/build/index.ts:3210-3212, packages/next/src/export/worker.ts:320-344, packages/next/src/export/routes/pages.ts:30-49
Static Route Assessment and Staticity Classification
Route Module Inspection and Component Loading
The build pipeline evaluates page staticity within a tracing span named is-page-static-utils. For routes running on edge runtimes, the build obtains component exports by invoking getRuntimeContext with entry paths and WebAssembly bindings resolved from the distribution directory. Non-edge routes are loaded via loadComponents, which retrieves userland components, route modules, and data-fetching hooks such as getServerSideProps, getStaticPaths, and getStaticProps.
const isPageStaticSpan = trace('is-page-static-utils', parentId)
return isPageStaticSpan
.traceAsyncFn(async (): Promise<PageIsStaticResult> => {
setHttpClientAndAgentOptions({
httpAgentOptions,
})
let componentsResult: LoadComponentsReturnType
let prerenderedRoutes: PrerenderedRoute[] | undefined
let prerenderRouteMatchers: PrerenderRouteMatcher[] | undefined
let paramMatching: ParamMatching | undefined
let prerenderFallbackMode: FallbackMode | undefined
let appConfig: AppSegmentConfig = {}
let rootParamKeys: readonly string[] | undefined
const pathIsEdgeRuntime = isEdgeRuntime(pageRuntime)
if (pathIsEdgeRuntime) {
const runtime = await getRuntimeContext({
paths: edgeInfo.files.map((file: string) => path.join(distDir, file)),
edgeFunctionEntry: {
...edgeInfo,
wasm: (edgeInfo.wasm ?? []).map((binding: AssetBinding) => ({
...binding,
filePath: path.join(distDir, binding.filePath),
})),
},
name: edgeInfo.name,
useCache: true,
distDir,
clientAssetToken,
})
const mod = (
await runtime.context._ENTRIES[`middleware_${edgeInfo.name}`]
).ComponentMod
// This is not needed during require.
const buildManifest = {} as BuildManifest
componentsResult = {
Component: mod.default,
Document: mod.Document,
App: mod.App,
routeModule: mod.routeModule,
page,
ComponentMod: mod,
pageConfig: mod.config || {},
buildManifest,
reactLoadableManifest: {},
getServerSideProps: mod.getServerSideProps,
getStaticPaths: mod.getStaticPaths,
getStaticProps: mod.getStaticProps,
}
} else {
componentsResult = await loadComponents({
distDir,
page: originalAppPath || page,
isAppPath: pageType === 'app',
isDev: false,
sriEnabled,
needsManifestsForLegacyReasons: true,
})
}
const { Component, routeModule } = componentsResultSources: packages/next/src/build/utils.ts:909-972
For App Router targets, the build inspects route modules by collecting layout and page segments through collectSegments. This step processes the route module with options like cacheComponents and partialPrefetching, capturing segment configurations and structural requirements.
if (pageType === 'app') {
// @ts-expect-error pageType is app, so we can assume AppPageModule | AppRouteModule
const ComponentMod: AppPageModule | AppRouteModule =
componentsResult.ComponentMod
let collectedSegments: Awaited<ReturnType<typeof collectSegments>>
try {
collectedSegments = await collectSegments(
// We know this is an app page or app route module because we
// checked above that the page type is 'app'.
routeModule as AppPageRouteModule | AppRouteRouteModule,
{ cacheComponents, partialPrefetching }
)
} catch (err) {
throw new Error(`Failed to collect configuration for ${page}`, {
cause: err,
})
}Sources: packages/next/src/build/utils.ts:978-995
Note
Edge runtime route modules are evaluated inside a sandboxed VM context where entry bindings (_ENTRIES) provide direct access to the userland module exports.
Sources: packages/next/src/build/utils.ts:925-943
Pages Router Method Export Hoisting
In the Pages Router, compiled templates hoist userland methods and check for data-fetching exports such as getStaticProps, getStaticPaths, and getServerSideProps. These exports are bound directly to the generated routeModule instance along with app.default and document.default from the document templates.
// Re-export methods.
export const getStaticProps = hoist(userland, 'getStaticProps')
export const getStaticPaths = hoist(userland, 'getStaticPaths')
export const getServerSideProps = hoist(userland, 'getServerSideProps')
export const config = hoist(userland, 'config')
export const reportWebVitals = hoist(userland, 'reportWebVitals')
// Re-export legacy methods.
export const unstable_getStaticProps = hoist(
userland,
'unstable_getStaticProps'
)
export const unstable_getStaticPaths = hoist(
userland,
'unstable_getStaticPaths'
)
export const unstable_getStaticParams = hoist(
userland,
'unstable_getStaticParams'
)
export const unstable_getServerProps = hoist(
userland,
'unstable_getServerProps'
)
export const unstable_getServerSideProps = hoist(
userland,
'unstable_getServerSideProps'
)Sources: packages/next/src/build/templates/pages.ts:17-44
The generated handler receives these hoisted methods to process incoming requests and determine execution paths based on the presence of server-side data requirements.
export const handler = getHandler({
srcPage: 'VAR_DEFINITION_PAGE',
config,
userland,
routeModule,
getStaticPaths,
getStaticProps,
getServerSideProps,
})Sources: packages/next/src/build/templates/pages.ts:66-74
App Router Static Parameter Extraction
Loader Tree Traversal and Segment Extraction
The App Router static parameter extraction pipeline processes route hierarchies by inspecting loader trees and matching them against normalized routes. For AppPageRouteModule, extractPathnameRouteParamSegments invokes extractPathnameRouteParamSegmentsFromLoaderTree, which performs a breadth-first search across the loader tree retrieved from routeModule.userland.loaderTree.
export function extractPathnameRouteParamSegmentsFromLoaderTree(
loaderTree: LoaderTree,
route: NormalizedAppRoute
): {
pathnameRouteParamSegments: Array<{
readonly name: string
readonly paramName: string
readonly paramType: DynamicParamTypes
}>
params: Params
} {
const segmentsByDepth = new Map<
number,
{
readonly name: string
readonly paramName: string
readonly paramType: DynamicParamTypes
}
>()
const params: Params = {}
// BFS traversal with depth and path tracking
const queue: Array<{
tree: LoaderTree
depth: number
currentPath: NormalizedAppRouteSegment[]
}> = [{ tree: loaderTree, depth: 0, currentPath: [] }]During tree traversal, parseLoaderTree extracts the segment, parallel routes, and module conventions from each tree node. Route segments are parsed using parseAppRouteSegment. Route groups (route-group) and parallel route markers (parallel-route) are excluded from contributing directly to URL pathname generation.
while (queue.length > 0) {
const { tree, depth, currentPath } = queue.shift()!
const { segment, parallelRoutes } = parseLoaderTree(tree)
// Build the path for the current node
let updatedPath = currentPath
let nextDepth = depth
const appSegment = parseAppRouteSegment(segment)
// Only add to path if it's a real segment that appears in the URL
// Route groups and parallel markers don't contribute to URL pathname
if (
appSegment &&
appSegment.type !== 'route-group' &&
appSegment.type !== 'parallel-route'
) {
updatedPath = [...currentPath, appSegment]
nextDepth = depth + 1
}Dynamic segments must satisfy depth and prefix validations before their parameters are accepted. validatePrefixMatch checks that accumulated static segments, dynamic parameters, and interception markers align exactly with the target route segments.
function validatePrefixMatch(
currentPath: NormalizedAppRouteSegment[],
route: NormalizedAppRoute
): boolean {
for (let i = 0; i < currentPath.length; i++) {
const pathSegment = currentPath[i]
const targetPathSegment = route.segments[i]
// Type mismatch - one is static, one is dynamic
if (pathSegment.type !== targetPathSegment.type) {
return false
}
// One has an interception marker, the other doesn't.
if (
pathSegment.interceptionMarker !== targetPathSegment.interceptionMarker
) {
return false
}
// Both are static but names don't match
if (
pathSegment.type === 'static' &&
targetPathSegment.type === 'static' &&
pathSegment.name !== targetPathSegment.name
) {
return false
}
// Both are dynamic but param names don't match
else if (
pathSegment.type === 'dynamic' &&
targetPathSegment.type === 'dynamic' &&
pathSegment.param.paramType !== targetPathSegment.param.paramType &&
pathSegment.param.paramName !== targetPathSegment.param.paramName
) {
return false
}
}
return true
}Parameter Resolution and Validation Rules
For AppRouteRouteModule, parameter segments are extracted by filtering the route segment array directly via extractPathnameRouteParamSegmentsFromSegments.
export function extractPathnameRouteParamSegmentsFromSegments(
segments: readonly Readonly<AppSegment>[]
): Array<{
readonly name: string
readonly paramName: string
readonly paramType: DynamicParamTypes
}> {
// TODO: should we consider what values are already present in the page?
// For AppRouteRouteModule, filter the segments array to get the route params
// that contribute to the pathname.
const result: Array<{
readonly name: string
readonly paramName: string
readonly paramType: DynamicParamTypes
}> = []
for (const segment of segments) {
// Skip segments without param info.
if (!segment.paramName || !segment.paramType) continue
// Collect all the route param keys that contribute to the pathname.
result.push({
name: segment.name,
paramName: segment.paramName,
paramType: segment.paramType,
})
}
return result
}Sources: packages/next/src/build/static-paths/utils.ts:80-110
The core build function generateStaticParams evaluates parameter combinations while checking configuration rules and output modes. If nextConfigOutput is set to 'export', encountering dynamicParams: true triggers an error.
if (
segments.some((generate) => generate.config?.dynamicParams === true) &&
nextConfigOutput === 'export'
) {
throw new Error(
'"dynamicParams: true" cannot be used with "output: export". See more info here: https://nextjs.org/docs/app/building-your-application/deploying/static-exports'
)
}Sources: packages/next/src/build/static-paths/app.ts:918-925
| Validation Check | Condition | Outcome |
|---|---|---|
| Export Output Dynamic Check | segments.some(s => s.config?.dynamicParams === true) && nextConfigOutput === 'export' | Throws "dynamicParams: true" cannot be used with "output: export" error |
| Param Matching Feature Gate | hasParamMatchingExport && !cacheComponents | Throws cannot use parameter matching without enabling cacheComponents error |
| Navigation Mode Guarantee | isEnsureStaticPage && paramMatching with mode 'fallback' or 'dynamic' | Throws error prohibiting 'fallback' or 'dynamic' parameter matching with ensureStatic = "navigation" |
| Static Export Incomplete Params | nextConfigOutput === 'export' && routeParams.length > 0 && !hadAllParamsGenerated | Throws incomplete params error listing missing parameter names |
Sources: packages/next/src/build/static-paths/app.ts:918-957, packages/next/src/build/static-paths/app.ts:1009-1017, packages/next/src/build/static-paths/app.ts:1126-1134
Warning
When output: export is configured, every generated params object returned by generateStaticParams() must include all dynamic route parameters; missing parameters cause the build to fail immediately.
Sources: packages/next/src/build/static-paths/app.ts:1126-1134
Note
Parallel slots can repeat parameters, and route groups can cause loader tree traversal to encounter deeper URL parameters before shallower ones. Parameters are sorted by depth ascending (a - b) to ensure consistent ordering.
Pages Router Static Paths Resolution
The build process for the legacy Pages Router invokes the page component's getStaticPaths method, passing a cloned array of configured locales and the defaultLocale. The return value undergoes strict validation to ensure it is a non-null object containing valid keys and a boolean or 'blocking' fallback property.
const staticPathsResult = await getStaticPaths({
// We create a copy here to avoid having the types of `getStaticPaths`
// change. This ensures that users can't mutate this array and have it
// poison the reference.
locales: [...(locales ?? [])],
defaultLocale,
})
const expectedReturnVal =
`Expected: { paths: [], fallback: boolean }\n` +
`See here for more info: https://nextjs.org/docs/messages/invalid-getstaticpaths-value`
if (
!staticPathsResult ||
typeof staticPathsResult !== 'object' ||
Array.isArray(staticPathsResult)
) {
throw new Error(
`Invalid value returned from getStaticPaths in ${page}. Received ${typeof staticPathsResult} ${expectedReturnVal}`
)
}
const invalidStaticPathKeys = Object.keys(staticPathsResult).filter(
(key) => !(key === 'paths' || key === 'fallback')
)
if (invalidStaticPathKeys.length > 0) {
throw new Error(
`Extra keys returned from getStaticPaths in ${page} (${invalidStaticPathKeys.join(
', '
)}) ${expectedReturnVal}`
)
}
if (
!(
typeof staticPathsResult.fallback === 'boolean' ||
staticPathsResult.fallback === 'blocking'
)
) {
throw new Error(
`The \`fallback\` key must be returned from getStaticPaths in ${page}.\n` +
expectedReturnVal
)
}Sources: packages/next/src/build/static-paths/pages.ts:31-75
| Validation Check | Condition | Error Thrown / Outcome |
|---|---|---|
| Non-Object Result | `!staticPathsResult | |
| Extra Return Keys | Any key other than paths or fallback present | Throws extra keys returned error |
| Missing/Invalid Fallback | fallback is not a boolean or 'blocking' | Throws missing fallback key error |
| Invalid Paths Type | paths property is not an array | Throws invalid paths value error |
Sources: packages/next/src/build/static-paths/pages.ts:43-84
Path entries within the paths array can be supplied either as strings or objects containing a params dictionary and an optional locale. String entries are cleaned of locale prefixes, matched against the route regex, and decoded using decodeURIComponent while escaping path delimiters. Object entries are checked for illegal top-level keys outside params and locale, and their parameters are validated against expected route parameter definitions.
const invalidKeys = Object.keys(entry).filter(
(key) => key !== 'params' && key !== 'locale'
)
if (invalidKeys.length) {
throw new Error(
`Additional keys were returned from \`getStaticPaths\` in page "${page}". ` +
`URL Parameters intended for this dynamic route must be nested under the \`params\` key, i.e.:` +
`\n\n\treturn { params: { ${routeParameterKeys
.map((k) => `${k}: ...`)
.join(', ')} } }` +
`\n\nKeys that need to be moved: ${invalidKeys.join(', ')}.\n`
)
}
const { params = {} } = entry
let builtPage = page
let encodedBuiltPage = page
routeParameterKeys.forEach((validParamKey) => {
const { repeat, optional } = _routeRegex.groups[validParamKey]
let paramValue = params[validParamKey]
if (
optional &&
params.hasOwnProperty(validParamKey) &&
(paramValue === null ||
paramValue === undefined ||
(paramValue as any) === false)
) {
paramValue = []
}
if (
(repeat && !Array.isArray(paramValue)) ||
(!repeat && typeof paramValue !== 'string') ||
typeof paramValue === 'undefined'
) {
throw new Error(
`A required parameter (${validParamKey}) was not provided as ${
repeat ? 'an array' : 'a string'
} received ${typeof paramValue} in getStaticPaths for ${page}`
)
}Sources: packages/next/src/build/static-paths/pages.ts:133-175
Warning
Object-provided paths in getStaticPaths must place all dynamic route parameters exclusively under the params key; returning additional top-level keys triggers an error listing the specific properties that must be moved.
Sources: packages/next/src/build/static-paths/pages.ts:133-146
Parameter values for optional and repeating segments are normalized into arrays or strings respectively, then encoded using encodeParam with path delimiter escaping or standard encodeURIComponent. Finally, locale identifiers are validated against the configured locales list, and prerender route records are populated with resolved pathnames, params, and fallback modes derived via parseStaticPathsResult.
routeParameterKeys.forEach((validParamKey) => {
const { repeat, optional } = _routeRegex.groups[validParamKey]
let paramValue = params[validParamKey]
if (
optional &&
params.hasOwnProperty(validParamKey) &&
(paramValue === null ||
paramValue === undefined ||
(paramValue as any) === false)
) {
paramValue = []
}
if (
(repeat && !Array.isArray(paramValue)) ||
(!repeat && typeof paramValue !== 'string') ||
typeof paramValue === 'undefined'
) {
throw new Error(
`A required parameter (${validParamKey}) was not provided as ${
repeat ? 'an array' : 'a string'
} received ${typeof paramValue} in getStaticPaths for ${page}`
)
}
let replaced = `[${repeat ? '...' : ''}${validParamKey}]`
if (optional) {
replaced = `[${replaced}]`
}
builtPage = builtPage.replace(
replaced,
encodeParam(paramValue, (value) => escapePathDelimiters(value, true))
)
encodedBuiltPage = encodedBuiltPage.replace(
replaced,
encodeParam(paramValue, encodeURIComponent)
)
})
if (!builtPage && !encodedBuiltPage) {
return
}
if (entry.locale && !locales?.includes(entry.locale)) {
throw new Error(
`Invalid locale returned from getStaticPaths for ${page}, the locale ${entry.locale} is not specified in ${configFileName}`
)
}
const curLocale = entry.locale || defaultLocale || ''
const pathname = normalizePathname(
`${curLocale ? `/${curLocale}` : ''}${curLocale && builtPage === '/' ? '' : builtPage}`
)
if (!prerenderedRoutesByPathname.has(pathname)) {
prerenderedRoutesByPathname.set(pathname, {
params,
pathname,
encodedPathname: normalizePathname(
`${curLocale ? `/${curLocale}` : ''}${
curLocale && encodedBuiltPage === '/' ? '' : encodedBuiltPage
}`
),
fallbackRouteParams: undefined,
fallbackMode: parseStaticPathsResult(staticPathsResult.fallback),
fallbackRootParams: undefined,
throwOnEmptyStaticShell: undefined,
})
}
}Sources: packages/next/src/build/static-paths/pages.ts:152-223
Prerender Execution in Worker Pools
Worker Initialization and Request Mocking
Prerendering execution for individual routes occurs inside isolated worker processes spawned during build. When exportPages or exportPageImpl initializes, it installs native bindings and code frame support, and establishes global behaviors on globalThis.__NEXT_DATA__. Each prerender execution constructs mocked request and response objects using createRequestResponseMocks based on the normalized target path, simulating HTTP environments for static generation.
;(globalThis as any).__NEXT_DATA__ = {
nextExport: true,
}Sources: packages/next/src/export/worker.ts:63-65, packages/next/src/export/worker.ts:178-178, packages/next/src/export/worker.ts:382-388
Note
Native bindings are explicitly loaded inside the worker process so that code frame rendering—which relies on the native codeFrameColumns function—operates correctly during prerendering failures.
Sources: packages/next/src/export/worker.ts:385-387
Pages Router Prerendering Execution
For legacy Pages Router routes, exportPagesPage executes page components while enforcing strict rendering rules. It checks whether getServerSideProps is defined, throwing an error if server-side rendering props are requested during static export. For static pages without dynamic parameters, non-dynamic SSG prerendering bypasses redundant execution if already completed.
if (components.getServerSideProps) {
throw new Error(`Error for page ${page}: ${SERVER_PROPS_EXPORT_ERROR}`)
}
// for non-dynamic SSG pages we should have already
// prerendered the file
if (!buildExport && components.getStaticProps && !isDynamic) {
return
}Sources: packages/next/src/export/routes/pages.ts:50-58
Parameters extracted from paths are merged back into query parameters to support legacy access patterns via useRouter().query. When rendering completes, page metadata and static page data files are appended using MultiFileWriter to the configured pages data directory and HTML filepath destinations.
| Condition / Check | Target Action | Error / Behavior Outcome |
|---|---|---|
components.getServerSideProps | Present on page components | Throws SERVER_PROPS_EXPORT_ERROR |
!buildExport && components.getStaticProps && !isDynamic | Non-dynamic SSG page outside build export | Returns early without re-rendering |
typeof components.Component === 'string' | Static component string | Uses RenderResult.fromStatic |
hasOrigQueryValues with static component | Query values supplied on auto-exported page | Throws auto-export query configuration error |
Sources: packages/next/src/export/routes/pages.ts:50-87
Incremental Cache Setup During Build
Custom Cache Handler Initialization and File Path Resolution
The incremental cache instance utilized during static generation is created through createIncrementalCache. When a custom cacheHandler string path is supplied, it is processed via formatDynamicImportPath to convert absolute filesystem paths or file:// import URLs into valid file URLs through resolveCacheHandlerPathToFilesystem. The imported default module is then assigned as the custom cache implementation CurCacheHandler.
export async function createIncrementalCache({
cacheHandler,
cacheMaxMemorySize,
fetchCacheKeyPrefix,
distDir,
dir,
flushToDisk,
cacheHandlers,
requestHeaders,
}: {
cacheHandler?: string
cacheMaxMemorySize: number
fetchCacheKeyPrefix?: string
distDir: string
dir: string
flushToDisk?: boolean
requestHeaders?: Record<string, string | string[] | undefined>
cacheHandlers?: Record<string, string | undefined>
}) {
// Custom cache handler overrides.
let CacheHandler: any
if (cacheHandler) {
CacheHandler = interopDefault(
await import(formatDynamicImportPath(dir, cacheHandler)).then(
(mod) => mod.default || mod
)
)
}Sources: packages/next/src/export/helpers/create-incremental-cache.ts:13-40, packages/next/src/lib/format-dynamic-import-path.ts:11-34
Warning
Passing an absolute Windows path directly to dynamic imports causes it to interpret the drive letter as a protocol prefix (C:). formatDynamicImportPath avoids this by ensuring paths are correctly resolved and converted to file URLs using pathToFileURL.
Sources: packages/next/src/lib/format-dynamic-import-path.ts:19-34
When multiple cacheHandlers are provided, the cache handlers subsystem is initialized with cacheMaxMemorySize. Each handler entry iterates through the dictionary, imports the corresponding path using formatDynamicImportPath, and registers it via setCacheHandler.
Sources: packages/next/src/export/helpers/create-incremental-cache.ts:42-58
IncrementalCache and Work Store Construction
The core cache instance is instantiated using the IncrementalCache class, configured with node filesystem methods (nodeFs), a server distribution directory set under distDir/server, and version 4 prerender manifest metadata. The created instance is assigned to globalThis.__incrementalCache for global process access.
Sources: packages/next/src/export/helpers/create-incremental-cache.ts:65-87
During prerender execution, createPrerenderWorkStore constructs a WorkStore instance that tracks fetch metrics, cache lifecycles, and request contexts. It binds either the incremental cache supplied via renderOpts.incrementalCache or falls back to the global __incrementalCache instance.
export function createPrerenderWorkStore(context: WorkStoreContext): WorkStore {
return createWorkStoreImpl(
context,
!!process.env.__NEXT_DEV_SERVER ||
!!process.env.NEXT_DEBUG_BUILD ||
process.env.NEXT_SSG_FETCH_METRICS === '1'
)
}
function createWorkStoreImpl(
{
page,
renderOpts,
isPrefetchRequest,
buildId,
deploymentId,
previouslyRevalidatedTags,
nonce,
}: WorkStoreContext,
shouldTrackFetchMetrics: boolean
): WorkStore {
const store: WorkStore = {
page,
route: normalizeAppPath(page),
incrementalCache:
// we fallback to a global incremental cache for edge-runtime locally
// so that it can access the fs cache without mocks
renderOpts.incrementalCache || (globalThis as any).__incrementalCache,Sources: packages/next/src/server/async-storage/work-store.ts:95-122
| Function / Helper | Primary Role | Associated Options & Parameters |
|---|---|---|
createIncrementalCache | Initializes local or custom cache handlers and sets up IncrementalCache | cacheHandler, cacheMaxMemorySize, fetchCacheKeyPrefix, distDir, dir, flushToDisk, cacheHandlers, requestHeaders |
formatDynamicImportPath | Resolves file paths and URLs into valid file URLs for dynamic imports | dir, filePath |
resolveCacheHandlerPathToFilesystem | Unwraps file:// URLs into filesystem paths | filePath |
createPrerenderWorkStore | Initializes work storage context for static prerendering | context containing page, renderOpts, buildId, deploymentId |
Sources: packages/next/src/export/helpers/create-incremental-cache.ts:13-31, packages/next/src/lib/format-dynamic-import-path.ts:11-26, packages/next/src/server/async-storage/work-store.ts:95-102
Static Export and Artifact Packaging
Processing Static HTML and RSC Payload Emits
During a static export or production build packaging phase, Next.js validates that configured outputs adhere to static generation constraints. When nextConfig.output === 'export', the builder inspects the routes manifest to catch incompatible features such as interception route rewrites under routesManifest.rewrites.beforeFiles and dynamic server action references in the server actions manifest (serverActionsManifest.node and serverActionsManifest.edge), throwing an ExportError if either is found.
if (nextConfig.output === 'export') {
const routesManifest = require(join(distDir, ROUTES_MANIFEST))
// We already prevent rewrites earlier in the process, however Next.js will insert rewrites
// for interception routes so we need to check for that here.
if (routesManifest?.rewrites?.beforeFiles?.length > 0) {
const hasInterceptionRouteRewrite =
routesManifest.rewrites.beforeFiles.some(isInterceptionRouteRewrite)
if (hasInterceptionRouteRewrite) {
throw new ExportError(
`Intercepting routes are not supported with static export.\nRead more: https://nextjs.org/docs/app/building-your-application/deploying/static-exports#unsupported-features`
)
}
}
const actionIds = [
...Object.keys(serverActionsManifest.node),
...Object.keys(serverActionsManifest.edge),
]
if (
actionIds.some(
(actionId) =>
extractInfoFromServerReferenceId(actionId).type === 'server-action'
)
) {
throw new ExportError(
`Server Actions are not supported with static export.\nRead more: https://nextjs.org/docs/app/building-your-application/deploying/static-exports#unsupported-features`
)
}Sources: packages/next/src/export/index.ts:444-474
Warning
Static export mode prohibits runtime server actions, intercepting route rewrites, default-loader image optimization, and middleware or API routes. Encountering any of these during packaging immediately halts the build with an ExportError.
Sources: packages/next/src/export/index.ts:428-434, packages/next/src/export/index.ts:454-457, packages/next/src/export/index.ts:471-474
Adapter Output Mapping and Route Manifest Generation
Build completion adapters marshal pages, static files, and middleware into structured output maps for hosting platforms. Static pages and internationalized locale paths are assigned AdapterOutputType.STATIC_FILE targets pointing to .html outputs, accompanied by .rsc payload fallback files (rsc-fallback.json) when application router pages are present.
if (config.i18n) {
for (const locale of config.i18n.locales || []) {
const localePage =
page === '/' ? `/${locale}` : addPathPrefix(page, `/${locale}`)
const localeOutput = {
id: localePage,
pathname: localePage,
type: AdapterOutputType.STATIC_FILE,
filePath: path.join(
pagesDistDir,
`${normalizePagePath(localePage)}.html`
),
immutableHash: undefined,
} satisfies AdapterOutput['STATIC_FILE']
outputs.staticFiles.push(localeOutput)
emittedStaticFilePathnames.add(localeOutput.pathname)
if (appPageKeys && appPageKeys.length > 0) {
outputs.staticFiles.push({
id: `${localePage}.rsc`,
pathname: `${localePage}.rsc`,
type: AdapterOutputType.STATIC_FILE,
filePath: rscFallbackPath,
immutableHash: undefined,
})
}
}
} else {
const staticOutput = {
id: page,
pathname: route,
type: AdapterOutputType.STATIC_FILE,
filePath: pageFile.replace(/\.js$/, '.html'),
immutableHash: undefined,
} satisfies AdapterOutput['STATIC_FILE']
outputs.staticFiles.push(staticOutput)
emittedStaticFilePathnames.add(staticOutput.pathname)
if (appPageKeys && appPageKeys.length > 0) {
outputs.staticFiles.push({
id: `${page}.rsc`,
pathname: `${route}.rsc`,
type: AdapterOutputType.STATIC_FILE,
filePath: rscFallbackPath,
immutableHash: undefined,
})
}
}Sources: packages/next/src/build/adapter/build-complete.ts:943-993
| Adapter Output Type | Target Path Identifier | Runtime / Source Handling |
|---|---|---|
AdapterOutputType.STATIC_FILE | .html or .rsc file path | Static HTML / RSC payload file generated during prerendering |
AdapterOutputType.PAGES | Server entry point (pagesDistDir) | Node.js runtime execution for server-rendered Pages Router routes |
AdapterOutputType.PAGES_API | Server API entry point | Node.js runtime execution for Pages API endpoints |
AdapterOutputType.MIDDLEWARE | distDir/server/middleware.js | Neutral or Node.js runtime execution for global middleware functions |
Sources: packages/next/src/build/adapter/build-complete.ts:973-980, packages/next/src/build/adapter/build-complete.ts:1016-1031, packages/next/src/build/adapter/build-complete.ts:1109-1117
Related
On This Page
OverviewStatic Route Assessment and Staticity ClassificationRoute Module Inspection and Component LoadingPages Router Method Export HoistingApp Router Static Parameter ExtractionLoader Tree Traversal and Segment ExtractionParameter Resolution and Validation RulesPages Router Static Paths ResolutionPrerender Execution in Worker PoolsWorker Initialization and Request MockingPages Router Prerendering ExecutionIncremental Cache Setup During BuildCustom Cache Handler Initialization and File Path ResolutionIncrementalCache and Work Store ConstructionStatic Export and Artifact PackagingProcessing Static HTML and RSC Payload EmitsAdapter Output Mapping and Route Manifest GenerationRelated