Relevant source files
The following files were used as context for generating this wiki page:
- packages/next/src/server/render.tsx
- packages/next/src/server/base-server.ts
- packages/next/src/pages/_document.tsx
- packages/next/src/client/index.tsx
- packages/next/src/server/route-modules/pages/pages-handler.ts
- packages/next/src/build/templates/app-page-runtime.ts
- packages/next/src/server/next-server.ts
- packages/next/src/server/load-components.ts
- packages/next/src/shared/lib/router/router.ts
- packages/next/src/build/templates/pages.ts
- packages/next/src/export/worker.ts
- packages/next/src/pages/_app.tsx
- packages/next/src/export/routes/pages.ts
- packages/next-codemod/transforms/cra-to-next.ts
- packages/next/src/server/route-modules/pages/module.ts
- packages/next/src/server/render-result.ts
- packages/next/src/server/get-page-files.ts
- packages/next/src/shared/lib/page-path/denormalize-page-path.ts
- packages/next/src/shared/lib/page-path/normalize-page-path.ts
- packages/next/src/shared/lib/page-path/normalize-path-sep.ts
- packages/next/src/shared/lib/router/utils/interception-routes.ts
- packages/next/src/shared/lib/router/utils/is-dynamic.ts
- packages/next/src/shared/lib/utils.ts
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 Identifier | Source Userland Symbol | Purpose |
|---|---|---|
default | userland['default'] | The main React page component |
getStaticProps | userland['getStaticProps'] | Static generation data fetching hook |
getStaticPaths | userland['getStaticPaths'] | Dynamic route paths enumeration for SSG |
getServerSideProps | userland['getServerSideProps'] | Server-side rendering data fetching hook |
config | userland['config'] | Page-specific configuration options |
reportWebVitals | userland['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 Constant | File Name / Suffix | Loading Function / Behavior |
|---|---|---|
BUILD_MANIFEST | build-manifest.json | loadManifestWithRetries |
REACT_LOADABLE_MANIFEST | react-loadable-manifest.json | tryLoadManifestWithRetries |
DYNAMIC_CSS_MANIFEST | dynamic-css-manifest.json | loadManifestWithRetries (Pages dir + Webpack + Prod) |
SERVER_REFERENCE_MANIFEST | server-reference-manifest.json | loadManifestWithRetries (App path only) |
SUBRESOURCE_INTEGRITY_MANIFEST | subresource-integrity-manifest.json | loadManifestWithRetries (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 Path | Transformed 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 / Check | Behavior in loadGetInitialProps |
|---|---|
App.prototype.getInitialProps (Dev-only) | Throws an error instructing the user to remove instance-level definition. |
Missing App.getInitialProps with nested ctx.Component | Recursively loads initial props from ctx.Component using ctx.ctx. |
Missing App.getInitialProps without nested component | Returns an empty object ({} as IP). |
Resolved props evaluate to null or undefined | Throws 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 Property | Target File Extension | Description |
|---|---|---|
| HTML Content | .html | Prerendered page markup string |
pageData | .json (_next/data/...) | Serialized getStaticProps payload |
routeCache | .meta | Cached 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
Related
On This Page
OverviewPages Route Module ArchitectureRoute Module Layer and Request DispatchingRe-exports and Lifecycle Data MethodsComponent Loading and Assets ResolutionComponent and Document Loading ImplementationManifest Types and Resolution PathsBuild Manifest and Page File NormalizationData Fetching Execution LifecyclesCustom App Initial Props DelegationComponent Tree Composition FlowAssembling the Custom App and Page HierarchyDocument Shell and Script InjectionDocument Lifecycle and InitializationHtmlContext and Document Component ElementsScript Injection and Hydration TagsStatic Page Export IntegrationPages Export Execution PipelineFile Writer Integration and Metadata HandlingRelated