Skip to main content
doc0blade47/next.jsdocumentation home
Sign in

Static Generation

Last updated Oct 12, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

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 } = componentsResult

Sources: 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: [] }]

Sources: packages/next/src/build/static-paths/app/extract-pathname-route-param-segments-from-loader-tree.ts:86-113

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
  }

Sources: packages/next/src/build/static-paths/app/extract-pathname-route-param-segments-from-loader-tree.ts:114-134

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
}

Sources: packages/next/src/build/static-paths/app/extract-pathname-route-param-segments-from-loader-tree.ts:36-76

Sources: packages/next/src/build/static-paths/app/extract-pathname-route-param-segments-from-loader-tree.ts:107-202

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 CheckConditionOutcome
Export Output Dynamic Checksegments.some(s => s.config?.dynamicParams === true) && nextConfigOutput === 'export'Throws "dynamicParams: true" cannot be used with "output: export" error
Param Matching Feature GatehasParamMatchingExport && !cacheComponentsThrows cannot use parameter matching without enabling cacheComponents error
Navigation Mode GuaranteeisEnsureStaticPage && paramMatching with mode 'fallback' or 'dynamic'Throws error prohibiting 'fallback' or 'dynamic' parameter matching with ensureStatic = "navigation"
Static Export Incomplete ParamsnextConfigOutput === 'export' && routeParams.length > 0 && !hadAllParamsGeneratedThrows 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.

Sources: packages/next/src/build/static-paths/app/extract-pathname-route-param-segments-from-loader-tree.ts:195-200

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 CheckConditionError Thrown / Outcome
Non-Object Result`!staticPathsResult
Extra Return KeysAny key other than paths or fallback presentThrows extra keys returned error
Missing/Invalid Fallbackfallback is not a boolean or 'blocking'Throws missing fallback key error
Invalid Paths Typepaths property is not an arrayThrows 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 / CheckTarget ActionError / Behavior Outcome
components.getServerSidePropsPresent on page componentsThrows SERVER_PROPS_EXPORT_ERROR
!buildExport && components.getStaticProps && !isDynamicNon-dynamic SSG page outside build exportReturns early without re-rendering
typeof components.Component === 'string'Static component stringUses RenderResult.fromStatic
hasOrigQueryValues with static componentQuery values supplied on auto-exported pageThrows 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 / HelperPrimary RoleAssociated Options & Parameters
createIncrementalCacheInitializes local or custom cache handlers and sets up IncrementalCachecacheHandler, cacheMaxMemorySize, fetchCacheKeyPrefix, distDir, dir, flushToDisk, cacheHandlers, requestHeaders
formatDynamicImportPathResolves file paths and URLs into valid file URLs for dynamic importsdir, filePath
resolveCacheHandlerPathToFilesystemUnwraps file:// URLs into filesystem pathsfilePath
createPrerenderWorkStoreInitializes work storage context for static prerenderingcontext 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 TypeTarget Path IdentifierRuntime / Source Handling
AdapterOutputType.STATIC_FILE.html or .rsc file pathStatic HTML / RSC payload file generated during prerendering
AdapterOutputType.PAGESServer entry point (pagesDistDir)Node.js runtime execution for server-rendered Pages Router routes
AdapterOutputType.PAGES_APIServer API entry pointNode.js runtime execution for Pages API endpoints
AdapterOutputType.MIDDLEWAREdistDir/server/middleware.jsNeutral 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

Was this page helpful?