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

Pages Rendering

Last updated Oct 12, 2026100% grounded
Relevant source files

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

Overview

Pages rendering governs the server-side execution, component tree assembly, and HTML serialization pipeline for legacy Pages Router routes. It provides request dispatching through route modules, coordinates asset resolution with build manifests, manages asynchronous data fetching lifecycles, and integrates static site export workers.

Sources: packages/next/src/server/route-modules/pages/module.ts:124-155, packages/next/src/server/render.tsx:1271-1445, packages/next/src/export/routes/pages.ts:30-145

Pages Route Module Architecture

Route Module Layer and Request Dispatching

The Pages Router leverages a route module architecture built upon the core RouteModule abstraction to handle incoming requests and render pages. The compilation output template wraps userland page modules alongside custom App and Document components into a unified PagesRouteModule instance. This module accepts request parameters via PagesRouteHandlerContext, which bundles the target page identifier, parsed URL query, shared context, and render options (RenderOpts), omitting internal Document and App components.

export class PagesRouteModule extends RouteModule<
  PagesRouteDefinition,
  PagesUserlandModule
> {
  private readonly components: PagesComponents
 
  constructor(options: PagesRouteModuleOptions) {
    super(options)
 
    this.components = options.components
  }
 
  public render(
    req: IncomingMessage,
    res: ServerResponse,
    context: PagesRouteHandlerContext
  ): Promise<RenderResult> {
    return renderToHTMLImpl(
      req,
      res,
      context.page,
      context.query,
      context.renderOpts,
      {
        App: this.components.App,
        Document: this.components.Document,
      },
      context.sharedContext,
      context.renderContext
    )
  }
}

Sources: packages/next/src/server/route-modules/pages/module.ts:124-155

The PagesRouteModule class implements the render method, serving as the bridge that delegates execution directly to renderToHTMLImpl. This implementation takes node request (IncomingMessage) and response (ServerResponse) streams, alongside the context fields, and returns a promise resolving to a RenderResult object.

// Create and export the route module that will be consumed.
export const routeModule = new PagesRouteModule({
  definition: {
    kind: RouteKind.PAGES,
    page: 'VAR_DEFINITION_PAGE',
    pathname: 'VAR_DEFINITION_PATHNAME',
    // The following aren't used in production.
    bundlePath: '',
    filename: '',
  },
  distDir: process.env.__NEXT_RELATIVE_DIST_DIR || '',
  relativeProjectDir: process.env.__NEXT_RELATIVE_PROJECT_DIR || '',
  components: {
    // default export might not exist when optimized for data only
    App: app.default,
    Document: document.default,
  },
  userland,
})

Sources: packages/next/src/build/templates/pages.ts:46-64

Re-exports and Lifecycle Data Methods

During the build process, the pages template module acts as a re-export aggregator for userland page modules. It uses a helper utility (hoist) to expose default components and data-fetching hooks such as getStaticProps, getStaticPaths, and getServerSideProps, along with legacy aliases like unstable_getStaticProps and unstable_getServerSideProps.

Export IdentifierSource Userland SymbolPurpose
defaultuserland['default']The main React page component
getStaticPropsuserland['getStaticProps']Static generation data fetching hook
getStaticPathsuserland['getStaticPaths']Dynamic route paths enumeration for SSG
getServerSidePropsuserland['getServerSideProps']Server-side rendering data fetching hook
configuserland['config']Page-specific configuration options
reportWebVitalsuserland['reportWebVitals']Performance metrics reporting hook
// Re-export the component (should be the default export).
export default hoist(userland, 'default')
 
// 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:14-44

Warning

In the Edge runtime, executing Document.getInitialProps is unsupported. If a custom Document component defines getInitialProps while targeting the Edge runtime, an explicit error is thrown unless a builtin functional document fallback is available.

const BuiltinFunctionalDocument: DocumentType | undefined = (
  Document as any
)[NEXT_BUILTIN_DOCUMENT]
 
if (process.env.NEXT_RUNTIME === 'edge' && Document.getInitialProps) {
  // In the Edge runtime, `Document.getInitialProps` isn't supported.
  // We throw an error here if it's customized.
  if (BuiltinFunctionalDocument) {
    Document = BuiltinFunctionalDocument
  } else {
    throw new Error(
      '`getInitialProps` in Document component is not supported with the Edge Runtime.'
    )
  }
}

Sources: packages/next/src/server/render.tsx:1276-1290

Component Loading and Assets Resolution

Component and Document Loading Implementation

The server initializes components and resolves required assets through loadComponents, which delegates implementation details to loadComponentsImpl. For pages path executions (isAppPath evaluated as false), the loader concurrently requires the custom document (/_document) and app (/_app) modules alongside the target page module via requirePage.

let DocumentMod = {}
let AppMod = {}
if (!isAppPath) {
  ;[DocumentMod, AppMod] = await Promise.all([
    requirePage('/_document', distDir, false),
    requirePage('/_app', distDir, false),
  ])
}

Sources: packages/next/src/server/load-components.ts:203-210

When needsManifestsForLegacyReasons is enabled, development and static generation paths evaluate retry mechanisms (manifestLoadAttempts set to 3 in development mode and 1 in production) to handle concurrent file compilation races when loading manifest files from disk.

if (needsManifestsForLegacyReasons) {
  // In dev mode we retry loading a manifest file to handle a race condition
  // that can occur while app and pages are compiling at the same time, and the
  // build-manifest is still being written to disk while an app path is
  // attempting to load.
  const manifestLoadAttempts = isDev ? 3 : 1
 

Sources: packages/next/src/server/load-components.ts:212-218

Manifest Types and Resolution Paths

Depending on whether Turbopack or Webpack is active, manifests are fetched from designated build directories (distDir) using specific path constructs. For pages routes, reactLoadableManifestPath resolves through normalizePagePath to locate the correct page segment folder.

Manifest ConstantFile Name / SuffixLoading Function / Behavior
BUILD_MANIFESTbuild-manifest.jsonloadManifestWithRetries
REACT_LOADABLE_MANIFESTreact-loadable-manifest.jsontryLoadManifestWithRetries
DYNAMIC_CSS_MANIFESTdynamic-css-manifest.jsonloadManifestWithRetries (Pages dir + Webpack + Prod)
SERVER_REFERENCE_MANIFESTserver-reference-manifest.jsonloadManifestWithRetries (App path only)
SUBRESOURCE_INTEGRITY_MANIFESTsubresource-integrity-manifest.jsonloadManifestWithRetries (Conditional on sriEnabled)
if (!process.env.TURBOPACK) {
  reactLoadableManifestPath = join(
    /* turbopackIgnore: true */ distDir,
    REACT_LOADABLE_MANIFEST
  )
} else if (isAppPath) {
  reactLoadableManifestPath = join(
    /* turbopackIgnore: true */ distDir,
    'server',
    'app',
    page,
    REACT_LOADABLE_MANIFEST
  )
} else {
  reactLoadableManifestPath = join(
    /* turbopackIgnore: true */ distDir,
    'server',
    'pages',
    normalizePagePath(page),
    REACT_LOADABLE_MANIFEST
  )
}

Sources: packages/next/src/server/load-components.ts:220-241

Note

Static metadata routes bypass client-side manifest loading (hasClientManifest evaluates to false when isStaticMetadataRoute(page) is true) to optimize performance.

// Make sure to avoid loading the manifest for static metadata routes for better performance.
const hasClientManifest = !isStaticMetadataRoute(page)
 

Sources: packages/next/src/server/load-components.ts:243-245

Build Manifest and Page File Normalization

The core BuildManifest structure defines fields for runtime assets, including devFiles, polyfillFiles, lowPriorityFiles, rootMainFiles, and a mapping of pages where /_app and page segments map to read-only string arrays of chunk files.

export type BuildManifest = {
  devFiles: readonly string[]
  polyfillFiles: readonly string[]
  lowPriorityFiles: readonly string[]
  rootMainFiles: readonly string[]
  // this is a separate field for flying shuttle to allow
  // different root main files per entries/build (ideally temporary)
  // until we can stitch the runtime chunks together safely
  rootMainFilesTree: { [appRoute: string]: readonly string[] }
  pages: {
    '/_app': readonly string[]
    [page: string]: readonly string[]
  }
  // Per-page Turbopack chunk-group bootstrap params, stored as raw JSON.
  pagesChunkGroupBootstrapParams?: { [page: string]: object }
  // The chunk-loading global the runtime drains (default "TURBOPACK").
  chunkLoadingGlobal?: string
}

Sources: packages/next/src/server/get-page-files.ts:4-21

To locate assets for a specific route, getPageFile processes the path through normalizePagePath and denormalizePagePath to resolve the exact manifest key.

export function getPageFiles(
  buildManifest: BuildManifest,
  page: string
): readonly string[] {
  const normalizedPage = denormalizePagePath(normalizePagePath(page))
  let files = buildManifest.pages[normalizedPage]
 
  if (!files) {
    console.warn(
      `Could not find files for ${normalizedPage} in .next/build-manifest.json`
    )
    return []
  }
 
  return files
}

Sources: packages/next/src/server/get-page-files.ts:49-64

Path transformation follows specific normalization rules depending on index positioning and dynamic parameters:

Input Page PathTransformed Path (normalizePagePath)Denormalized Path (denormalizePagePath)
//index/
/index/index/index/
/index/foo/index/index/foo/foo
/foo/bar/foo/bar/foo/bar
 *  - `/` -> `/index`
 *  - `/index/foo` -> `/index/index/foo`
 *  - `/index` -> `/index/index`
 */
export function normalizePagePath(page: string): string {
  const normalized =
    /^\/index(\/|$)/.test(page) && !isDynamicRoute(page)
      ? `/index${page}`
      : page === '/'
        ? '/index'
        : ensureLeadingSlash(page)

Sources: packages/next/src/shared/lib/page-path/normalize-page-path.ts:10-20, packages/next/src/shared/lib/page-path/denormalize-page-path.ts:8-18

Warning

Path normalization is not idempotent for routes starting with /index. Repeatedly calling normalizePagePath nests additional /index segments into the path string.

/**
 * Takes a page and transforms it into its file counterpart ensuring that the
 * output is normalized. Note this function is not idempotent because a page
 * `/index` can be referencing `/index/index.js` and `/index/index` could be
 * referencing `/index/index/index.js`. Examples:
 *  - `/` -> `/index`
 *  - `/index/foo` -> `/index/index/foo`
 *  - `/index` -> `/index/index`
 */

Sources: packages/next/src/shared/lib/page-path/normalize-page-path.ts:5-13

Data Fetching Execution Lifecycles

Custom App Initial Props Delegation

The initialization and data population of Pages Router pages rely on utility helpers that load data before tree rendering occurs. The core utility loadGetInitialProps coordinates data fetching for components such as custom App entries and individual page nodes.

export async function loadGetInitialProps<
  C extends BaseContext,
  IP = {},
  P = {},
>(App: NextComponentType<C, IP, P>, ctx: C): Promise<IP> {
  if (process.env.NODE_ENV !== 'production') {
    if (App.prototype?.getInitialProps) {
      const message = `"${getDisplayName(
        App
      )}.getInitialProps()" is defined as an instance method - visit https://nextjs.org/docs/messages/get-initial-props-as-an-instance-method for more information.`
      throw new Error(message)
    }
  }
  // when called from _app `ctx` is nested in `ctx`
  const res = ctx.res || (ctx.ctx && ctx.ctx.res)
 
  if (!App.getInitialProps) {
    if (ctx.ctx && ctx.Component) {
      // @ts-ignore pageProps default
      return {
        pageProps: await loadGetInitialProps(ctx.Component, ctx.ctx),
      }
    }
    return {} as IP
  }
 
  const props = await App.getInitialProps(ctx)
 
  if (res && isResSent(res)) {
    return props
  }
 
  if (!props) {
    const message = `"${getDisplayName(
      App
    )}.getInitialProps()" should resolve to an object. But found "${props}" instead.`
    throw new Error(message)
  }
 
  if (process.env.NODE_ENV !== 'production') {
    if (Object.keys(props).length === 0 && !ctx.ctx) {
      console.warn(
        `${getDisplayName(
          App
        )} returned an empty object from \`getInitialProps\`. This de-optimizes and prevents automatic static optimization. https://nextjs.org/docs/messages/empty-object-getInitialProps`
      )
    }
  }
 
  return props
}

Sources: packages/next/src/shared/lib/utils.ts:373-423

During development, loadGetInitialProps checks whether getInitialProps is improperly defined as an instance method rather than a static method on the component prototype, throwing an error if a mismatch is detected.

Caution

Defining getInitialProps as an instance method instead of a static method on a page component or custom App triggers an immediate development-time error.

if (process.env.NODE_ENV !== 'production') {
  if (App.prototype?.getInitialProps) {
    const message = `"${getDisplayName(
      App
    )}.getInitialProps()" is defined as an instance method - visit https://nextjs.org/docs/messages/get-initial-props-as-an-instance-method for more information.`
    throw new Error(message)
  }
}

Sources: packages/next/src/shared/lib/utils.ts:378-385

The custom App component (App) defines its default getInitialProps through appGetInitialProps, which delegates data loading directly to the active page component by invoking loadGetInitialProps on the supplied Component and ctx parameters.

async function appGetInitialProps({
  Component,
  ctx,
}: AppContext): Promise<AppInitialProps> {
  const pageProps = await loadGetInitialProps(Component, ctx)
  return { pageProps }
}

Sources: packages/next/src/pages/_app.tsx:26-32

If a component lacks a getInitialProps static method entirely, loadGetInitialProps falls back to inspecting nested context properties or returns an empty object.

Condition / CheckBehavior in loadGetInitialProps
App.prototype.getInitialProps (Dev-only)Throws an error instructing the user to remove instance-level definition.
Missing App.getInitialProps with nested ctx.ComponentRecursively loads initial props from ctx.Component using ctx.ctx.
Missing App.getInitialProps without nested componentReturns an empty object ({} as IP).
Resolved props evaluate to null or undefinedThrows an error stating getInitialProps() should resolve to an object.
Resolved props are an empty object (Object.keys(props).length === 0)Emits a development warning regarding de-optimization and prevention of automatic static optimization.
if (process.env.NODE_ENV !== 'production') {
  if (App.prototype?.getInitialProps) {
    const message = `"${getDisplayName(
      App
    )}.getInitialProps()" is defined as an instance method - visit https://nextjs.org/docs/messages/get-initial-props-as-an-instance-method for more information.`
    throw new Error(message)
  }
}
// when called from _app `ctx` is nested in `ctx`
const res = ctx.res || (ctx.ctx && ctx.ctx.res)
 
if (!App.getInitialProps) {
  if (ctx.ctx && ctx.Component) {
    // @ts-ignore pageProps default
    return {
      pageProps: await loadGetInitialProps(ctx.Component, ctx.ctx),
    }
  }
  return {} as IP
}
 
const props = await App.getInitialProps(ctx)
 
if (res && isResSent(res)) {
  return props
}
 
if (!props) {
  const message = `"${getDisplayName(
    App
  )}.getInitialProps()" should resolve to an object. But found "${props}" instead.`
  throw new Error(message)
}
 
if (process.env.NODE_ENV !== 'production') {
  if (Object.keys(props).length === 0 && !ctx.ctx) {
    console.warn(
      `${getDisplayName(
        App
      )} returned an empty object from \`getInitialProps\`. This de-optimizes and prevents automatic static optimization. https://nextjs.org/docs/messages/empty-object-getInitialProps`
    )
  }
}
 

Sources: packages/next/src/shared/lib/utils.ts:378-421

Component Tree Composition Flow

Assembling the Custom App and Page Hierarchy

The render pipeline constructs the React element tree by wrapping the active page component and its custom App wrapper inside an isomorphic container structure. Within renderContent, the server determines whether an error state or normal route rendering is active, forwarding either the ErrorDebug component or the core page element tree wrapped with AppContainerWithIsomorphicFiberStructure and a root Body element (<div id="__next">).

const renderContent = (_App: AppType, _Component: NextComponentType) => {
  const EnhancedApp = _App || App
  const EnhancedComponent = _Component || Component
 
  return ctx.err && ErrorDebug ? (
    <Body>
      <ErrorDebug />
    </Body>
  ) : (
    <Body>
      <AppContainerWithIsomorphicFiberStructure>
        {renderPageTree(EnhancedApp, EnhancedComponent, {
          ...props,
          router,
        })}
      </AppContainerWithIsomorphicFiberStructure>
    </Body>
  )
}

Sources: packages/next/src/server/render.tsx:1351-1369

The default custom App class exported by Next.js establishes the baseline component rendering behavior by extracting Component and pageProps from its instance properties and rendering the page component directly.

export default class App<P = any, CP = {}, S = {}> extends React.Component<
  P & AppProps<CP>,
  S
> {
  static origGetInitialProps = appGetInitialProps
  static getInitialProps = appGetInitialProps
 
  render() {
    const { Component, pageProps } = this.props as AppProps<CP>
 
    return <Component {...pageProps} />
  }

Sources: packages/next/src/pages/_app.tsx:34-45

Warning

Returning router or Component properties inside getInitialProps from _app.js throws an explicit development-time error to prevent corrupting router state and element reconciliation.

if (
  process.env.__NEXT_DEV_SERVER &&
  (props.router || props.Component)
) {
  throw new Error(
    `'router' and 'Component' can not be returned in getInitialProps from _app.js https://nextjs.org/docs/messages/cant-override-next-props`
  )
}

Sources: packages/next/src/server/render.tsx:1315-1322

Document Shell and Script Injection

Document Lifecycle and Initialization

The document serialization pipeline coordinates the custom or built-in Document component lifecycle. When rendering pages, the server inspects whether a custom Document defines getInitialProps. If an edge runtime context is active alongside a customized Document.getInitialProps, Next.js throws an error because the Edge Runtime does not support custom document initial props unless a built-in functional document fallback is available.

const BuiltinFunctionalDocument: DocumentType | undefined = (
  Document as any
)[NEXT_BUILTIN_DOCUMENT]
 
if (process.env.NEXT_RUNTIME === 'edge' && Document.getInitialProps) {
  // In the Edge runtime, `Document.getInitialProps` isn't supported.
  // We throw an error here if it's customized.
  if (BuiltinFunctionalDocument) {
    Document = BuiltinFunctionalDocument
  } else {
    throw new Error(
      '`getInitialProps` in Document component is not supported with the Edge Runtime.'
    )
  }
}

Sources: packages/next/src/server/render.tsx:1276-1290

const hasDocumentGetInitialProps =
  process.env.NEXT_RUNTIME !== 'edge' && !!Document.getInitialProps
 

Sources: packages/next/src/server/render.tsx:1383-1385

Caution

Using Document.getInitialProps inside the Edge runtime triggers an immediate execution error, as initial props loading for documents is prohibited in lightweight edge environments.

if (process.env.NEXT_RUNTIME === 'edge' && Document.getInitialProps) {
  // In the Edge runtime, `Document.getInitialProps` isn't supported.
  // We throw an error here if it's customized.
  if (BuiltinFunctionalDocument) {
    Document = BuiltinFunctionalDocument
  } else {
    throw new Error(
      '`getInitialProps` in Document component is not supported with the Edge Runtime.'
    )
  }

Sources: packages/next/src/server/render.tsx:1280-1289

HtmlContext and Document Component Elements

The document component exposes layout tags (Html, Main) that consume context via useHtmlContext. The Html component captures attributes like lang and data-dpl-id from the deployment identifier while triggering script loader registration items via handleDocumentScriptLoaderItems. The Main component evaluates to a custom element tag <next-js-internal-body-render-target />, marking the precise insertion boundary where the rendered React component tree gets mounted into the document shell.

export function Html(
  props: React.DetailedHTMLProps<
    React.HtmlHTMLAttributes<HTMLHtmlElement>,
    HTMLHtmlElement
  >
) {
  const {
    docComponentsRendered,
    locale,
    scriptLoader,
    deploymentId,
    __NEXT_DATA__,
  } = useHtmlContext()
 
  docComponentsRendered.Html = true
  handleDocumentScriptLoaderItems(scriptLoader, __NEXT_DATA__, props)
 
  return (
    <html
      {...props}
      lang={props.lang || locale || undefined}
      data-dpl-id={deploymentId || undefined}
    />
  )
}
 
export function Main() {
  const { docComponentsRendered } = useHtmlContext()
  docComponentsRendered.Main = true
  // @ts-ignore
  return <next-js-internal-body-render-target />
}

Sources: packages/next/src/pages/_document.tsx:970-1001

Script Injection and Hydration Tags

Script injection relies on manifest data retrieved via getDocumentFiles, which combines shared files (/_app) and page-specific files. Polyfill scripts are emitted first with noModule={true} and deferred attributes depending on optimized loading flags. Dynamic import chunks and regular page scripts are mapped with cache-busting query strings derived from asset query parameters or immutable chunk paths.

function getDocumentFiles(
  buildManifest: BuildManifest,
  pathname: string
): DocumentFiles {
  const sharedFiles: readonly string[] = getPageFiles(buildManifest, '/_app')
  const pageFiles: readonly string[] = getPageFiles(buildManifest, pathname)
 
  return {
    sharedFiles,
    pageFiles,
    allFiles: [...new Set([...sharedFiles, ...pageFiles])],
  }
}
 
function getPolyfillScripts(context: HtmlProps, props: OriginProps) {
  // polyfills.js has to be rendered as nomodule without async
  // It also has to be the first script to load
  const {
    assetPrefix,
    buildManifest,
    assetQueryString,
    disableOptimizedLoading,
    crossOrigin,
  } = context
 
  return buildManifest.polyfillFiles
    .filter(
      (polyfill) => polyfill.endsWith('.js') && !polyfill.endsWith('.module.js')
    )
    .map((polyfill) => (
      <script
        key={polyfill}
        defer={!disableOptimizedLoading}
        nonce={props.nonce}
        crossOrigin={props.crossOrigin || crossOrigin}
        noModule={true}
        src={`${assetPrefix}/_next/${encodeURIPath(
          polyfill
        )}${assetQueryString}`}
      />
    ))
}

Sources: packages/next/src/pages/_document.tsx:57-98

function getScripts(
  context: HtmlProps,
  props: OriginProps,
  files: DocumentFiles
) {
  const {
    assetPrefix,
    buildManifest,
    isDevelopment,
    assetQueryString,
    mutableAssetQueryString,
    disableOptimizedLoading,
    crossOrigin,
  } = context
 
  const normalScripts = files.allFiles.filter((file) => file.endsWith('.js'))
  const lowPriorityScripts = buildManifest.lowPriorityFiles?.filter((file) =>
    file.endsWith('.js')
  )
 
  const scripts = [...normalScripts, ...lowPriorityScripts].map((file) => {
    // static/immutable/chunks/51e975e7b637a580.js should use the immutable id, while
    // static/Yj152X97rfGgF7NPcJEZs/_ssgManifest.js should use the deployment id
    const query = file.startsWith('static/immutable/chunks')
      ? assetQueryString
      : mutableAssetQueryString
    return (
      <script
        key={file}
        src={`${assetPrefix}/_next/${encodeURIPath(file)}${query}`}
        nonce={props.nonce}
        async={!isDevelopment && disableOptimizedLoading}
        defer={!disableOptimizedLoading}
        crossOrigin={props.crossOrigin || crossOrigin}
      />
    )
  })

Sources: packages/next/src/pages/_document.tsx:163-199

For Turbopack production builds, getInlineBootstrapScript seeds the global runtime queue using getTurbopackChunkGroupBootstrap before chunk script elements drain the queue.

function getInlineBootstrapScript(context: HtmlProps, props: OriginProps) {
  const { buildManifest, __NEXT_DATA__, crossOrigin } = context
  // Only Turbopack production builds populate these; nothing to inline otherwise.
  if (
    !buildManifest.pagesChunkGroupBootstrapParams ||
    !buildManifest.chunkLoadingGlobal
  ) {
    return null
  }
  const bootstrap = getTurbopackChunkGroupBootstrap(
    buildManifest.pagesChunkGroupBootstrapParams,
    buildManifest.chunkLoadingGlobal,
    ['/_app', __NEXT_DATA__.page]
  )
  if (!bootstrap) return null
 
  const html = htmlEscapeJsonString(bootstrap)
  return (
    <script
      key="turbopack-bootstrap"
      nonce={props.nonce}
      crossOrigin={props.crossOrigin || crossOrigin}
      dangerouslySetInnerHTML={{ __html: html }}
    />
  )
}

Sources: packages/next/src/pages/_document.tsx:136-161

export function getTurbopackChunkGroupBootstrap(
  paramsByPage: { [page: string]: object },
  chunkLoadingGlobal: string,
  pages: readonly string[]
): string | undefined {
  const g = JSON.stringify(chunkLoadingGlobal)
  const statements = pages
    .map((page) => paramsByPage[page])
    .filter(Boolean)
    .map(
      (params) =>
        `(globalThis[${g}] || (globalThis[${g}] = [])).push(${JSON.stringify(
          params
        )});`
    )
 
  return statements.length > 0 ? statements.join('\n') : undefined
}

Sources: packages/next/src/server/get-page-files.ts:30-47

Static Page Export Integration

Pages Export Execution Pipeline

The static export route handler coordinates prerendering for individual pages within the Pages router. When exportPagesPage processes a page component, it verifies that the component does not define getServerSideProps, throwing an error if server-side props are declared during export. Non-dynamic static pages with getStaticProps skip export rendering if build-time exports have already prerendered them, whereas dynamic routes and auto-exported components proceed through the rendering lifecycle.

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

Warning

Defining getServerSideProps on a page during a static export invocation triggers an immediate error, as server-side execution requires a live Node.js origin server.

if (components.getServerSideProps) {
  throw new Error(`Error for page ${page}: ${SERVER_PROPS_EXPORT_ERROR}`)
}

Sources: packages/next/src/export/routes/pages.ts:50-52

Path parameters and search parameters are merged into searchAndDynamicParams to satisfy useRouter().query requirements. If optimizeCss is enabled within render options, process.env.__NEXT_OPTIMIZE_CSS is injected as a stringified boolean prior to invoking lazyRenderPagesPage.

const searchAndDynamicParams = {
  ...query,
  ...params,
}

Sources: packages/next/src/export/routes/pages.ts:64-67

if (renderOpts.optimizeCss) {
  process.env.__NEXT_OPTIMIZE_CSS = JSON.stringify(true)
}

Sources: packages/next/src/export/routes/pages.ts:94-96

File Writer Integration and Metadata Handling

Once the render result is obtained, output files are committed to disk via the multi-file writer. If metadata contains page data and the export did not result in a notFound status, a companion JSON data file corresponding to NEXT_DATA_SUFFIX is appended to the pages data directory.

const ssgNotFound = renderResult?.metadata.isNotFound
 
const html =
  renderResult && !renderResult.isNull ? renderResult.toUnchunkedString() : ''
 
const metadata = renderResult?.metadata || {}
if (metadata.pageData && !ssgNotFound) {
  const dataFile = join(
    pagesDataDir,
    htmlFilename.replace(/\.html$/, NEXT_DATA_SUFFIX)
  )
 
  fileWriter.append(dataFile, JSON.stringify(metadata.pageData))
}

Sources: packages/next/src/export/routes/pages.ts:112-125

Metadata PropertyTarget File ExtensionDescription
HTML Content.htmlPrerendered page markup string
pageData.json (_next/data/...)Serialized getStaticProps payload
routeCache.metaCached route metadata entries

Sources: packages/next/src/export/routes/pages.ts:114-135

Caution

If getStaticProps returns notFound: true, the exporter skips writing both the HTML markup file and the page data JSON file to disk entirely.

if (!ssgNotFound) {
  // don't attempt writing to disk if getStaticProps returned not found
  fileWriter.append(htmlFilepath, html)
  if (routeCache) {
    fileWriter.append(
      htmlFilepath.replace(/\.html$/, '.meta'),
      JSON.stringify({ routeCache })
    )
  }
}

Sources: packages/next/src/export/routes/pages.ts:127-136

Was this page helpful?