rsnext/packages/next/server/render.tsx

1678 lines
51 KiB
TypeScript
Raw Normal View History

import type { IncomingMessage, ServerResponse } from 'http'
import type { ParsedUrlQuery } from 'querystring'
import type { NextRouter } from '../shared/lib/router/router'
import type { HtmlProps } from '../shared/lib/html-context'
import type { DomainLocale } from './config'
import type {
AppType,
DocumentInitialProps,
DocumentType,
DocumentProps,
DocumentContext,
NextComponentType,
RenderPage,
RenderPageResult,
} from '../shared/lib/utils'
import type { ImageConfigComplete } from '../shared/lib/image-config'
import type { Redirect } from '../lib/load-custom-routes'
import type { NextApiRequestCookies, __ApiPreviewProps } from './api-utils'
import type { FontManifest } from './font-utils'
import type { LoadComponentsReturnType, ManifestItem } from './load-components'
import type { GetServerSideProps, GetStaticProps, PreviewData } from '../types'
import type { UnwrapPromise } from '../lib/coalesced-function'
import type { ReactReadableStream } from './node-web-streams-helper'
import React from 'react'
import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-webpack'
import { renderToReadableStream } from 'next/dist/compiled/react-server-dom-webpack/writer.browser.server'
import { StyleRegistry, createStyleRegistry } from 'styled-jsx'
import {
GSP_NO_RETURNED_VALUE,
GSSP_COMPONENT_MEMBER_ERROR,
GSSP_NO_RETURNED_VALUE,
STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR,
SERVER_PROPS_GET_INIT_PROPS_CONFLICT,
SERVER_PROPS_SSG_CONFLICT,
SSG_GET_INITIAL_PROPS_CONFLICT,
UNSTABLE_REVALIDATE_RENAME_ERROR,
} from '../lib/constants'
import {
NEXT_BUILTIN_DOCUMENT,
SERVER_PROPS_ID,
STATIC_PROPS_ID,
STATIC_STATUS_PAGES,
} from '../shared/lib/constants'
import { isSerializableProps } from '../lib/is-serializable-props'
import { isInAmpMode } from '../shared/lib/amp'
import { AmpStateContext } from '../shared/lib/amp-context'
import { defaultHead } from '../shared/lib/head'
import { HeadManagerContext } from '../shared/lib/head-manager-context'
import Loadable from '../shared/lib/loadable'
import { LoadableContext } from '../shared/lib/loadable-context'
import { RouterContext } from '../shared/lib/router-context'
import { isDynamicRoute } from '../shared/lib/router/utils/is-dynamic'
import {
ComponentsEnhancer,
getDisplayName,
isResSent,
loadGetInitialProps,
} from '../shared/lib/utils'
import { HtmlContext } from '../shared/lib/html-context'
Refactor Page Paths utils and Middleware Plugin (#36576) This PR brings some significant refactoring in preparation for upcoming middleware changes. Each commit can be reviewed independently, here is a summary of what each one does and the reasoning behind it: - [Move pagesDir to next-dev-server](https://github.com/javivelasco/next.js/pull/12/commits/f2fe154c007379f71c14960ddc553eaaaf786ffa) simply moves the `pagesDir` property to the dev server which is the only place where it is needed. Having it for every server is misleading. - [Move (de)normalize page path utils to a file page-path-utils.ts](https://github.com/javivelasco/next.js/pull/12/commits/27cedf087187b9632ef82a34b3af9cc4fe05d98b) Moves the functions to normalize and denormalize page paths to a single file that is intended to hold every utility function that transforms page paths. Since those are complementary it makes sense to have them together. I also added explanatory comments on why they are not idempotent and examples for input -> output that I find very useful. - [Extract removePagePathTail](https://github.com/javivelasco/next.js/pull/12/commits/6b121332aa9d3e50bd0f28b691fb7faea1b95f51) This extracts a function to remove the tail on a page path (absolute or relative). I'm sure there will be other contexts where we can use it. - [Extract getPagePaths and refactor findPageFile](https://github.com/javivelasco/next.js/pull/12/commits/cf2c7b842eebd8c02f23e79345681a794516b646) This extracts a function `getPagePaths` that is used to generate an array of paths to inspect when looking for a page file from `findPageFile`. Then it refactors such function to use it parallelizing lookups. This will allow us to print every path we look at when looking for a file which can be useful for debugging. It also adds a `flatten` helper. - [Refactor onDemandEntryHandler](https://github.com/javivelasco/next.js/pull/12/commits/4be685c37e3d1b797e929ea4f31495ed7b00e1cc) I've found this one quite difficult to understand so it is refactored to use some of the previously mentioned functions and make it easier to read. - [Extract absolutePagePath util](https://github.com/javivelasco/next.js/pull/12/commits/3bc078347426c73491a076d54ef4de977d9da073) Extracts yet another util from the `next-dev-server` that transforms an absolute path into a page name. Of course it adds comments, parameters and examples. - [Refactor MiddlewarePlugin](https://github.com/javivelasco/next.js/pull/12/commits/c595a2cc629b358cc61861a8a4848b7890d0a15b) This is the most significant change. The logic here was very hard to understand so it is totally redistributed with comments. This also removes a global variable `ssrEntries` that was deprecated in favour of module metadata added to Webpack from loaders keeping less dependencies. It also adds types and makes a clear distinction between phases where we statically analyze the code, find metadata and generate the manifest file cc @shuding @huozhi EDIT: - [Split page path utils](https://github.com/vercel/next.js/pull/36576/commits/158fb002d02887d7ce4be6747cf550a825a426eb) After seeing one of the utils was being used by the client while it was defined originally in the server, with this PR we are splitting the util into multiple files and moving it to `shared/lib` in order to make explicit that those can be also imported from client.
2022-04-30 13:19:27 +02:00
import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'
import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-path'
import { getRequestMeta, NextParsedUrlQuery } from './request-meta'
import {
allowedStatusCodes,
getRedirectStatus,
} from '../lib/load-custom-routes'
import RenderResult from './render-result'
import isError from '../lib/is-error'
import {
readableStreamTee,
encodeText,
decodeText,
streamFromArray,
streamToString,
chainStreams,
createBufferedTransformStream,
renderToInitialStream,
continueFromInitialStream,
} from './node-web-streams-helper'
import { ImageConfigContext } from '../shared/lib/image-config-context'
import stripAnsi from 'next/dist/compiled/strip-ansi'
import { urlQueryToSearchParams } from '../shared/lib/router/utils/querystring'
import { postProcessHTML } from './post-process'
import { htmlEscapeJsonString } from './htmlescape'
let tryGetPreviewData: typeof import('./api-utils/node').tryGetPreviewData
let warn: typeof import('../build/output/log').warn
const DOCTYPE = '<!DOCTYPE html>'
const ReactDOMServer = process.env.__NEXT_REACT_ROOT
? require('react-dom/server.browser')
: require('react-dom/server')
if (process.env.NEXT_RUNTIME !== 'edge') {
require('./node-polyfill-web-streams')
tryGetPreviewData = require('./api-utils/node').tryGetPreviewData
warn = require('../build/output/log').warn
} else {
warn = console.warn.bind(console)
}
function noRouter() {
const message =
'No router instance found. you should only use "next/router" inside the client side of your app. https://nextjs.org/docs/messages/no-router-instance'
throw new Error(message)
}
class ServerRouter implements NextRouter {
route: string
pathname: string
query: ParsedUrlQuery
asPath: string
basePath: string
events: any
isFallback: boolean
locale?: string
isReady: boolean
locales?: string[]
defaultLocale?: string
domainLocales?: DomainLocale[]
isPreview: boolean
isLocaleDomain: boolean
constructor(
pathname: string,
query: ParsedUrlQuery,
as: string,
{ isFallback }: { isFallback: boolean },
isReady: boolean,
basePath: string,
locale?: string,
locales?: string[],
defaultLocale?: string,
domainLocales?: DomainLocale[],
isPreview?: boolean,
isLocaleDomain?: boolean
) {
this.route = pathname.replace(/\/$/, '') || '/'
this.pathname = pathname
this.query = query
this.asPath = as
this.isFallback = isFallback
this.basePath = basePath
this.locale = locale
this.locales = locales
this.defaultLocale = defaultLocale
this.isReady = isReady
this.domainLocales = domainLocales
this.isPreview = !!isPreview
this.isLocaleDomain = !!isLocaleDomain
}
push(): any {
noRouter()
}
replace(): any {
noRouter()
}
reload() {
noRouter()
}
back() {
noRouter()
}
prefetch(): any {
noRouter()
}
beforePopState() {
noRouter()
}
}
function enhanceComponents(
options: ComponentsEnhancer,
App: AppType,
Component: NextComponentType
): {
App: AppType
Component: NextComponentType
} {
// For backwards compatibility
2019-02-08 11:57:29 +01:00
if (typeof options === 'function') {
return {
2019-02-08 11:57:29 +01:00
App,
Component: options(Component),
}
}
return {
App: options.enhanceApp ? options.enhanceApp(App) : App,
Component: options.enhanceComponent
? options.enhanceComponent(Component)
: Component,
}
}
function renderPageTree(
App: AppType,
Component: NextComponentType,
props: any
) {
return <App Component={Component} {...props} />
}
export type RenderOptsPartial = {
buildId: string
canonicalBase: string
runtimeConfig?: { [key: string]: any }
assetPrefix?: string
err?: Error | null
nextExport?: boolean
dev?: boolean
ampPath?: string
ErrorDebug?: React.ComponentType<{ error: Error }>
ampValidator?: (html: string, pathname: string) => Promise<void>
ampSkipValidation?: boolean
ampOptimizerConfig?: { [key: string]: any }
isDataReq?: boolean
params?: ParsedUrlQuery
previewProps: __ApiPreviewProps
basePath: string
unstable_runtimeJS?: false
unstable_JsPreload?: false
optimizeFonts: boolean
fontManifest?: FontManifest
optimizeCss: any
Adds web worker support to `<Script />` using Partytown (#34244) ## Summary This PR adds a new `worker` strategy to the `<Script />` component that automatically relocates and executes the script in a web worker. ```jsx <Script strategy="worker" ... /> ``` [Partytown](https://partytown.builder.io/) is used under the hood to provide this functionality. ## Behavior - This will land as an experimental feature and will only work behind an opt-in flag in `next.config.js`: ```js experimental: { nextScriptWorkers: true } ``` - This setup use a similar approach to how ESLint and Typescript is used in Next.js by showing an error to the user to install the dependency locally themselves if they've enabled the experimental `nextScriptWorkers` flag. <img width="1068" alt="Screen Shot 2022-03-03 at 2 33 13 PM" src="https://user-images.githubusercontent.com/12476932/156639227-42af5353-a2a6-4126-936e-269112809651.png"> - For Partytown to work, a number of static files must be served directly from the site (see [docs](https://partytown.builder.io/copy-library-files)). In this PR, these files are automatically copied to a `~partytown` directory in `.next/static` during `next build` and `next dev` if the `nextScriptWorkers` flag is set to true. ## Checklist - [X] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [X] Related issues linked using `fixes #number` - [X] Integration tests added - [X] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. This PR fixes #31517.
2022-03-11 23:26:46 +01:00
nextScriptWorkers: any
devOnlyCacheBusterQueryString?: string
resolvedUrl?: string
resolvedAsPath?: string
serverComponentManifest?: any
distDir?: string
locale?: string
locales?: string[]
defaultLocale?: string
domainLocales?: DomainLocale[]
disableOptimizedLoading?: boolean
supportsDynamicHTML?: boolean
runtime?: 'nodejs' | 'edge'
serverComponents?: boolean
customServer?: boolean
crossOrigin?: string
images: ImageConfigComplete
reactRoot: boolean
}
export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
const invalidKeysMsg = (
methodName: 'getServerSideProps' | 'getStaticProps',
invalidKeys: string[]
) => {
const docsPathname = `invalid-${methodName.toLocaleLowerCase()}-value`
return (
`Additional keys were returned from \`${methodName}\`. Properties intended for your component must be nested under the \`props\` key, e.g.:` +
`\n\n\treturn { props: { title: 'My Title', content: '...' } }` +
`\n\nKeys that need to be moved: ${invalidKeys.join(', ')}.` +
`\nRead more: https://nextjs.org/docs/messages/${docsPathname}`
)
}
function checkRedirectValues(
redirect: Redirect,
req: IncomingMessage,
method: 'getStaticProps' | 'getServerSideProps'
) {
const { destination, permanent, statusCode, basePath } = redirect
let errors: string[] = []
const hasStatusCode = typeof statusCode !== 'undefined'
const hasPermanent = typeof permanent !== 'undefined'
if (hasPermanent && hasStatusCode) {
errors.push(`\`permanent\` and \`statusCode\` can not both be provided`)
} else if (hasPermanent && typeof permanent !== 'boolean') {
errors.push(`\`permanent\` must be \`true\` or \`false\``)
} else if (hasStatusCode && !allowedStatusCodes.has(statusCode!)) {
errors.push(
`\`statusCode\` must undefined or one of ${[...allowedStatusCodes].join(
', '
)}`
)
}
const destinationType = typeof destination
if (destinationType !== 'string') {
errors.push(
`\`destination\` should be string but received ${destinationType}`
)
}
const basePathType = typeof basePath
if (basePathType !== 'undefined' && basePathType !== 'boolean') {
errors.push(
`\`basePath\` should be undefined or a false, received ${basePathType}`
)
}
if (errors.length > 0) {
throw new Error(
`Invalid redirect object returned from ${method} for ${req.url}\n` +
errors.join(' and ') +
'\n' +
`See more info here: https://nextjs.org/docs/messages/invalid-redirect-gssp`
)
}
}
const rscCache = new Map()
function useFlightResponse({
id,
req,
pageData,
inlinedDataWritable,
}: {
id: string
req: ReadableStream<Uint8Array>
pageData: { current: string } | null
inlinedDataWritable: WritableStream<Uint8Array>
}) {
let entry = rscCache.get(id)
if (!entry) {
const [renderStream, forwardStream] = readableStreamTee(req)
entry = createFromReadableStream(renderStream)
rscCache.set(id, entry)
let bootstrapped = false
const forwardReader = forwardStream.getReader()
const inlinedDataWriter = inlinedDataWritable.getWriter()
function process() {
forwardReader.read().then(({ done, value }) => {
if (!bootstrapped) {
bootstrapped = true
inlinedDataWriter.write(
encodeText(
`<script>(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString(
JSON.stringify([0, id])
)})</script>`
)
)
}
if (done) {
rscCache.delete(id)
inlinedDataWriter.close()
} else {
const decodedValue = decodeText(value)
inlinedDataWriter.write(
encodeText(
`<script>(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString(
JSON.stringify([1, id, decodedValue])
)})</script>`
)
)
if (pageData) {
pageData.current += decodedValue
}
process()
}
})
}
process()
}
return entry
}
// Create the wrapper component for a Flight stream.
function createServerComponentRenderer(
Component: any,
{
cachePrefix,
pageData,
inlinedTransformStream,
serverComponentManifest,
}: {
cachePrefix: string
pageData: { current: string } | null
inlinedTransformStream: TransformStream<Uint8Array, Uint8Array>
serverComponentManifest: NonNullable<RenderOpts['serverComponentManifest']>
}
) {
function ServerComponentWrapper({ router, ...props }: any) {
const id = (React as any).useId()
const reqStream: ReadableStream<Uint8Array> = renderToReadableStream(
<Component {...props} />,
serverComponentManifest
)
const response = useFlightResponse({
id: cachePrefix + ',' + id,
req: reqStream,
pageData,
inlinedDataWritable: inlinedTransformStream.writable,
})
const root = response.readRoot()
rscCache.delete(id)
return root
}
2022-04-14 16:35:09 +02:00
for (const methodName of Object.keys(Component)) {
const method = (Component as any)[methodName]
if (method) {
;(ServerComponentWrapper as any)[methodName] = method
}
}
return ServerComponentWrapper
}
export async function renderToHTML(
req: IncomingMessage,
res: ServerResponse,
pathname: string,
query: NextParsedUrlQuery,
renderOpts: RenderOpts
): Promise<RenderResult | null> {
// 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.
renderOpts.devOnlyCacheBusterQueryString = renderOpts.dev
? renderOpts.devOnlyCacheBusterQueryString || `?ts=${Date.now()}`
: ''
// don't modify original query object
query = Object.assign({}, query)
const {
err,
dev = false,
ampPath = '',
pageConfig = {},
buildManifest,
reactLoadableManifest,
2019-02-08 11:57:29 +01:00
ErrorDebug,
getStaticProps,
getStaticPaths,
getServerSideProps,
serverComponentManifest,
isDataReq,
params,
previewProps,
basePath,
devOnlyCacheBusterQueryString,
supportsDynamicHTML,
images,
runtime: globalRuntime,
ComponentMod,
App,
} = renderOpts
let Document = renderOpts.Document
// We don't need to opt-into the flight inlining logic if the page isn't a RSC.
const isServerComponent =
!!process.env.__NEXT_REACT_ROOT &&
!!serverComponentManifest &&
!!ComponentMod.__next_rsc__?.server
// Component will be wrapped by ServerComponentWrapper for RSC
let Component: React.ComponentType<{}> | ((props: any) => JSX.Element) =
renderOpts.Component
const OriginComponent = Component
let serverComponentsInlinedTransformStream: TransformStream<
Uint8Array,
Uint8Array
> | null = null
const serverComponentsStaticPageData: { current: string } | null =
isServerComponent && process.env.NEXT_RUNTIME !== 'edge'
? { current: '' }
: null
if (isServerComponent) {
serverComponentsInlinedTransformStream = new TransformStream()
const search = urlQueryToSearchParams(query).toString()
// @ts-ignore
globalThis.__next_require__ = (clientModuleId) => {
const ssrModuleId =
serverComponentManifest.__ssr_module_id__[clientModuleId]
return ComponentMod.__next_rsc__.__webpack_require__(ssrModuleId)
}
// @ts-ignore
globalThis.__next_chunk_load__ = () => Promise.resolve()
Component = createServerComponentRenderer(Component, {
cachePrefix: pathname + (search ? `?${search}` : ''),
inlinedTransformStream: serverComponentsInlinedTransformStream,
pageData: serverComponentsStaticPageData,
serverComponentManifest,
})
}
let renderServerComponentData = isServerComponent
? query.__flight__ !== undefined
: false
const serverComponentProps =
isServerComponent && query.__props__
? JSON.parse(query.__props__ as string)
: undefined
delete query.__flight__
delete query.__props__
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
const callMiddleware = async (method: string, args: any[], props = false) => {
let results: any = props ? {} : []
if ((Document as any)[`${method}Middleware`]) {
let middlewareFunc = await (Document as any)[`${method}Middleware`]
middlewareFunc = middlewareFunc.default || middlewareFunc
const curResults = await middlewareFunc(...args)
Initial plugins implementation (#9139) * Add initial bit for plugins * Add checks for needed metadata values * Add test * Initial plugins changes * Add handling for _app middleware * Add loading of _document middleware and handling of multiple default export syntaxes * Fix insert order for middleware member expression * Remove early return from middleware plugin from testing * Add tests for current plugin middlewares * Update test plugin package.json * Update handling for class default export * Update to use webpack loader instead of babel plugin, remove redundant middleware naming, and add field for required env for plugins * Add middleware to support material-ui use case and example material-ui plugin * Update tests and remove tests stuff from google analytics plugin * Remove old plugin suite * Add init-server middleware * Exit hard without stack trace when error in collecting plugins * Add on-error-client and on-error-server and update to run init-server with next-start in serverless mode * Update init-client for google analytics plugin * Add example Sentry plugin and update with-sentry-simple * Remove middleware field/folder and use src dir for plugins * Add post-hydration middleware and update material-ui plugin * Put plugins code behind flag * Update chromedriver * Revert "Update chromedriver" This reverts commit 1461e978e677f7da05e29e0415ec614a04bf65f9. * Update lock file * Remove un-needed _app for sentry example * Add auto loading of scoped packages, add plugins config for manually listing plugins, and update to only collect plugins once * Update example plugins * Expose plugins' config * Rename plugin lifecycles and add babel-preset-build * Rename other methods with unstable * Update log when plugin config overrides auto-detecting
2019-11-01 20:13:13 +01:00
if (props) {
for (const result of curResults) {
results = {
...results,
...result,
}
}
} else {
results = curResults
}
}
return results
}
const headTags = (...args: any) => callMiddleware('headTags', args)
const isFallback = !!query.__nextFallback
const notFoundSrcPage = query.__nextNotFoundSrcPage
delete query.__nextFallback
delete query.__nextLocale
delete query.__nextDefaultLocale
delete query.__nextIsNotFound
const isSSG = !!getStaticProps
const isBuildTimeSSG = isSSG && renderOpts.nextExport
const defaultAppGetInitialProps =
App.getInitialProps === (App as any).origGetInitialProps
const hasPageGetInitialProps = !!(Component as any)?.getInitialProps
const hasPageScripts = (Component as any)?.unstable_scriptLoader
const pageIsDynamic = isDynamicRoute(pathname)
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
const isAutoExport =
!hasPageGetInitialProps &&
defaultAppGetInitialProps &&
!isSSG &&
!getServerSideProps &&
!isServerComponent
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
if (hasPageGetInitialProps && isSSG) {
throw new Error(SSG_GET_INITIAL_PROPS_CONFLICT + ` ${pathname}`)
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
}
if (hasPageGetInitialProps && getServerSideProps) {
throw new Error(SERVER_PROPS_GET_INIT_PROPS_CONFLICT + ` ${pathname}`)
}
if (getServerSideProps && isSSG) {
throw new Error(SERVER_PROPS_SSG_CONFLICT + ` ${pathname}`)
}
if (getStaticPaths && !pageIsDynamic) {
throw new Error(
`getStaticPaths is only allowed for dynamic SSG pages and was found on '${pathname}'.` +
`\nRead more: https://nextjs.org/docs/messages/non-dynamic-getstaticpaths-usage`
)
}
if (!!getStaticPaths && !isSSG) {
throw new Error(
`getStaticPaths was added without a getStaticProps in ${pathname}. Without getStaticProps, getStaticPaths does nothing`
)
}
if (isSSG && pageIsDynamic && !getStaticPaths) {
throw new Error(
`getStaticPaths is required for dynamic SSG pages and is missing for '${pathname}'.` +
`\nRead more: https://nextjs.org/docs/messages/invalid-getstaticpaths-value`
)
}
let asPath: string = renderOpts.resolvedAsPath || (req.url as string)
if (dev) {
const { isValidElementType } = require('next/dist/compiled/react-is')
if (!isValidElementType(Component)) {
throw new Error(
`The default export is not a React Component in page: "${pathname}"`
)
}
if (!isValidElementType(App)) {
throw new Error(
`The default export is not a React Component in page: "/_app"`
)
}
if (!isValidElementType(Document)) {
throw new Error(
`The default export is not a React Component in page: "/_document"`
)
}
if (isAutoExport || isFallback) {
// remove query values except ones that will be set during export
query = {
...(query.amp
? {
amp: query.amp,
}
: {}),
}
asPath = `${pathname}${
// ensure trailing slash is present for non-dynamic auto-export pages
req.url!.endsWith('/') && pathname !== '/' && !pageIsDynamic ? '/' : ''
}`
req.url = pathname
}
if (pathname === '/404' && (hasPageGetInitialProps || getServerSideProps)) {
throw new Error(
`\`pages/404\` ${STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR}`
)
}
if (
STATIC_STATUS_PAGES.includes(pathname) &&
(hasPageGetInitialProps || getServerSideProps)
) {
throw new Error(
`\`pages${pathname}\` ${STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR}`
)
}
}
for (const methodName of [
'getStaticProps',
'getServerSideProps',
'getStaticPaths',
]) {
if ((Component as any)?.[methodName]) {
throw new Error(
`page ${pathname} ${methodName} ${GSSP_COMPONENT_MEMBER_ERROR}`
)
}
}
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
await Loadable.preloadAll() // Make sure all dynamic imports are loaded
let isPreview
let previewData: PreviewData
if (
(isSSG || getServerSideProps) &&
!isFallback &&
process.env.NEXT_RUNTIME !== 'edge'
) {
// Reads of this are cached on the `req` object, so this should resolve
// instantly. There's no need to pass this data down from a previous
// invoke, where we'd have to consider server & serverless.
previewData = tryGetPreviewData(req, res, previewProps)
isPreview = previewData !== false
}
// url will always be set
const routerIsReady = !!(
getServerSideProps ||
hasPageGetInitialProps ||
(!defaultAppGetInitialProps && !isSSG)
)
const router = new ServerRouter(
pathname,
query,
asPath,
{
isFallback: isFallback,
},
routerIsReady,
basePath,
renderOpts.locale,
renderOpts.locales,
renderOpts.defaultLocale,
renderOpts.domainLocales,
isPreview,
getRequestMeta(req, '__nextIsLocaleDomain')
)
const jsxStyleRegistry = createStyleRegistry()
const ctx = {
err,
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
req: isAutoExport ? undefined : req,
res: isAutoExport ? undefined : res,
pathname,
query,
asPath,
locale: renderOpts.locale,
locales: renderOpts.locales,
defaultLocale: renderOpts.defaultLocale,
AppTree: (props: any) => {
return (
<AppContainerWithIsomorphicFiberStructure>
{renderPageTree(App, OriginComponent, { ...props, router })}
</AppContainerWithIsomorphicFiberStructure>
)
},
defaultGetInitialProps: async (
docCtx: DocumentContext,
options: { nonce?: string } = {}
): Promise<DocumentInitialProps> => {
const enhanceApp = (AppComp: any) => {
return (props: any) => <AppComp {...props} />
}
const { html, head } = await docCtx.renderPage({ enhanceApp })
const styles = jsxStyleRegistry.styles({ nonce: options.nonce })
jsxStyleRegistry.flush()
return { html, head, styles }
},
}
let props: any
const ampState = {
ampFirst: pageConfig.amp === true,
hasQuery: Boolean(query.amp),
hybrid: pageConfig.amp === 'hybrid',
}
// Disable AMP under the web environment
const inAmpMode = process.env.NEXT_RUNTIME !== 'edge' && isInAmpMode(ampState)
const reactLoadableModules: string[] = []
let head: JSX.Element[] = defaultHead(inAmpMode)
let initialScripts: any = {}
if (hasPageScripts) {
initialScripts.beforeInteractive = []
.concat(hasPageScripts())
.filter((script: any) => script.props.strategy === 'beforeInteractive')
.map((script: any) => script.props)
}
2020-12-01 19:10:16 +01:00
let scriptLoader: any = {}
const nextExport =
!isSSG && (renderOpts.nextExport || (dev && (isAutoExport || isFallback)))
2020-12-01 19:10:16 +01:00
const styledJsxFlushEffect = () => {
const styles = jsxStyleRegistry.styles()
jsxStyleRegistry.flush()
return <>{styles}</>
}
const AppContainer = ({ children }: { children: JSX.Element }) => (
<RouterContext.Provider value={router}>
<AmpStateContext.Provider value={ampState}>
<HeadManagerContext.Provider
value={{
updateHead: (state) => {
head = state
},
updateScripts: (scripts) => {
scriptLoader = scripts
},
scripts: initialScripts,
mountedInstances: new Set(),
}}
>
<LoadableContext.Provider
value={(moduleName) => reactLoadableModules.push(moduleName)}
>
<StyleRegistry registry={jsxStyleRegistry}>
<ImageConfigContext.Provider value={images}>
{children}
</ImageConfigContext.Provider>
</StyleRegistry>
</LoadableContext.Provider>
</HeadManagerContext.Provider>
</AmpStateContext.Provider>
</RouterContext.Provider>
)
// The `useId` API uses the path indexes to generate an ID for each node.
// To guarantee the match of hydration, we need to ensure that the structure
// of wrapper nodes is isomorphic in server and client.
// TODO: With `enhanceApp` and `enhanceComponents` options, this approach may
// not be useful.
// https://github.com/facebook/react/pull/22644
const Noop = () => null
const AppContainerWithIsomorphicFiberStructure: React.FC<{
children: JSX.Element
}> = ({ children }) => {
return (
<>
{/* <Head/> */}
<Noop />
<AppContainer>
<>
{/* <ReactDevOverlay/> */}
{dev ? (
<>
{children}
<Noop />
</>
) : (
children
)}
{/* <RouteAnnouncer/> */}
<Noop />
</>
</AppContainer>
</>
)
}
props = await loadGetInitialProps(App, {
AppTree: ctx.AppTree,
Component,
router,
ctx,
})
if ((isSSG || getServerSideProps) && isPreview) {
props.__N_PREVIEW = true
}
if (isSSG) {
props[STATIC_PROPS_ID] = true
}
if (isSSG && !isFallback) {
let data: UnwrapPromise<ReturnType<GetStaticProps>>
try {
data = await getStaticProps!({
...(pageIsDynamic ? { params: query as ParsedUrlQuery } : undefined),
...(isPreview
? { preview: true, previewData: previewData }
: undefined),
locales: renderOpts.locales,
locale: renderOpts.locale,
defaultLocale: renderOpts.defaultLocale,
})
} catch (staticPropsError: any) {
// remove not found error code to prevent triggering legacy
// 404 rendering
if (staticPropsError && staticPropsError.code === 'ENOENT') {
delete staticPropsError.code
}
throw staticPropsError
}
if (data == null) {
throw new Error(GSP_NO_RETURNED_VALUE)
}
const invalidKeys = Object.keys(data).filter(
(key) =>
key !== 'revalidate' &&
key !== 'props' &&
key !== 'redirect' &&
key !== 'notFound'
)
if (invalidKeys.includes('unstable_revalidate')) {
throw new Error(UNSTABLE_REVALIDATE_RENAME_ERROR)
}
if (invalidKeys.length) {
throw new Error(invalidKeysMsg('getStaticProps', invalidKeys))
}
if (process.env.NODE_ENV !== 'production') {
if (
typeof (data as any).notFound !== 'undefined' &&
typeof (data as any).redirect !== 'undefined'
) {
throw new Error(
`\`redirect\` and \`notFound\` can not both be returned from ${
isSSG ? 'getStaticProps' : 'getServerSideProps'
} at the same time. Page: ${pathname}\nSee more info here: https://nextjs.org/docs/messages/gssp-mixed-not-found-redirect`
)
}
}
if ('notFound' in data && data.notFound) {
if (pathname === '/404') {
throw new Error(
`The /404 page can not return notFound in "getStaticProps", please remove it to continue!`
)
}
;(renderOpts as any).isNotFound = true
}
if (
'redirect' in data &&
data.redirect &&
typeof data.redirect === 'object'
) {
checkRedirectValues(data.redirect as Redirect, req, 'getStaticProps')
if (isBuildTimeSSG) {
throw new Error(
`\`redirect\` can not be returned from getStaticProps during prerendering (${req.url})\n` +
`See more info here: https://nextjs.org/docs/messages/gsp-redirect-during-prerender`
)
}
;(data as any).props = {
__N_REDIRECT: data.redirect.destination,
__N_REDIRECT_STATUS: getRedirectStatus(data.redirect),
}
if (typeof data.redirect.basePath !== 'undefined') {
;(data as any).props.__N_REDIRECT_BASE_PATH = data.redirect.basePath
}
;(renderOpts as any).isRedirect = true
}
if (
(dev || isBuildTimeSSG) &&
!(renderOpts as any).isNotFound &&
!isSerializableProps(pathname, 'getStaticProps', (data as any).props)
) {
// this fn should throw an error instead of ever returning `false`
throw new Error(
'invariant: getStaticProps did not return valid props. Please report this.'
)
}
if ('revalidate' in data) {
if (typeof data.revalidate === 'number') {
if (!Number.isInteger(data.revalidate)) {
throw new Error(
`A page's revalidate option must be seconds expressed as a natural number for ${req.url}. Mixed numbers, such as '${data.revalidate}', cannot be used.` +
`\nTry changing the value to '${Math.ceil(
data.revalidate
)}' or using \`Math.ceil()\` if you're computing the value.`
)
} else if (data.revalidate <= 0) {
throw new Error(
`A page's revalidate option can not be less than or equal to zero for ${req.url}. A revalidate option of zero means to revalidate after _every_ request, and implies stale data cannot be tolerated.` +
`\n\nTo never revalidate, you can set revalidate to \`false\` (only ran once at build-time).` +
`\nTo revalidate as soon as possible, you can set the value to \`1\`.`
)
} else if (data.revalidate > 31536000) {
// if it's greater than a year for some reason error
console.warn(
`Warning: A page's revalidate option was set to more than a year for ${req.url}. This may have been done in error.` +
`\nTo only run getStaticProps at build-time and not revalidate at runtime, you can set \`revalidate\` to \`false\`!`
)
}
} else if (data.revalidate === true) {
// When enabled, revalidate after 1 second. This value is optimal for
// the most up-to-date page possible, but without a 1-to-1
// request-refresh ratio.
data.revalidate = 1
} else if (
data.revalidate === false ||
typeof data.revalidate === 'undefined'
) {
// By default, we never revalidate.
data.revalidate = false
} else {
throw new Error(
`A page's revalidate option must be seconds expressed as a natural number. Mixed numbers and strings cannot be used. Received '${JSON.stringify(
data.revalidate
)}' for ${req.url}`
)
}
} else {
// By default, we never revalidate.
;(data as any).revalidate = false
}
props.pageProps = Object.assign(
{},
props.pageProps,
'props' in data ? data.props : undefined
)
// pass up revalidate and props for export
// TODO: change this to a different passing mechanism
;(renderOpts as any).revalidate =
'revalidate' in data ? data.revalidate : undefined
;(renderOpts as any).pageData = props
// this must come after revalidate is added to renderOpts
if ((renderOpts as any).isNotFound) {
return null
}
}
if (getServerSideProps) {
props[SERVER_PROPS_ID] = true
}
if (getServerSideProps && !isFallback) {
let data: UnwrapPromise<ReturnType<GetServerSideProps>>
let canAccessRes = true
let resOrProxy = res
let deferredContent = false
if (process.env.NODE_ENV !== 'production') {
resOrProxy = new Proxy<ServerResponse>(res, {
get: function (obj, prop, receiver) {
if (!canAccessRes) {
const message =
`You should not access 'res' after getServerSideProps resolves.` +
`\nRead more: https://nextjs.org/docs/messages/gssp-no-mutating-res`
if (deferredContent) {
throw new Error(message)
} else {
warn(message)
}
}
const value = Reflect.get(obj, prop, receiver)
// since ServerResponse uses internal fields which
// proxy can't map correctly we need to ensure functions
// are bound correctly while being proxied
if (typeof value === 'function') {
return value.bind(obj)
}
return value
},
})
}
try {
data = await getServerSideProps({
req: req as IncomingMessage & {
cookies: NextApiRequestCookies
},
res: resOrProxy,
query,
resolvedUrl: renderOpts.resolvedUrl as string,
...(pageIsDynamic ? { params: params as ParsedUrlQuery } : undefined),
...(previewData !== false
? { preview: true, previewData: previewData }
: undefined),
locales: renderOpts.locales,
locale: renderOpts.locale,
defaultLocale: renderOpts.defaultLocale,
})
canAccessRes = false
} catch (serverSidePropsError: any) {
// remove not found error code to prevent triggering legacy
// 404 rendering
if (
isError(serverSidePropsError) &&
serverSidePropsError.code === 'ENOENT'
) {
delete serverSidePropsError.code
}
throw serverSidePropsError
Add experimental SPR support (#8832) * initial commit for SPRv2 * Add initial SPR cache handling * update SPR handling * Implement SPR handling in render * Update tests, handle caching with serverless next start, add TODOs, and update manifest generating * Handle no prerender-manifest from not being used * Fix url.parse error * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Replace set with constants in next-page-config * simplify sprStatus.used * Add error if getStaticProps is used with getInitialProps * Remove stale TODO * Update revalidate values in SPR cache for non-seeded routes * Apply suggestions from code review * Remove concurrency type * Rename variable for clarity * Add copying prerender files during export * Add comment for clarity * Fix exporting * Update comment * Add additional note * Rename variable * Update to not re-export SPR pages from build * Hard navigate when fetching data fails * Remove default extension * Add brackets * Add checking output files to prerender tests * Adjust export move logic * Clarify behavior of export aggregation * Update variable names for clarity * Update tests * Add comment * s/an oxymoron/contradictory/ * rename * Extract error case * Add tests for exporting SPR pages and update /_next/data endpoint to end with .json * Relocate variable * Adjust route building * Rename to unstable * Rename unstable_getStaticParams * Fix linting * Only add this when a data request * Update prerender data tests * s/isServerless/isLikeServerless/ * Don't rely on query for `next start` in serverless mode * Rename var * Update renderedDuringBuild check * Add test for dynamic param with bracket * Fix serverless next start handling * remove todo * Adjust comment * Update calculateRevalidate * Remove cache logic from render.tsx * Remove extra imports * Move SPR cache logic to next-server * Remove old isDynamic prop * Add calling App getInitialProps for SPR pages * Update revalidate logic * Add isStale to SprCacheValue * Update headers for SPR * add awaiting pendingRevalidation * Dont return null for revalidation render * Adjust logic * Be sure to remove coalesced render * Fix data for serverless * Create a method coalescing utility * Remove TODO * Extract send payload helper * Wrap in-line * Move around some code * Add tests for de-duping and revalidating * Update prerender manifest test
2019-09-24 10:50:04 +02:00
}
if (data == null) {
throw new Error(GSSP_NO_RETURNED_VALUE)
}
if ((data as any).props instanceof Promise) {
deferredContent = true
}
const invalidKeys = Object.keys(data).filter(
(key) => key !== 'props' && key !== 'redirect' && key !== 'notFound'
)
if ((data as any).unstable_notFound) {
throw new Error(
`unstable_notFound has been renamed to notFound, please update the field to continue. Page: ${pathname}`
)
}
if ((data as any).unstable_redirect) {
throw new Error(
`unstable_redirect has been renamed to redirect, please update the field to continue. Page: ${pathname}`
)
}
if (invalidKeys.length) {
throw new Error(invalidKeysMsg('getServerSideProps', invalidKeys))
}
if ('notFound' in data && data.notFound) {
if (pathname === '/404') {
throw new Error(
`The /404 page can not return notFound in "getStaticProps", please remove it to continue!`
)
}
;(renderOpts as any).isNotFound = true
return null
}
if ('redirect' in data && typeof data.redirect === 'object') {
checkRedirectValues(data.redirect as Redirect, req, 'getServerSideProps')
;(data as any).props = {
__N_REDIRECT: data.redirect.destination,
__N_REDIRECT_STATUS: getRedirectStatus(data.redirect),
}
if (typeof data.redirect.basePath !== 'undefined') {
;(data as any).props.__N_REDIRECT_BASE_PATH = data.redirect.basePath
}
;(renderOpts as any).isRedirect = true
}
if (deferredContent) {
;(data as any).props = await (data as any).props
}
if (
(dev || isBuildTimeSSG) &&
!isSerializableProps(pathname, 'getServerSideProps', (data as any).props)
) {
// this fn should throw an error instead of ever returning `false`
throw new Error(
'invariant: getServerSideProps did not return valid props. Please report this.'
)
}
props.pageProps = Object.assign({}, props.pageProps, (data as any).props)
;(renderOpts as any).pageData = props
}
if (
!isSSG && // we only show this warning for legacy pages
!getServerSideProps &&
process.env.NODE_ENV !== 'production' &&
Object.keys(props?.pageProps || {}).includes('url')
) {
console.warn(
`The prop \`url\` is a reserved prop in Next.js for legacy reasons and will be overridden on page ${pathname}\n` +
`See more info here: https://nextjs.org/docs/messages/reserved-page-prop`
)
}
// Avoid rendering page un-necessarily for getServerSideProps data request
// and getServerSideProps/getStaticProps redirects
if ((isDataReq && !isSSG) || (renderOpts as any).isRedirect) {
// For server components, we still need to render the page to get the flight
// data.
if (!serverComponentsStaticPageData) {
return RenderResult.fromStatic(JSON.stringify(props))
}
}
// We don't call getStaticProps or getServerSideProps while generating
// the fallback so make sure to set pageProps to an empty object
if (isFallback) {
props.pageProps = {}
}
// Pass router to the Server Component as a temporary workaround.
if (isServerComponent) {
2022-04-13 10:16:29 +02:00
props.pageProps = Object.assign({}, props.pageProps)
}
// the response might be finished on the getInitialProps call
if (isResSent(res) && !isSSG) return null
if (renderServerComponentData) {
return new RenderResult(
renderToReadableStream(
<OriginComponent {...props.pageProps} {...serverComponentProps} />,
serverComponentManifest
).pipeThrough(createBufferedTransformStream())
)
}
// we preload the buildManifest for auto-export dynamic pages
// to speed up hydrating query values
let filteredBuildManifest = buildManifest
if (isAutoExport && pageIsDynamic) {
const page = denormalizePagePath(normalizePagePath(pathname))
// This code would be much cleaner using `immer` and directly pushing into
// the result from `getPageFiles`, we could maybe consider that in the
// future.
if (page in filteredBuildManifest.pages) {
filteredBuildManifest = {
...filteredBuildManifest,
pages: {
...filteredBuildManifest.pages,
[page]: [
...filteredBuildManifest.pages[page],
...filteredBuildManifest.lowPriorityFiles.filter((f) =>
f.includes('_buildManifest')
),
],
},
lowPriorityFiles: filteredBuildManifest.lowPriorityFiles.filter(
(f) => !f.includes('_buildManifest')
),
}
}
}
const Body = ({ children }: { children: JSX.Element }) => {
return inAmpMode ? children : <div id="__next">{children}</div>
}
/**
* Rules of Static & Dynamic HTML:
*
* 1.) We must generate static HTML unless the caller explicitly opts
* in to dynamic HTML support.
*
* 2.) If dynamic HTML support is requested, we must honor that request
* or throw an error. It is the sole responsibility of the caller to
* ensure they aren't e.g. requesting dynamic HTML for an AMP page.
*
* These rules help ensure that other existing features like request caching,
* coalescing, and ISR continue working as intended.
*/
const generateStaticHTML = supportsDynamicHTML !== true
const renderDocument = async () => {
// For `Document`, there are two cases that we don't support:
// 1. Using `Document.getInitialProps` in the Edge runtime.
// 2. Using the class component `Document` with concurrent features.
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) {
throw new Error(
'`getInitialProps` in Document component is not supported with the Edge Runtime.'
)
}
}
if (
(isServerComponent || process.env.NEXT_RUNTIME === 'edge') &&
Document.getInitialProps
) {
if (BuiltinFunctionalDocument) {
Document = BuiltinFunctionalDocument
} else {
throw new Error(
'`getInitialProps` in Document component is not supported with React Server Components.'
)
}
}
async function loadDocumentInitialProps(
renderShell?: (
_App: AppType,
_Component: NextComponentType
) => Promise<ReactReadableStream>
) {
const renderPage: RenderPage = (
options: ComponentsEnhancer = {}
): RenderPageResult | Promise<RenderPageResult> => {
if (ctx.err && ErrorDebug) {
// Always start rendering the shell even if there's an error.
if (renderShell) {
renderShell(App, Component)
}
const html = ReactDOMServer.renderToString(
<Body>
<ErrorDebug error={ctx.err} />
</Body>
)
return { html, head }
}
if (dev && (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`
)
}
const { App: EnhancedApp, Component: EnhancedComponent } =
enhanceComponents(options, App, Component)
if (renderShell) {
return renderShell(EnhancedApp, EnhancedComponent).then(
async (stream) => {
const forwardStream = readableStreamTee(stream)[1]
const html = await streamToString(forwardStream)
return { html, head }
}
)
}
const html = ReactDOMServer.renderToString(
<Body>
<AppContainerWithIsomorphicFiberStructure>
{renderPageTree(EnhancedApp, EnhancedComponent, {
...props,
router,
})}
</AppContainerWithIsomorphicFiberStructure>
</Body>
)
return { html, head }
}
const documentCtx = { ...ctx, renderPage }
const docProps: DocumentInitialProps = await loadGetInitialProps(
Document,
documentCtx
)
// the response might be finished on the getInitialProps call
if (isResSent(res) && !isSSG) return null
if (!docProps || typeof docProps.html !== 'string') {
const message = `"${getDisplayName(
Document
)}.getInitialProps()" should resolve to an object with a "html" prop set with a valid html string`
throw new Error(message)
}
return { docProps, documentCtx }
}
const renderContent = (_App: AppType, _Component: NextComponentType) => {
const EnhancedApp = _App || App
const EnhancedComponent = _Component || Component
return ctx.err && ErrorDebug ? (
<Body>
<ErrorDebug error={ctx.err} />
</Body>
) : (
<Body>
<AppContainerWithIsomorphicFiberStructure>
{renderPageTree(EnhancedApp, EnhancedComponent, {
...props,
router,
})}
</AppContainerWithIsomorphicFiberStructure>
</Body>
)
}
if (!process.env.__NEXT_REACT_ROOT) {
// Enabling react legacy rendering mode: __NEXT_REACT_ROOT = false
if (Document.getInitialProps) {
const documentInitialProps = await loadDocumentInitialProps()
if (documentInitialProps === null) return null
const { docProps, documentCtx } = documentInitialProps
return {
bodyResult: (suffix: string) =>
streamFromArray([docProps.html, suffix]),
documentElement: (htmlProps: HtmlProps) => (
<Document {...htmlProps} {...docProps} />
),
head: docProps.head,
headTags: await headTags(documentCtx),
styles: docProps.styles,
}
} else {
const content = renderContent(App, Component)
// for non-concurrent rendering we need to ensure App is rendered
// before _document so that updateHead is called/collected before
// rendering _document's head
const result = ReactDOMServer.renderToString(content)
const bodyResult = (suffix: string) => streamFromArray([result, suffix])
const styles = jsxStyleRegistry.styles()
jsxStyleRegistry.flush()
return {
bodyResult,
documentElement: () => (Document as any)(),
head,
headTags: [],
styles,
}
}
} else {
// Enabling react concurrent rendering mode: __NEXT_REACT_ROOT = true
const renderShell = async (
EnhancedApp: AppType,
EnhancedComponent: NextComponentType
) => {
const content = renderContent(EnhancedApp, EnhancedComponent)
return await renderToInitialStream({
ReactDOMServer,
element: content,
})
}
const createBodyResult = (
initialStream: ReactReadableStream,
suffix?: string
) => {
// this must be called inside bodyResult so appWrappers is
// up to date when `wrapApp` is called
const flushEffectHandler = (): string => {
const flushed = ReactDOMServer.renderToString(styledJsxFlushEffect())
return flushed
}
return continueFromInitialStream(initialStream, {
suffix,
dataStream: serverComponentsInlinedTransformStream?.readable,
generateStaticHTML,
flushEffectHandler,
})
}
const hasDocumentGetInitialProps = !(
isServerComponent ||
process.env.NEXT_RUNTIME === 'edge' ||
!Document.getInitialProps
)
let bodyResult: (s: string) => Promise<ReadableStream<Uint8Array>>
// If it has getInitialProps, we will render the shell in `renderPage`.
// Otherwise we do it right now.
let documentInitialPropsRes:
| {}
| Awaited<ReturnType<typeof loadDocumentInitialProps>>
if (hasDocumentGetInitialProps) {
documentInitialPropsRes = await loadDocumentInitialProps(renderShell)
if (documentInitialPropsRes === null) return null
const { docProps } = documentInitialPropsRes as any
// includes suffix in initial html stream
bodyResult = (suffix: string) =>
createBodyResult(streamFromArray([docProps.html, suffix]))
} else {
const stream = await renderShell(App, Component)
bodyResult = (suffix: string) => createBodyResult(stream, suffix)
documentInitialPropsRes = {}
}
const { docProps } = (documentInitialPropsRes as any) || {}
const documentElement = () => {
if (isServerComponent || process.env.NEXT_RUNTIME === 'edge') {
return (Document as any)()
}
return <Document {...htmlProps} {...docProps} />
}
let styles
if (hasDocumentGetInitialProps) {
styles = docProps.styles
} else {
styles = jsxStyleRegistry.styles()
jsxStyleRegistry.flush()
}
return {
bodyResult,
documentElement,
head,
headTags: [],
styles,
}
}
2019-04-02 16:09:34 +02:00
}
const documentResult = await renderDocument()
if (!documentResult) {
return null
}
const dynamicImportsIds = new Set<string | number>()
const dynamicImports = new Set<string>()
for (const mod of reactLoadableModules) {
const manifestItem: ManifestItem = reactLoadableManifest[mod]
if (manifestItem) {
dynamicImportsIds.add(manifestItem.id)
manifestItem.files.forEach((item) => {
dynamicImports.add(item)
})
}
}
const hybridAmp = ampState.hybrid
const docComponentsRendered: DocumentProps['docComponentsRendered'] = {}
const {
assetPrefix,
buildId,
customServer,
defaultLocale,
disableOptimizedLoading,
domainLocales,
locale,
locales,
runtimeConfig,
} = renderOpts
const htmlProps: HtmlProps = {
__NEXT_DATA__: {
props, // The result of getInitialProps
page: pathname, // The rendered page
query, // querystring parsed / passed by the user
buildId, // buildId is used to facilitate caching of page bundles, we send it to the client so that pageloader knows where to load bundles
assetPrefix: assetPrefix === '' ? undefined : assetPrefix, // send assetPrefix to the client side when configured, otherwise don't sent in the resulting HTML
runtimeConfig, // runtimeConfig if provided, otherwise don't sent in the resulting HTML
nextExport: nextExport === true ? true : undefined, // If this is a page exported by `next export`
autoExport: isAutoExport === true ? true : undefined, // If this is an auto exported page
isFallback,
dynamicIds:
dynamicImportsIds.size === 0
? undefined
: Array.from(dynamicImportsIds),
err: renderOpts.err ? serializeError(dev, renderOpts.err) : undefined, // Error if one happened, otherwise don't sent in the resulting HTML
gsp: !!getStaticProps ? true : undefined, // whether the page is getStaticProps
gssp: !!getServerSideProps ? true : undefined, // whether the page is getServerSideProps
rsc: isServerComponent ? true : undefined, // whether the page is a server components page
customServer, // whether the user is using a custom server
gip: hasPageGetInitialProps ? true : undefined, // whether the page has getInitialProps
appGip: !defaultAppGetInitialProps ? true : undefined, // whether the _app has getInitialProps
locale,
locales,
defaultLocale,
domainLocales,
isPreview: isPreview === true ? true : undefined,
notFoundSrcPage: notFoundSrcPage && dev ? notFoundSrcPage : undefined,
},
buildManifest: filteredBuildManifest,
docComponentsRendered,
dangerousAsPath: router.asPath,
canonicalBase:
!renderOpts.ampPath && getRequestMeta(req, '__nextStrippedLocale')
? `${renderOpts.canonicalBase || ''}/${renderOpts.locale}`
: renderOpts.canonicalBase,
ampPath,
inAmpMode,
isDevelopment: !!dev,
hybridAmp,
dynamicImports: Array.from(dynamicImports),
assetPrefix,
// Only enabled in production as development mode has features relying on HMR (style injection for example)
unstable_runtimeJS:
process.env.NODE_ENV === 'production'
? pageConfig.unstable_runtimeJS
: undefined,
unstable_JsPreload: pageConfig.unstable_JsPreload,
devOnlyCacheBusterQueryString,
2020-12-01 19:10:16 +01:00
scriptLoader,
locale,
disableOptimizedLoading,
head: documentResult.head,
headTags: documentResult.headTags,
styles: documentResult.styles,
crossOrigin: renderOpts.crossOrigin,
optimizeCss: renderOpts.optimizeCss,
optimizeFonts: renderOpts.optimizeFonts,
Adds web worker support to `<Script />` using Partytown (#34244) ## Summary This PR adds a new `worker` strategy to the `<Script />` component that automatically relocates and executes the script in a web worker. ```jsx <Script strategy="worker" ... /> ``` [Partytown](https://partytown.builder.io/) is used under the hood to provide this functionality. ## Behavior - This will land as an experimental feature and will only work behind an opt-in flag in `next.config.js`: ```js experimental: { nextScriptWorkers: true } ``` - This setup use a similar approach to how ESLint and Typescript is used in Next.js by showing an error to the user to install the dependency locally themselves if they've enabled the experimental `nextScriptWorkers` flag. <img width="1068" alt="Screen Shot 2022-03-03 at 2 33 13 PM" src="https://user-images.githubusercontent.com/12476932/156639227-42af5353-a2a6-4126-936e-269112809651.png"> - For Partytown to work, a number of static files must be served directly from the site (see [docs](https://partytown.builder.io/copy-library-files)). In this PR, these files are automatically copied to a `~partytown` directory in `.next/static` during `next build` and `next dev` if the `nextScriptWorkers` flag is set to true. ## Checklist - [X] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [X] Related issues linked using `fixes #number` - [X] Integration tests added - [X] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. This PR fixes #31517.
2022-03-11 23:26:46 +01:00
nextScriptWorkers: renderOpts.nextScriptWorkers,
runtime: globalRuntime,
}
const document = (
<AmpStateContext.Provider value={ampState}>
<HtmlContext.Provider value={htmlProps}>
{documentResult.documentElement(htmlProps)}
</HtmlContext.Provider>
</AmpStateContext.Provider>
)
const documentHTML = ReactDOMServer.renderToStaticMarkup(document)
if (process.env.NODE_ENV !== 'production') {
const nonRenderedComponents = []
const expectedDocComponents = ['Main', 'Head', 'NextScript', 'Html']
for (const comp of expectedDocComponents) {
if (!(docComponentsRendered as any)[comp]) {
nonRenderedComponents.push(comp)
}
}
if (nonRenderedComponents.length) {
const missingComponentList = nonRenderedComponents
.map((e) => `<${e} />`)
.join(', ')
const plural = nonRenderedComponents.length !== 1 ? 's' : ''
console.warn(
`Your custom Document (pages/_document) did not render all the required subcomponent${plural}.\n` +
`Missing component${plural}: ${missingComponentList}\n` +
'Read how to fix here: https://nextjs.org/docs/messages/missing-document-component'
)
}
}
const [renderTargetPrefix, renderTargetSuffix] = documentHTML.split(
'<next-js-internal-body-render-target></next-js-internal-body-render-target>'
)
const prefix: Array<string> = []
if (!documentHTML.startsWith(DOCTYPE)) {
prefix.push(DOCTYPE)
}
prefix.push(renderTargetPrefix)
if (inAmpMode) {
prefix.push('<!-- __NEXT_DATA__ -->')
}
const streams = [
streamFromArray(prefix),
await documentResult.bodyResult(renderTargetSuffix),
]
// After the page is fully rendered, we can assign the flight response to
// page data of `renderOpts` now.
function updateServerComponentPageData() {
if (serverComponentsStaticPageData) {
;(renderOpts as any).pageData = {
...(renderOpts as any).pageData,
__flight__: serverComponentsStaticPageData.current,
}
}
}
if (
serverComponentsStaticPageData &&
((isDataReq && !isSSG) || (renderOpts as any).isRedirect)
) {
await streamToString(streams[1])
updateServerComponentPageData()
return RenderResult.fromStatic((renderOpts as any).pageData)
}
const postOptimize = (html: string) =>
postProcessHTML(pathname, html, renderOpts, { inAmpMode, hybridAmp })
if (generateStaticHTML) {
const html = await streamToString(chainStreams(streams))
updateServerComponentPageData()
const optimizedHtml = await postOptimize(html)
return new RenderResult(optimizedHtml)
}
return new RenderResult(
chainStreams(streams).pipeThrough(
createBufferedTransformStream(postOptimize)
)
)
}
function errorToJSON(err: Error) {
return {
name: err.name,
message: stripAnsi(err.message),
stack: err.stack,
middleware: (err as any).middleware,
}
}
function serializeError(
dev: boolean | undefined,
err: Error
): Error & { statusCode?: number } {
if (dev) {
return errorToJSON(err)
}
return {
name: 'Internal Server Error.',
message: '500 - Internal Server Error.',
statusCode: 500,
}
}