next.js/packages/next/src/pages/_document.tsx
_document.tsx1010 lines28.8 KB
/// <reference types="webpack/module.d.ts" />

import React, { type JSX } from 'react'
import { NEXT_BUILTIN_DOCUMENT } from '../shared/lib/constants'
import type {
  DocumentContext,
  DocumentInitialProps,
  DocumentProps,
  DocumentType,
  NEXT_DATA,
} from '../shared/lib/utils'
import type { ScriptProps } from '../client/script'
import type { NextFontManifest } from '../build/webpack/plugins/next-font-manifest-plugin'

import { getPageFiles } from '../server/get-page-files'
import type { BuildManifest } from '../server/get-page-files'
import { htmlEscapeJsonString } from '../server/htmlescape'
import isError from '../lib/is-error'

import {
  HtmlContext,
  useHtmlContext,
} from '../shared/lib/html-context.shared-runtime'
import type { HtmlProps } from '../shared/lib/html-context.shared-runtime'
import { encodeURIPath } from '../shared/lib/encode-uri-path'
import type { DeepReadonly } from '../shared/lib/deep-readonly'
import { getTracer } from '../server/lib/trace/tracer'
import { getTracedMetadata } from '../server/lib/trace/utils'

export type { DocumentContext, DocumentInitialProps, DocumentProps }

export type OriginProps = {
  nonce?: string
  crossOrigin?: 'anonymous' | 'use-credentials' | '' | undefined
  children?: React.ReactNode
}

type DocumentFiles = {
  sharedFiles: readonly string[]
  pageFiles: readonly string[]
  allFiles: readonly string[]
}

type HeadHTMLProps = React.DetailedHTMLProps<
  React.HTMLAttributes<HTMLHeadElement>,
  HTMLHeadElement
>

type HeadProps = OriginProps & HeadHTMLProps

/** Set of pages that have triggered a large data warning on production mode. */
const largePageDataWarnings = new Set<string>()

function getDocumentFiles(
  buildManifest: BuildManifest,
  pathname: string
): DocumentFiles {
  const sharedFiles: readonly string[] = getPageFiles(buildManifest, '/_app')
  const pageFiles: readonly string[] = getPageFiles(buildManifest, pathname)

  return {
    sharedFiles,
    pageFiles,
    allFiles: [...new Set([...sharedFiles, ...pageFiles])],
  }
}

function getPolyfillScripts(context: HtmlProps, props: OriginProps) {
  // polyfills.js has to be rendered as nomodule without async
  // It also has to be the first script to load
  const {
    assetPrefix,
    buildManifest,
    assetQueryString,
    disableOptimizedLoading,
    crossOrigin,
  } = context

  return buildManifest.polyfillFiles
    .filter(
      (polyfill) => polyfill.endsWith('.js') && !polyfill.endsWith('.module.js')
    )
    .map((polyfill) => (
      <script
        key={polyfill}
        defer={!disableOptimizedLoading}
        nonce={props.nonce}
        crossOrigin={props.crossOrigin || crossOrigin}
        noModule={true}
        src={`${assetPrefix}/_next/${encodeURIPath(
          polyfill
        )}${assetQueryString}`}
      />
    ))
}

function hasComponentProps(child: any): child is React.ReactElement<any> {
  return !!child && !!child.props
}

function getDynamicChunks(
  context: HtmlProps,
  props: OriginProps,
  files: DocumentFiles
) {
  const {
    dynamicImports,
    assetPrefix,
    isDevelopment,
    assetQueryString,
    disableOptimizedLoading,
    crossOrigin,
  } = context

  return dynamicImports.map((file) => {
    if (!file.endsWith('.js') || files.allFiles.includes(file)) return null

    return (
      <script
        async={!isDevelopment && disableOptimizedLoading}
        defer={!disableOptimizedLoading}
        key={file}
        src={`${assetPrefix}/_next/${encodeURIPath(file)}${assetQueryString}`}
        nonce={props.nonce}
        crossOrigin={props.crossOrigin || crossOrigin}
      />
    )
  })
}

function getScripts(
  context: HtmlProps,
  props: OriginProps,
  files: DocumentFiles
) {
  const {
    assetPrefix,
    buildManifest,
    isDevelopment,
    assetQueryString,
    mutableAssetQueryString,
    disableOptimizedLoading,
    crossOrigin,
  } = context

  const normalScripts = files.allFiles.filter((file) => file.endsWith('.js'))
  const lowPriorityScripts = buildManifest.lowPriorityFiles?.filter((file) =>
    file.endsWith('.js')
  )

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

function getPreNextWorkerScripts(context: HtmlProps, props: OriginProps) {
  const { assetPrefix, scriptLoader, crossOrigin, nextScriptWorkers } = context

  // disable `nextScriptWorkers` in edge runtime
  if (!nextScriptWorkers || process.env.NEXT_RUNTIME === 'edge') return null

  try {
    // @ts-expect-error: Prevent webpack from processing this require
    let { partytownSnippet } = __non_webpack_require__(
      '@builder.io/partytown/integration'!
    )

    const children = Array.isArray(props.children)
      ? props.children
      : [props.children]

    // Check to see if the user has defined their own Partytown configuration
    const userDefinedConfig = children.find(
      (child) =>
        hasComponentProps(child) &&
        child?.props?.dangerouslySetInnerHTML?.__html.length &&
        'data-partytown-config' in child.props
    )

    return (
      <>
        {!userDefinedConfig && (
          <script
            data-partytown-config=""
            dangerouslySetInnerHTML={{
              __html: `
            partytown = {
              lib: "${assetPrefix}/_next/static/~partytown/"
            };
          `,
            }}
          />
        )}
        <script
          data-partytown=""
          dangerouslySetInnerHTML={{
            __html: partytownSnippet(),
          }}
        />
        {(scriptLoader.worker || []).map((file: ScriptProps, index: number) => {
          const {
            strategy,
            src,
            children: scriptChildren,
            dangerouslySetInnerHTML,
            ...scriptProps
          } = file

          let srcProps: {
            src?: string
            dangerouslySetInnerHTML?: ScriptProps['dangerouslySetInnerHTML']
          } = {}

          if (src) {
            // Use external src if provided
            srcProps.src = src
          } else if (
            dangerouslySetInnerHTML &&
            dangerouslySetInnerHTML.__html
          ) {
            // Embed inline script if provided with dangerouslySetInnerHTML
            srcProps.dangerouslySetInnerHTML = {
              __html: dangerouslySetInnerHTML.__html,
            }
          } else if (scriptChildren) {
            // Embed inline script if provided with children
            srcProps.dangerouslySetInnerHTML = {
              __html:
                typeof scriptChildren === 'string'
                  ? scriptChildren
                  : Array.isArray(scriptChildren)
                    ? scriptChildren.join('')
                    : '',
            }
          } else {
            throw new Error(
              'Invalid usage of next/script. Did you forget to include a src attribute or an inline script? https://nextjs.org/docs/messages/invalid-script'
            )
          }

          return (
            <script
              {...srcProps}
              {...scriptProps}
              type="text/partytown"
              key={src || index}
              nonce={props.nonce}
              data-nscript="worker"
              crossOrigin={props.crossOrigin || crossOrigin}
            />
          )
        })}
      </>
    )
  } catch (err) {
    if (isError(err) && err.code !== 'MODULE_NOT_FOUND') {
      console.warn(`Warning: ${err.message}`)
    }
    return null
  }
}

function getPreNextScripts(context: HtmlProps, props: OriginProps) {
  const { scriptLoader, disableOptimizedLoading, crossOrigin } = context

  const webWorkerScripts = getPreNextWorkerScripts(context, props)

  const beforeInteractiveScripts = (scriptLoader.beforeInteractive || [])
    .filter((script) => script.src)
    .map((file: ScriptProps, index: number) => {
      const { strategy, ...scriptProps } = file
      return (
        <script
          {...scriptProps}
          key={scriptProps.src || index}
          defer={scriptProps.defer ?? !disableOptimizedLoading}
          nonce={scriptProps.nonce || props.nonce}
          data-nscript="beforeInteractive"
          crossOrigin={props.crossOrigin || crossOrigin}
        />
      )
    })

  return (
    <>
      {webWorkerScripts}
      {beforeInteractiveScripts}
    </>
  )
}

function getHeadHTMLProps(props: HeadProps) {
  const { crossOrigin, nonce, ...restProps } = props

  // This assignment is necessary for additional type checking to avoid unsupported attributes in <head>
  const headProps: HeadHTMLProps & {
    [P in Exclude<keyof HeadProps, keyof HeadHTMLProps>]?: never
  } = restProps

  return headProps
}

function getNextFontLinkTags(
  nextFontManifest: DeepReadonly<NextFontManifest> | undefined,
  dangerousAsPath: string,
  assetPrefix: string = '',
  assetQueryString: string = ''
) {
  if (!nextFontManifest) {
    return {
      preconnect: null,
      preload: null,
    }
  }

  const appFontsEntry = nextFontManifest.pages['/_app']
  const pageFontsEntry = nextFontManifest.pages[dangerousAsPath]

  const preloadedFontFiles = Array.from(
    new Set([...(appFontsEntry ?? []), ...(pageFontsEntry ?? [])])
  )

  // If no font files should preload but there's an entry for the path, add a preconnect tag.
  const preconnectToSelf = !!(
    preloadedFontFiles.length === 0 &&
    (appFontsEntry || pageFontsEntry)
  )

  return {
    preconnect: preconnectToSelf ? (
      <link
        data-next-font={
          nextFontManifest.pagesUsingSizeAdjust ? 'size-adjust' : ''
        }
        rel="preconnect"
        href="/"
        crossOrigin="anonymous"
      />
    ) : null,
    preload: preloadedFontFiles
      ? preloadedFontFiles.map((fontFile) => {
          const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(fontFile)![1]
          return (
            <link
              key={fontFile}
              rel="preload"
              href={`${assetPrefix}/_next/${encodeURIPath(fontFile)}${assetQueryString}`}
              as="font"
              type={`font/${ext}`}
              crossOrigin="anonymous"
              data-next-font={fontFile.includes('-s') ? 'size-adjust' : ''}
            />
          )
        })
      : null,
  }
}

// Use `React.Component` to avoid errors from the RSC checks because
// it can't be imported directly in Server Components:
//
//   import { Component } from 'react'
//
// More info: https://github.com/vercel/next.js/pull/40686
export class Head extends React.Component<HeadProps> {
  static contextType = HtmlContext

  context!: HtmlProps

  getCssLinks(files: DocumentFiles): JSX.Element[] | null {
    const {
      assetPrefix,
      cssAssetQueryString,
      dynamicImports,
      dynamicCssManifest,
      crossOrigin,
      optimizeCss,
    } = this.context
    const cssFiles = files.allFiles.filter((f) => f.endsWith('.css'))
    const sharedFiles: Set<string> = new Set(files.sharedFiles)

    // Unmanaged files are CSS files that will be handled directly by the
    // webpack runtime (`mini-css-extract-plugin`).
    let unmanagedFiles: Set<string> = new Set([])
    let localDynamicCssFiles = Array.from(
      new Set(dynamicImports.filter((file) => file.endsWith('.css')))
    )
    if (localDynamicCssFiles.length) {
      const existing = new Set(cssFiles)
      localDynamicCssFiles = localDynamicCssFiles.filter(
        (f) => !(existing.has(f) || sharedFiles.has(f))
      )
      unmanagedFiles = new Set(localDynamicCssFiles)
      cssFiles.push(...localDynamicCssFiles)
    }

    let cssLinkElements: JSX.Element[] = []
    cssFiles.forEach((file) => {
      const isSharedFile = sharedFiles.has(file)
      const isUnmanagedFile = unmanagedFiles.has(file)
      const isFileInDynamicCssManifest = dynamicCssManifest.has(file)

      if (!optimizeCss) {
        cssLinkElements.push(
          <link
            key={`${file}-preload`}
            nonce={this.props.nonce}
            rel="preload"
            href={`${assetPrefix}/_next/${encodeURIPath(
              file
            )}${cssAssetQueryString}`}
            as="style"
            crossOrigin={this.props.crossOrigin || crossOrigin}
          />
        )
      }

      cssLinkElements.push(
        <link
          key={file}
          nonce={this.props.nonce}
          rel="stylesheet"
          href={`${assetPrefix}/_next/${encodeURIPath(
            file
          )}${cssAssetQueryString}`}
          crossOrigin={this.props.crossOrigin || crossOrigin}
          data-n-g={isUnmanagedFile ? undefined : isSharedFile ? '' : undefined}
          data-n-p={
            isSharedFile || isUnmanagedFile || isFileInDynamicCssManifest
              ? undefined
              : ''
          }
        />
      )
    })

    return cssLinkElements.length === 0 ? null : cssLinkElements
  }

  getPreloadDynamicChunks() {
    const { dynamicImports, assetPrefix, assetQueryString, crossOrigin } =
      this.context

    return (
      dynamicImports
        .map((file) => {
          if (!file.endsWith('.js')) {
            return null
          }

          return (
            <link
              rel="preload"
              key={file}
              href={`${assetPrefix}/_next/${encodeURIPath(
                file
              )}${assetQueryString}`}
              as="script"
              nonce={this.props.nonce}
              crossOrigin={this.props.crossOrigin || crossOrigin}
            />
          )
        })
        // Filter out nulled scripts
        .filter(Boolean)
    )
  }

  getPreloadMainLinks(files: DocumentFiles): JSX.Element[] | null {
    const { assetPrefix, assetQueryString, scriptLoader, crossOrigin } =
      this.context
    const preloadFiles = files.allFiles.filter((file: string) => {
      return file.endsWith('.js')
    })

    return [
      ...(scriptLoader.beforeInteractive || []).map((file) => (
        <link
          key={file.src}
          nonce={this.props.nonce}
          rel="preload"
          href={file.src}
          as="script"
          crossOrigin={this.props.crossOrigin || crossOrigin}
        />
      )),
      ...preloadFiles.map((file: string) => (
        <link
          key={file}
          nonce={this.props.nonce}
          rel="preload"
          href={`${assetPrefix}/_next/${encodeURIPath(
            file
          )}${assetQueryString}`}
          as="script"
          crossOrigin={this.props.crossOrigin || crossOrigin}
        />
      )),
    ]
  }

  getBeforeInteractiveInlineScripts() {
    const { scriptLoader } = this.context
    const { nonce, crossOrigin } = this.props

    return (scriptLoader.beforeInteractive || [])
      .filter(
        (script) =>
          !script.src && (script.dangerouslySetInnerHTML || script.children)
      )
      .map((file: ScriptProps, index: number) => {
        const {
          strategy,
          children,
          dangerouslySetInnerHTML,
          src,
          ...scriptProps
        } = file
        let html: NonNullable<
          ScriptProps['dangerouslySetInnerHTML']
        >['__html'] = ''

        if (dangerouslySetInnerHTML && dangerouslySetInnerHTML.__html) {
          html = dangerouslySetInnerHTML.__html
        } else if (children) {
          html =
            typeof children === 'string'
              ? children
              : Array.isArray(children)
                ? children.join('')
                : ''
        }

        return (
          <script
            {...scriptProps}
            dangerouslySetInnerHTML={{ __html: html }}
            key={scriptProps.id || index}
            nonce={nonce}
            data-nscript="beforeInteractive"
            crossOrigin={
              crossOrigin ||
              (process.env.__NEXT_CROSS_ORIGIN as typeof crossOrigin)
            }
          />
        )
      })
  }

  getDynamicChunks(files: DocumentFiles) {
    return getDynamicChunks(this.context, this.props, files)
  }

  getPreNextScripts() {
    return getPreNextScripts(this.context, this.props)
  }

  getScripts(files: DocumentFiles) {
    return getScripts(this.context, this.props, files)
  }

  getPolyfillScripts() {
    return getPolyfillScripts(this.context, this.props)
  }

  render() {
    const {
      styles,
      __NEXT_DATA__,
      dangerousAsPath,
      headTags,
      unstable_runtimeJS,
      unstable_JsPreload,
      disableOptimizedLoading,
      optimizeCss,
      assetPrefix,
      nextFontManifest,
      cssAssetQueryString,
    } = this.context

    const disableRuntimeJS = unstable_runtimeJS === false
    const disableJsPreload =
      unstable_JsPreload === false || !disableOptimizedLoading

    this.context.docComponentsRendered.Head = true

    let { head } = this.context
    let cssPreloads: Array<JSX.Element> = []
    let otherHeadElements: Array<JSX.Element> = []
    if (head) {
      head.forEach((child) => {
        if (
          child &&
          child.type === 'link' &&
          child.props['rel'] === 'preload' &&
          child.props['as'] === 'style'
        ) {
          cssPreloads.push(child)
        } else {
          if (child) {
            otherHeadElements.push(
              React.cloneElement(child, { 'data-next-head': '' })
            )
          }
        }
      })
      head = cssPreloads.concat(otherHeadElements)
    }
    let children: React.ReactNode[] = React.Children.toArray(
      this.props.children
    ).filter(Boolean)
    // show a warning if Head contains <title> (only in development)
    if (process.env.NODE_ENV !== 'production') {
      children = React.Children.map(children, (child: any) => {
        const isReactHelmet = child?.props?.['data-react-helmet']
        if (!isReactHelmet) {
          if (child?.type === 'title') {
            console.warn(
              "Warning: <title> should not be used in _document.js's <Head>. https://nextjs.org/docs/messages/no-document-title"
            )
          } else if (
            child?.type === 'meta' &&
            child?.props?.name === 'viewport'
          ) {
            console.warn(
              "Warning: viewport meta tags should not be used in _document.js's <Head>. https://nextjs.org/docs/messages/no-document-viewport-meta"
            )
          }
        }
        return child
        // @types/react bug. Returned value from .map will not be `null` if you pass in `[null]`
      })!
      if (this.props.crossOrigin)
        console.warn(
          'Warning: `Head` attribute `crossOrigin` is deprecated. https://nextjs.org/docs/messages/doc-crossorigin-deprecated'
        )
    }

    const files: DocumentFiles = getDocumentFiles(
      this.context.buildManifest,
      this.context.__NEXT_DATA__.page
    )

    const nextFontLinkTags = getNextFontLinkTags(
      nextFontManifest,
      dangerousAsPath,
      assetPrefix,
      cssAssetQueryString
    )

    const tracingMetadata = getTracedMetadata(
      getTracer().getTracePropagationData(),
      this.context.experimentalClientTraceMetadata
    )

    const traceMetaTags = (tracingMetadata || []).map(
      ({ key, value }, index) => (
        <meta key={`next-trace-data-${index}`} name={key} content={value} />
      )
    )

    return (
      <head {...getHeadHTMLProps(this.props)}>
        {this.context.isDevelopment && (
          <>
            <style
              data-next-hide-fouc
              dangerouslySetInnerHTML={{
                __html: `body{display:none}`,
              }}
            />
            <noscript data-next-hide-fouc>
              <style
                dangerouslySetInnerHTML={{
                  __html: `body{display:block}`,
                }}
              />
            </noscript>
          </>
        )}
        {head}

        {children}

        {nextFontLinkTags.preconnect}
        {nextFontLinkTags.preload}

        {this.getBeforeInteractiveInlineScripts()}
        {!optimizeCss && this.getCssLinks(files)}
        {!optimizeCss && <noscript data-n-css={this.props.nonce ?? ''} />}

        {!disableRuntimeJS &&
          !disableJsPreload &&
          this.getPreloadDynamicChunks()}
        {!disableRuntimeJS &&
          !disableJsPreload &&
          this.getPreloadMainLinks(files)}

        {!disableOptimizedLoading &&
          !disableRuntimeJS &&
          this.getPolyfillScripts()}

        {!disableOptimizedLoading &&
          !disableRuntimeJS &&
          this.getPreNextScripts()}
        {!disableOptimizedLoading &&
          !disableRuntimeJS &&
          this.getDynamicChunks(files)}
        {!disableOptimizedLoading &&
          !disableRuntimeJS &&
          this.getScripts(files)}

        {optimizeCss && this.getCssLinks(files)}
        {optimizeCss && <noscript data-n-css={this.props.nonce ?? ''} />}
        {this.context.isDevelopment && (
          // this element is used to mount development styles so the
          // ordering matches production
          // (by default, style-loader injects at the bottom of <head />)
          <noscript id="__next_css__DO_NOT_USE__" />
        )}
        {traceMetaTags}
        {styles || null}

        {React.createElement(React.Fragment, {}, ...(headTags || []))}
      </head>
    )
  }
}

function handleDocumentScriptLoaderItems(
  scriptLoader: { beforeInteractive?: any[] },
  __NEXT_DATA__: NEXT_DATA,
  props: any
): void {
  if (!props.children) return

  const scriptLoaderItems: ScriptProps[] = []

  const children = Array.isArray(props.children)
    ? props.children
    : [props.children]

  const headChildren = children.find(
    (child: React.ReactElement) => child.type === Head
  )?.props?.children
  const bodyChildren = children.find(
    (child: React.ReactElement) => child.type === 'body'
  )?.props?.children

  // Scripts with beforeInteractive can be placed inside Head or <body> so children of both needs to be traversed
  const combinedChildren = [
    ...(Array.isArray(headChildren) ? headChildren : [headChildren]),
    ...(Array.isArray(bodyChildren) ? bodyChildren : [bodyChildren]),
  ]

  React.Children.forEach(combinedChildren, (child: any) => {
    if (!child) return

    // When using the `next/script` component, register it in script loader.
    if (child.type?.__nextScript) {
      if (child.props.strategy === 'beforeInteractive') {
        scriptLoader.beforeInteractive = (
          scriptLoader.beforeInteractive || []
        ).concat([
          {
            ...child.props,
          },
        ])
        return
      } else if (
        ['lazyOnload', 'afterInteractive', 'worker'].includes(
          child.props.strategy
        )
      ) {
        scriptLoaderItems.push(child.props)
        return
      } else if (typeof child.props.strategy === 'undefined') {
        scriptLoaderItems.push({ ...child.props, strategy: 'afterInteractive' })
        return
      }
    }
  })

  __NEXT_DATA__.scriptLoader = scriptLoaderItems
}

export class NextScript extends React.Component<OriginProps> {
  static contextType = HtmlContext

  context!: HtmlProps

  getDynamicChunks(files: DocumentFiles) {
    return getDynamicChunks(this.context, this.props, files)
  }

  getPreNextScripts() {
    return getPreNextScripts(this.context, this.props)
  }

  getScripts(files: DocumentFiles) {
    return getScripts(this.context, this.props, files)
  }

  getPolyfillScripts() {
    return getPolyfillScripts(this.context, this.props)
  }

  static getInlineScriptSource(context: Readonly<HtmlProps>): string {
    const { __NEXT_DATA__, largePageDataBytes } = context
    try {
      const data = JSON.stringify(__NEXT_DATA__)

      if (largePageDataWarnings.has(__NEXT_DATA__.page)) {
        return htmlEscapeJsonString(data)
      }

      const bytes =
        process.env.NEXT_RUNTIME === 'edge'
          ? new TextEncoder().encode(data).buffer.byteLength
          : Buffer.from(data).byteLength
      const prettyBytes = (
        require('../lib/pretty-bytes') as typeof import('../lib/pretty-bytes')
      ).default

      if (largePageDataBytes && bytes > largePageDataBytes) {
        if (process.env.NODE_ENV === 'production') {
          largePageDataWarnings.add(__NEXT_DATA__.page)
        }

        console.warn(
          `Warning: data for page "${__NEXT_DATA__.page}"${
            __NEXT_DATA__.page === context.dangerousAsPath
              ? ''
              : ` (path "${context.dangerousAsPath}")`
          } is ${prettyBytes(
            bytes
          )} which exceeds the threshold of ${prettyBytes(
            largePageDataBytes
          )}, this amount of data can reduce performance.\nSee more info here: https://nextjs.org/docs/messages/large-page-data`
        )
      }

      return htmlEscapeJsonString(data)
    } catch (err) {
      if (isError(err) && err.message.indexOf('circular structure') !== -1) {
        throw new Error(
          `Circular structure in "getInitialProps" result of page "${__NEXT_DATA__.page}". https://nextjs.org/docs/messages/circular-structure`
        )
      }
      throw err
    }
  }

  render() {
    const {
      assetPrefix,
      buildManifest,
      unstable_runtimeJS,
      docComponentsRendered,
      assetQueryString,
      disableOptimizedLoading,
      crossOrigin,
    } = this.context
    const disableRuntimeJS = unstable_runtimeJS === false

    docComponentsRendered.NextScript = true

    if (process.env.NODE_ENV !== 'production') {
      if (this.props.crossOrigin)
        console.warn(
          'Warning: `NextScript` attribute `crossOrigin` is deprecated. https://nextjs.org/docs/messages/doc-crossorigin-deprecated'
        )
    }

    const files: DocumentFiles = getDocumentFiles(
      this.context.buildManifest,
      this.context.__NEXT_DATA__.page
    )

    return (
      <>
        {!disableRuntimeJS && buildManifest.devFiles
          ? buildManifest.devFiles.map((file: string) => (
              <script
                key={file}
                src={`${assetPrefix}/_next/${encodeURIPath(
                  file
                )}${assetQueryString}`}
                nonce={this.props.nonce}
                crossOrigin={this.props.crossOrigin || crossOrigin}
              />
            ))
          : null}
        {disableRuntimeJS ? null : (
          <script
            id="__NEXT_DATA__"
            type="application/json"
            nonce={this.props.nonce}
            crossOrigin={this.props.crossOrigin || crossOrigin}
            dangerouslySetInnerHTML={{
              __html: NextScript.getInlineScriptSource(this.context),
            }}
          />
        )}
        {disableOptimizedLoading &&
          !disableRuntimeJS &&
          this.getPolyfillScripts()}
        {disableOptimizedLoading &&
          !disableRuntimeJS &&
          this.getPreNextScripts()}
        {disableOptimizedLoading &&
          !disableRuntimeJS &&
          this.getDynamicChunks(files)}
        {disableOptimizedLoading && !disableRuntimeJS && this.getScripts(files)}
      </>
    )
  }
}

export function Html(
  props: React.DetailedHTMLProps<
    React.HtmlHTMLAttributes<HTMLHtmlElement>,
    HTMLHtmlElement
  >
) {
  const {
    docComponentsRendered,
    locale,
    scriptLoader,
    deploymentId,
    __NEXT_DATA__,
  } = useHtmlContext()

  docComponentsRendered.Html = true
  handleDocumentScriptLoaderItems(scriptLoader, __NEXT_DATA__, props)

  return (
    <html
      {...props}
      lang={props.lang || locale || undefined}
      data-dpl-id={deploymentId || undefined}
    />
  )
}

export function Main() {
  const { docComponentsRendered } = useHtmlContext()
  docComponentsRendered.Main = true
  // @ts-ignore
  return <next-js-internal-body-render-target />
}

/**
 * `Document` component handles the initial `document` markup and renders only on the server side.
 * Commonly used for implementing server side rendering for `css-in-js` libraries.
 */
export default class Document<P = {}> extends React.Component<
  DocumentProps & P
> {
  /**
   * `getInitialProps` hook returns the context object with the addition of `renderPage`.
   * `renderPage` callback executes `React` rendering logic synchronously to support server-rendering wrappers
   */
  static getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
    return ctx.defaultGetInitialProps(ctx)
  }

  render() {
    return (
      <Html>
        <Head nonce={this.props.nonce} />
        <body>
          <Main />
          <NextScript nonce={this.props.nonce} />
        </body>
      </Html>
    )
  }
}

// Add a special property to the built-in `Document` component so later we can
// identify if a user customized `Document` is used or not.
const InternalFunctionDocument: DocumentType =
  function InternalFunctionDocument() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
;(Document as any)[NEXT_BUILTIN_DOCUMENT] = InternalFunctionDocument
Quest for Codev2.0.0
/
SIGN IN