Cleanup Render Result (#58782)

This improves some of the typings around the `RenderResult` returned during renders. Previously it had a single large metadata object that was shared across both the pages and app render pipelines. To add more type safety, this splits the types used by each of the render pipelines into their own types while still allowing the default `RenderResult` to reference metadata from either render pipeline.

This also improved the flight data generation for app renders. Previously, the promise was inlined, and errors were swallowed. With the advent of improvements in #58779 the postpone errors are no longer returned by the flight generation and are instead handled correctly inside the render by React so it can emit properly postponed flight data.

Besides that there was some whitespace changes, so hiding whitespace differences during review should make it much clearer to review!
This commit is contained in:
Wyatt Johnson 2023-11-28 09:10:38 -07:00 committed by GitHub
parent 8d1c619ad6
commit f4c14935aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 285 additions and 203 deletions

View file

@ -1127,10 +1127,16 @@ export async function buildStaticPaths({
}
}
export type AppConfigDynamic =
| 'auto'
| 'error'
| 'force-static'
| 'force-dynamic'
export type AppConfig = {
revalidate?: number | false
dynamicParams?: true | false
dynamic?: 'auto' | 'error' | 'force-static' | 'force-dynamic'
dynamic?: AppConfigDynamic
fetchCache?: 'force-cache' | 'only-cache'
preferredRegion?: string
}

View file

@ -2,6 +2,8 @@ import type { AsyncLocalStorage } from 'async_hooks'
import type { IncrementalCache } from '../../server/lib/incremental-cache'
import type { DynamicServerError } from './hooks-server-context'
import type { FetchMetrics } from '../../server/base-http'
import type { Revalidate } from '../../server/lib/revalidate'
import { createAsyncLocalStorage } from './async-local-storage'
export interface StaticGenerationStore {
@ -23,7 +25,7 @@ export interface StaticGenerationStore {
| 'default-no-store'
| 'only-no-store'
revalidate?: false | number
revalidate?: Revalidate
forceStatic?: boolean
dynamicShouldError?: boolean
pendingRevalidates?: Record<string, Promise<any>>

View file

@ -1,3 +1,5 @@
import type { AppConfigDynamic } from '../../build/utils'
import { DynamicServerError } from './hooks-server-context'
import { maybePostpone } from './maybe-postpone'
import { staticGenerationAsyncStorage } from './static-generation-async-storage.external'
@ -6,7 +8,7 @@ class StaticGenBailoutError extends Error {
code = 'NEXT_STATIC_GEN_BAILOUT'
}
type BailoutOpts = { dynamic?: string; link?: string }
type BailoutOpts = { dynamic?: AppConfigDynamic; link?: string }
export type StaticGenerationBailout = (
reason: string,
@ -23,7 +25,7 @@ function formatErrorMessage(reason: string, opts?: BailoutOpts) {
export const staticGenerationBailout: StaticGenerationBailout = (
reason,
opts
{ dynamic, link } = {}
) => {
const staticGenerationStore = staticGenerationAsyncStorage.getStore()
if (!staticGenerationStore) return false
@ -34,12 +36,12 @@ export const staticGenerationBailout: StaticGenerationBailout = (
if (staticGenerationStore.dynamicShouldError) {
throw new StaticGenBailoutError(
formatErrorMessage(reason, { ...opts, dynamic: opts?.dynamic ?? 'error' })
formatErrorMessage(reason, { link, dynamic: dynamic ?? 'error' })
)
}
const message = formatErrorMessage(reason, {
...opts,
dynamic,
// this error should be caught by Next to bail out of static generation
// in case it's uncaught, this link provides some additional context as to why
link: 'https://nextjs.org/docs/messages/dynamic-server-error',
@ -51,7 +53,7 @@ export const staticGenerationBailout: StaticGenerationBailout = (
// to 0.
staticGenerationStore.revalidate = 0
if (!opts?.dynamic) {
if (!dynamic) {
// we can statically prefetch pages that opt into dynamic,
// but not things like headers/cookies
staticGenerationStore.staticPrefetchBailout = true

View file

@ -25,6 +25,7 @@ import { lazyRenderAppPage } from '../../server/future/route-modules/app-page/mo
export const enum ExportedAppPageFiles {
HTML = 'HTML',
FLIGHT = 'FLIGHT',
PREFETCH_FLIGHT = 'PREFETCH_FLIGHT',
META = 'META',
POSTPONED = 'POSTPONED',
}
@ -128,10 +129,8 @@ export async function exportAppPage(
const html = result.toUnchunkedString()
const {
metadata: { pageData, revalidate = false, postponed, fetchTags },
} = result
const { metadata } = result
const { flightData, revalidate = false, postponed, fetchTags } = metadata
// Ensure we don't postpone without having PPR enabled.
if (postponed && !renderOpts.experimental.ppr) {
@ -169,6 +168,11 @@ export async function exportAppPage(
return { revalidate: 0 }
}
// If page data isn't available, it means that the page couldn't be rendered
// properly.
else if (!flightData) {
throw new Error(`Invariant: failed to get page data for ${path}`)
}
// If PPR is enabled, we want to emit a prefetch rsc file for the page
// instead of the standard rsc. This is because the standard rsc will
// contain the dynamic data.
@ -176,16 +180,16 @@ export async function exportAppPage(
// If PPR is enabled, we should emit the flight data as the prefetch
// payload.
await fileWriter(
ExportedAppPageFiles.FLIGHT,
ExportedAppPageFiles.PREFETCH_FLIGHT,
htmlFilepath.replace(/\.html$/, RSC_PREFETCH_SUFFIX),
pageData
flightData
)
} else {
// Writing the RSC payload to a file if we don't have PPR enabled.
await fileWriter(
ExportedAppPageFiles.FLIGHT,
htmlFilepath.replace(/\.html$/, RSC_SUFFIX),
pageData
flightData
)
}

View file

@ -210,7 +210,8 @@ async function createRedirectRenderResult(
console.error(`failed to get redirect response`, err)
}
}
return new RenderResult(JSON.stringify({}))
return RenderResult.fromStatic('{}')
}
// Used to compare Host header and Origin header.
@ -607,7 +608,7 @@ To configure the body size limit for Server Actions, see: https://nextjs.org/doc
res.statusCode = 303
return {
type: 'done',
result: new RenderResult(''),
result: RenderResult.fromStatic(''),
}
} else if (isNotFoundError(err)) {
res.statusCode = 404

View file

@ -15,6 +15,7 @@ import type { NextParsedUrlQuery } from '../request-meta'
import type { LoaderTree } from '../lib/app-dir-module'
import type { AppPageModule } from '../future/route-modules/app-page/module'
import type { ClientReferenceManifest } from '../../build/webpack/plugins/flight-manifest-plugin'
import type { Revalidate } from '../lib/revalidate'
import React from 'react'
@ -22,7 +23,11 @@ import {
createServerComponentRenderer,
type ServerComponentRendererOptions,
} from './create-server-components-renderer'
import RenderResult, { type RenderResultMetadata } from '../render-result'
import RenderResult, {
type AppPageRenderResultMetadata,
type RenderResultOptions,
type RenderResultResponse,
} from '../render-result'
import {
renderToInitialFizzStream,
continueFizzStream,
@ -106,7 +111,7 @@ export type AppRenderContext = AppRenderBaseContext & {
appUsingSizeAdjustment: boolean
providedFlightRouterState?: FlightRouterState
requestId: string
defaultRevalidate: StaticGenerationStore['revalidate']
defaultRevalidate: Revalidate
pagePath: string
clientReferenceManifest: ClientReferenceManifest
assetPrefix: string
@ -303,6 +308,34 @@ async function generateFlight(
return new FlightRenderResult(flightReadableStream)
}
/**
* Creates a resolver that eagerly generates a flight payload that is then
* resolved when the resolver is called.
*/
function createFlightDataResolver(ctx: AppRenderContext) {
// Generate the flight data and as soon as it can, convert it into a string.
const promise = generateFlight(ctx)
.then(async (result) => ({
flightData: await result.toUnchunkedString(true),
}))
// Otherwise if it errored, return the error.
.catch((err) => ({ err }))
return async () => {
// Resolve the promise to get the flight data or error.
const result = await promise
// If the flight data failed to render due to an error, re-throw the error
// here.
if ('err' in result) {
throw result.err
}
// Otherwise, return the flight data.
return result.flightData
}
}
type ServerComponentsRendererOptions = {
ctx: AppRenderContext
preinitScripts: () => void
@ -430,7 +463,7 @@ async function renderToHTMLOrFlightImpl(
globalThis.__next_chunk_load__ = ComponentMod.__next_app__.loadChunk
}
const extraRenderResultMeta: RenderResultMetadata = {}
const metadata: AppPageRenderResultMetadata = {}
const appUsingSizeAdjustment = !!nextFontManifest?.appUsingSizeAdjust
@ -469,7 +502,7 @@ async function renderToHTMLOrFlightImpl(
const allCapturedErrors: Error[] = []
const isNextExport = !!renderOpts.nextExport
const { staticGenerationStore, requestStore } = baseCtx
const isStaticGeneration = staticGenerationStore.isStaticGeneration
const { isStaticGeneration } = staticGenerationStore
// when static generation fails during PPR, we log the errors separately. We intentionally
// silence the error logger in this case to avoid double logging.
const silenceStaticGenerationErrors =
@ -537,7 +570,7 @@ async function renderToHTMLOrFlightImpl(
const { urlPathname } = staticGenerationStore
staticGenerationStore.fetchMetrics = []
extraRenderResultMeta.fetchMetrics = staticGenerationStore.fetchMetrics
metadata.fetchMetrics = staticGenerationStore.fetchMetrics
// don't modify original query object
query = { ...query }
@ -615,11 +648,14 @@ async function renderToHTMLOrFlightImpl(
const hasPostponed = typeof renderOpts.postponed === 'string'
let stringifiedFlightPayloadPromise = isStaticGeneration
? generateFlight(ctx)
.then((renderResult) => renderResult.toUnchunkedString(true))
.catch(() => null)
: Promise.resolve(null)
// Create the resolver that can get the flight payload when it's ready or
// throw the error if it occurred. If we are not generating static HTML, we
// don't need to generate the flight payload because it's a dynamic request
// which means we're either getting the flight payload only or just the
// regular HTML.
const flightDataResolver = isStaticGeneration
? createFlightDataResolver(ctx)
: null
// Get the nonce from the incoming request if it has one.
const csp = req.headers['content-security-policy']
@ -754,8 +790,8 @@ async function renderToHTMLOrFlightImpl(
// result.
if (isStaticGeneration) {
headers.forEach((value, key) => {
extraRenderResultMeta.headers ??= {}
extraRenderResultMeta.headers[key] = value
metadata.headers ??= {}
metadata.headers[key] = value
})
// Resolve the promise to continue the stream.
@ -779,7 +815,7 @@ async function renderToHTMLOrFlightImpl(
// If the stream was postponed, we need to add the result to the
// metadata so that it can be resumed later.
if (postponed) {
extraRenderResultMeta.postponed = JSON.stringify(postponed)
metadata.postponed = JSON.stringify(postponed)
// We don't need to "continue" this stream now as it's continued when
// we resume the stream.
@ -789,8 +825,7 @@ async function renderToHTMLOrFlightImpl(
const options: ContinueStreamOptions = {
inlinedDataStream:
serverComponentsRenderOpts.inlinedDataTransformStream.readable,
generateStaticHTML:
staticGenerationStore.isStaticGeneration || generateStaticHTML,
isStaticGeneration: isStaticGeneration || generateStaticHTML,
getServerInsertedHTML: () => getServerInsertedHTML(allCapturedErrors),
serverInsertedHTMLToHead: !renderOpts.postponed,
// If this render generated a postponed state or this is a resume
@ -968,7 +1003,7 @@ async function renderToHTMLOrFlightImpl(
inlinedDataStream:
serverErrorComponentsRenderOpts.inlinedDataTransformStream
.readable,
generateStaticHTML: staticGenerationStore.isStaticGeneration,
isStaticGeneration,
getServerInsertedHTML: () => getServerInsertedHTML([]),
serverInsertedHTMLToHead: true,
validateRootLayout,
@ -1012,11 +1047,11 @@ async function renderToHTMLOrFlightImpl(
tree: notFoundLoaderTree,
formState,
}),
{ ...extraRenderResultMeta }
{ metadata }
)
} else if (actionRequestResult.type === 'done') {
if (actionRequestResult.result) {
actionRequestResult.result.extendMetadata(extraRenderResultMeta)
actionRequestResult.result.assignMetadata(metadata)
return actionRequestResult.result
} else if (actionRequestResult.formState) {
formState = actionRequestResult.formState
@ -1024,34 +1059,40 @@ async function renderToHTMLOrFlightImpl(
}
}
const renderResult = new RenderResult(
await renderToStream({
const options: RenderResultOptions = {
metadata,
}
let response: RenderResultResponse = await renderToStream({
asNotFound: isNotFoundPath,
tree: loaderTree,
formState,
}),
{
...extraRenderResultMeta,
// Wait for and collect the flight payload data if we don't have it
// already.
pageData: await stringifiedFlightPayloadPromise,
// If we have pending revalidates, wait until they are all resolved.
waitUntil: staticGenerationStore.pendingRevalidates
? Promise.all(Object.values(staticGenerationStore.pendingRevalidates))
: undefined,
}
)
addImplicitTags(staticGenerationStore)
extraRenderResultMeta.fetchTags = staticGenerationStore.tags?.join(',')
renderResult.extendMetadata({
fetchTags: extraRenderResultMeta.fetchTags,
})
if (staticGenerationStore.isStaticGeneration) {
// Collect the entire render result to a string (by streaming it to a
// string).
const htmlResult = await renderResult.toUnchunkedString(true)
// If we have pending revalidates, wait until they are all resolved.
if (staticGenerationStore.pendingRevalidates) {
options.waitUntil = Promise.all(
Object.values(staticGenerationStore.pendingRevalidates)
)
}
addImplicitTags(staticGenerationStore)
if (staticGenerationStore.tags) {
metadata.fetchTags = staticGenerationStore.tags.join(',')
}
// Create the new render result for the response.
const result = new RenderResult(response, options)
// If we aren't performing static generation, we can return the result now.
if (!isStaticGeneration) {
return result
}
// If this is static generation, we should read this in now rather than
// sending it back to be sent to the client.
response = await result.toUnchunkedString(true)
// Timeout after 1.5 seconds for the headers to write. If it takes
// longer than this it's more likely that the stream has stalled and
@ -1079,7 +1120,7 @@ async function renderToHTMLOrFlightImpl(
// and a call to `maybePostpone` happened
staticGenerationStore.postponeWasTriggered &&
// but there's no postpone state
!extraRenderResultMeta.postponed
!metadata.postponed
) {
// a call to postpone was made but was caught and not detected by Next.js. We should fail the build immediately
// as we won't be able to generate the static part
@ -1104,34 +1145,44 @@ async function renderToHTMLOrFlightImpl(
)
}
// if we encountered any unexpected errors during build
// we fail the prerendering phase and the build
if (!flightDataResolver) {
throw new Error(
'Invariant: Flight data resolver is missing when generating static HTML'
)
}
// If we encountered any unexpected errors during build we fail the
// prerendering phase and the build.
if (capturedErrors.length > 0) {
throw capturedErrors[0]
}
// Wait for and collect the flight payload data if we don't have it
// already
const flightData = await flightDataResolver()
if (flightData) {
metadata.flightData = flightData
}
// If force static is specifically set to false, we should not revalidate
// the page.
if (staticGenerationStore.forceStatic === false) {
staticGenerationStore.revalidate = 0
}
// TODO-APP: derive this from same pass to prevent additional
// render during static generation
extraRenderResultMeta.pageData = await stringifiedFlightPayloadPromise
extraRenderResultMeta.revalidate =
// Copy the revalidation value onto the render result metadata.
metadata.revalidate =
staticGenerationStore.revalidate ?? ctx.defaultRevalidate
// provide bailout info for debugging
if (extraRenderResultMeta.revalidate === 0) {
extraRenderResultMeta.staticBailoutInfo = {
if (metadata.revalidate === 0) {
metadata.staticBailoutInfo = {
description: staticGenerationStore.dynamicUsageDescription,
stack: staticGenerationStore.dynamicUsageStack,
}
}
return new RenderResult(htmlResult, { ...extraRenderResultMeta })
}
return renderResult
return new RenderResult(response, options)
}
export type AppPageRender = (
@ -1140,7 +1191,7 @@ export type AppPageRender = (
pagePath: string,
query: NextParsedUrlQuery,
renderOpts: RenderOpts
) => Promise<RenderResult>
) => Promise<RenderResult<AppPageRenderResultMetadata>>
export const renderToHTMLOrFlight: AppPageRender = (
req,

View file

@ -6,6 +6,6 @@ import RenderResult from '../render-result'
*/
export class FlightRenderResult extends RenderResult {
constructor(response: string | ReadableStream<Uint8Array>) {
super(response, { contentType: RSC_CONTENT_TYPE_HEADER })
super(response, { contentType: RSC_CONTENT_TYPE_HEADER, metadata: {} })
}
}

View file

@ -2406,7 +2406,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
// should return.
// Handle `isNotFound`.
if (metadata.isNotFound) {
if ('isNotFound' in metadata && metadata.isNotFound) {
return { value: null, revalidate: metadata.revalidate }
}
@ -2415,7 +2415,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
return {
value: {
kind: 'REDIRECT',
props: metadata.pageData,
props: metadata.pageData ?? metadata.flightData,
},
revalidate: metadata.revalidate,
}
@ -2431,7 +2431,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
value: {
kind: 'PAGE',
html: result,
pageData: metadata.pageData,
pageData: metadata.pageData ?? metadata.flightData,
postponed: metadata.postponed,
headers,
status: isAppPath ? res.statusCode : undefined,
@ -3224,7 +3224,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
if (this.renderOpts.dev && ctx.pathname === '/favicon.ico') {
return {
type: 'html',
body: new RenderResult(''),
body: RenderResult.fromStatic(''),
}
}
const { res, query } = ctx

View file

@ -1,6 +1,6 @@
import type { OutgoingHttpHeaders, ServerResponse } from 'http'
import type { StaticGenerationStore } from '../client/components/static-generation-async-storage.external'
import type { Revalidate } from './lib/revalidate'
import type { FetchMetrics } from './base-http'
import {
chainStreams,
@ -11,38 +11,58 @@ import { isAbortError, pipeToNodeResponse } from './pipe-readable'
type ContentTypeOption = string | undefined
export type RenderResultMetadata = {
pageData?: any
export type AppPageRenderResultMetadata = {
flightData?: string
revalidate?: Revalidate
staticBailoutInfo?: {
stack?: string
description?: string
}
assetQueryString?: string
isNotFound?: boolean
isRedirect?: boolean
fetchMetrics?: StaticGenerationStore['fetchMetrics']
fetchTags?: string
waitUntil?: Promise<any>
/**
* The headers to set on the response that were added by the render.
*/
headers?: OutgoingHttpHeaders
/**
* The postponed state if the render had postponed and needs to be resumed.
*/
postponed?: string
/**
* The headers to set on the response that were added by the render.
*/
headers?: OutgoingHttpHeaders
fetchTags?: string
fetchMetrics?: FetchMetrics
}
type RenderResultResponse =
export type PagesRenderResultMetadata = {
pageData?: any
revalidate?: Revalidate
assetQueryString?: string
isNotFound?: boolean
isRedirect?: boolean
}
export type StaticRenderResultMetadata = {}
export type RenderResultMetadata = AppPageRenderResultMetadata &
PagesRenderResultMetadata &
StaticRenderResultMetadata
export type RenderResultResponse =
| ReadableStream<Uint8Array>[]
| ReadableStream<Uint8Array>
| string
| null
export default class RenderResult {
export type RenderResultOptions<
Metadata extends RenderResultMetadata = RenderResultMetadata
> = {
contentType?: ContentTypeOption
waitUntil?: Promise<unknown>
metadata: Metadata
}
export default class RenderResult<
Metadata extends RenderResultMetadata = RenderResultMetadata
> {
/**
* The detected content type for the response. This is used to set the
* `Content-Type` header.
@ -53,7 +73,7 @@ export default class RenderResult {
* The metadata for the response. This is used to set the revalidation times
* and other metadata.
*/
public readonly metadata: RenderResultMetadata
public readonly metadata: Readonly<Metadata>
/**
* The response itself. This can be a string, a stream, or null. If it's a
@ -69,21 +89,15 @@ export default class RenderResult {
* @param value the static response value
* @returns a new RenderResult instance
*/
public static fromStatic(value: string): RenderResult {
return new RenderResult(value)
public static fromStatic(value: string) {
return new RenderResult<StaticRenderResultMetadata>(value, { metadata: {} })
}
private waitUntil?: Promise<void>
private readonly waitUntil?: Promise<unknown>
constructor(
response: RenderResultResponse,
{
contentType,
waitUntil,
...metadata
}: {
contentType?: ContentTypeOption
} & RenderResultMetadata = {}
{ contentType, waitUntil, metadata }: RenderResultOptions<Metadata>
) {
this.response = response
this.contentType = contentType
@ -91,7 +105,7 @@ export default class RenderResult {
this.waitUntil = waitUntil
}
public extendMetadata(metadata: RenderResultMetadata) {
public assignMetadata(metadata: Metadata) {
Object.assign(this.metadata, metadata)
}

View file

@ -78,7 +78,7 @@ import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'
import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-path'
import { getRequestMeta } from './request-meta'
import { allowedStatusCodes, getRedirectStatus } from '../lib/redirect-status'
import RenderResult, { type RenderResultMetadata } from './render-result'
import RenderResult, { type PagesRenderResultMetadata } from './render-result'
import isError from '../lib/is-error'
import {
streamFromString,
@ -414,20 +414,20 @@ export async function renderToHTMLImpl(
// Adds support for reading `cookies` in `getServerSideProps` when SSR.
setLazyProp({ req: req as any }, 'cookies', getCookieParser(req.headers))
const renderResultMeta: RenderResultMetadata = {}
const metadata: PagesRenderResultMetadata = {}
// In dev we invalidate the cache by appending a timestamp to the resource URL.
// This is a workaround to fix https://github.com/vercel/next.js/issues/5860
// TODO: remove this workaround when https://bugs.webkit.org/show_bug.cgi?id=187726 is fixed.
renderResultMeta.assetQueryString = renderOpts.dev
metadata.assetQueryString = renderOpts.dev
? renderOpts.assetQueryString || `?ts=${Date.now()}`
: ''
// if deploymentId is provided we append it to all asset requests
if (renderOpts.deploymentId) {
renderResultMeta.assetQueryString += `${
renderResultMeta.assetQueryString ? '&' : '?'
}dpl=${renderOpts.deploymentId}`
metadata.assetQueryString += `${metadata.assetQueryString ? '&' : '?'}dpl=${
renderOpts.deploymentId
}`
}
// don't modify original query object
@ -454,7 +454,7 @@ export async function renderToHTMLImpl(
} = renderOpts
const { App } = extra
const assetQueryString = renderResultMeta.assetQueryString
const assetQueryString = metadata.assetQueryString
let Document = extra.Document
@ -892,7 +892,7 @@ export async function renderToHTMLImpl(
)
}
renderResultMeta.isNotFound = true
metadata.isNotFound = true
}
if (
@ -916,12 +916,12 @@ export async function renderToHTMLImpl(
if (typeof data.redirect.basePath !== 'undefined') {
;(data as any).props.__N_REDIRECT_BASE_PATH = data.redirect.basePath
}
renderResultMeta.isRedirect = true
metadata.isRedirect = true
}
if (
(dev || isBuildTimeSSG) &&
!renderResultMeta.isNotFound &&
!metadata.isNotFound &&
!isSerializableProps(pathname, 'getStaticProps', (data as any).props)
) {
// this fn should throw an error instead of ever returning `false`
@ -992,12 +992,12 @@ export async function renderToHTMLImpl(
)
// pass up revalidate and props for export
renderResultMeta.revalidate = revalidate
renderResultMeta.pageData = props
metadata.revalidate = revalidate
metadata.pageData = props
// this must come after revalidate is added to renderResultMeta
if (renderResultMeta.isNotFound) {
return new RenderResult(null, renderResultMeta)
if (metadata.isNotFound) {
return new RenderResult(null, { metadata })
}
}
@ -1110,8 +1110,8 @@ export async function renderToHTMLImpl(
)
}
renderResultMeta.isNotFound = true
return new RenderResult(null, renderResultMeta)
metadata.isNotFound = true
return new RenderResult(null, { metadata })
}
if ('redirect' in data && typeof data.redirect === 'object') {
@ -1123,7 +1123,7 @@ export async function renderToHTMLImpl(
if (typeof data.redirect.basePath !== 'undefined') {
;(data as any).props.__N_REDIRECT_BASE_PATH = data.redirect.basePath
}
renderResultMeta.isRedirect = true
metadata.isRedirect = true
}
if (deferredContent) {
@ -1141,7 +1141,7 @@ export async function renderToHTMLImpl(
}
props.pageProps = Object.assign({}, props.pageProps, (data as any).props)
renderResultMeta.pageData = props
metadata.pageData = props
}
if (
@ -1158,8 +1158,10 @@ export async function renderToHTMLImpl(
// Avoid rendering page un-necessarily for getServerSideProps data request
// and getServerSideProps/getStaticProps redirects
if ((isDataReq && !isSSG) || renderResultMeta.isRedirect) {
return new RenderResult(JSON.stringify(props), renderResultMeta)
if ((isDataReq && !isSSG) || metadata.isRedirect) {
return new RenderResult(JSON.stringify(props), {
metadata,
})
}
// We don't call getStaticProps or getServerSideProps while generating
@ -1169,7 +1171,7 @@ export async function renderToHTMLImpl(
}
// the response might be finished on the getInitialProps call
if (isResSent(res) && !isSSG) return new RenderResult(null, renderResultMeta)
if (isResSent(res) && !isSSG) return new RenderResult(null, { metadata })
// we preload the buildManifest for auto-export dynamic pages
// to speed up hydrating query values
@ -1333,7 +1335,7 @@ export async function renderToHTMLImpl(
return continueFizzStream(initialStream, {
suffix,
inlinedDataStream: serverComponentsInlinedTransformStream?.readable,
generateStaticHTML: true,
isStaticGeneration: true,
// this must be called inside bodyResult so appWrappers is
// up to date when `wrapApp` is called
getServerInsertedHTML: () => {
@ -1408,7 +1410,7 @@ export async function renderToHTMLImpl(
async () => renderDocument()
)
if (!documentResult) {
return new RenderResult(null, renderResultMeta)
return new RenderResult(null, { metadata })
}
const dynamicImportsIds = new Set<string | number>()
@ -1567,7 +1569,7 @@ export async function renderToHTMLImpl(
hybridAmp,
})
return new RenderResult(optimizedHtml, renderResultMeta)
return new RenderResult(optimizedHtml, { metadata })
}
export type PagesRender = (

View file

@ -442,7 +442,7 @@ function chainTransformers<T>(
export type ContinueStreamOptions = {
inlinedDataStream: ReadableStream<Uint8Array> | undefined
generateStaticHTML: boolean
isStaticGeneration: boolean
getServerInsertedHTML: (() => Promise<string>) | undefined
serverInsertedHTMLToHead: boolean
validateRootLayout:
@ -462,7 +462,7 @@ export async function continueFizzStream(
{
suffix,
inlinedDataStream,
generateStaticHTML,
isStaticGeneration,
getServerInsertedHTML,
serverInsertedHTMLToHead,
validateRootLayout,
@ -475,7 +475,7 @@ export async function continueFizzStream(
// If we're generating static HTML and there's an `allReady` promise on the
// stream, we need to wait for it to resolve before continuing.
if (generateStaticHTML && 'allReady' in renderStream) {
if (isStaticGeneration && 'allReady' in renderStream) {
await renderStream.allReady
}
@ -518,7 +518,7 @@ export async function continueFizzStream(
type ContinuePostponedStreamOptions = Pick<
ContinueStreamOptions,
| 'inlinedDataStream'
| 'generateStaticHTML'
| 'isStaticGeneration'
| 'getServerInsertedHTML'
| 'serverInsertedHTMLToHead'
>
@ -527,7 +527,7 @@ export async function continuePostponedFizzStream(
renderStream: ReactReadableStream,
{
inlinedDataStream,
generateStaticHTML,
isStaticGeneration,
getServerInsertedHTML,
serverInsertedHTMLToHead,
}: ContinuePostponedStreamOptions
@ -536,7 +536,7 @@ export async function continuePostponedFizzStream(
// If we're generating static HTML and there's an `allReady` promise on the
// stream, we need to wait for it to resolve before continuing.
if (generateStaticHTML && 'allReady' in renderStream) {
if (isStaticGeneration && 'allReady' in renderStream) {
await renderStream.allReady
}