render.tsx 23.3 KB
Newer Older
1
import { IncomingMessage, ServerResponse } from 'http'
2 3
import { ParsedUrlQuery } from 'querystring'
import React from 'react'
J
Joe Haddad 已提交
4
import { renderToStaticMarkup, renderToString } from 'react-dom/server'
5
import { UnwrapPromise } from '../../lib/coalesced-function'
J
Joe Haddad 已提交
6
import {
7 8 9
  GSP_NO_RETURNED_VALUE,
  GSSP_COMPONENT_MEMBER_ERROR,
  GSSP_NO_RETURNED_VALUE,
J
Joe Haddad 已提交
10 11 12 13
  PAGES_404_GET_INITIAL_PROPS_ERROR,
  SERVER_PROPS_GET_INIT_PROPS_CONFLICT,
  SERVER_PROPS_SSG_CONFLICT,
  SSG_GET_INITIAL_PROPS_CONFLICT,
J
JJ Kasper 已提交
14
  UNSTABLE_REVALIDATE_RENAME_ERROR,
J
Joe Haddad 已提交
15
} from '../../lib/constants'
16
import { isSerializableProps } from '../../lib/is-serializable-props'
17
import { GetServerSideProps, GetStaticProps } from '../../types'
J
Joe Haddad 已提交
18 19
import { isInAmpMode } from '../lib/amp'
import { AmpStateContext } from '../lib/amp-context'
20 21 22
import {
  AMP_RENDER_TARGET,
  SERVER_PROPS_ID,
23
  STATIC_PROPS_ID,
24
} from '../lib/constants'
25
import { defaultHead } from '../lib/head'
26
import { HeadManagerContext } from '../lib/head-manager-context'
27
import Loadable from '../lib/loadable'
28
import { LoadableContext } from '../lib/loadable-context'
J
Joe Haddad 已提交
29
import mitt, { MittEmitter } from '../lib/mitt'
T
Tim Neutkens 已提交
30
import { RouterContext } from '../lib/router-context'
J
Joe Haddad 已提交
31
import { NextRouter } from '../lib/router/router'
J
JJ Kasper 已提交
32
import { isDynamicRoute } from '../lib/router/utils/is-dynamic'
33
import {
J
Joe Haddad 已提交
34 35 36 37 38 39 40 41 42 43 44 45
  AppType,
  ComponentsEnhancer,
  DocumentInitialProps,
  DocumentType,
  getDisplayName,
  isResSent,
  loadGetInitialProps,
  NextComponentType,
  RenderPage,
} from '../lib/utils'
import { tryGetPreviewData, __ApiPreviewProps } from './api-utils'
import { getPageFiles } from './get-page-files'
46
import { LoadComponentsReturnType, ManifestItem } from './load-components'
J
Joe Haddad 已提交
47
import optimizeAmp from './optimize-amp'
48

49
function noRouter() {
J
Joe Haddad 已提交
50
  const message =
51
    'No router instance found. you should only use "next/router" inside the client side of your app. https://err.sh/vercel/next.js/no-router-instance'
52 53 54
  throw new Error(message)
}

55
class ServerRouter implements NextRouter {
56 57
  route: string
  pathname: string
58
  query: ParsedUrlQuery
59
  asPath: string
60
  basePath: string
61
  events: any
62
  isFallback: boolean
63 64 65
  // TODO: Remove in the next major version, as this would mean the user is adding event listeners in server-side `render` method
  static events: MittEmitter = mitt()

66 67 68 69
  constructor(
    pathname: string,
    query: ParsedUrlQuery,
    as: string,
70 71
    { isFallback }: { isFallback: boolean },
    basePath: string
72
  ) {
T
Tim Neutkens 已提交
73
    this.route = pathname.replace(/\/$/, '') || '/'
74 75 76
    this.pathname = pathname
    this.query = query
    this.asPath = as
77
    this.isFallback = isFallback
78
    this.basePath = basePath
79
  }
80
  push(): any {
81 82
    noRouter()
  }
83
  replace(): any {
84 85 86 87 88 89 90 91
    noRouter()
  }
  reload() {
    noRouter()
  }
  back() {
    noRouter()
  }
92
  prefetch(): any {
93 94 95 96 97 98 99
    noRouter()
  }
  beforePopState() {
    noRouter()
  }
}

100 101
function enhanceComponents(
  options: ComponentsEnhancer,
102
  App: AppType,
J
Joe Haddad 已提交
103
  Component: NextComponentType
104
): {
J
Joe Haddad 已提交
105 106
  App: AppType
  Component: NextComponentType
107 108
} {
  // For backwards compatibility
109
  if (typeof options === 'function') {
110
    return {
111 112
      App,
      Component: options(Component),
113 114 115 116 117
    }
  }

  return {
    App: options.enhanceApp ? options.enhanceApp(App) : App,
118 119 120
    Component: options.enhanceComponent
      ? options.enhanceComponent(Component)
      : Component,
121 122 123
  }
}

124
export type RenderOptsPartial = {
125
  buildId: string
126
  canonicalBase: string
127 128 129
  runtimeConfig?: { [key: string]: any }
  assetPrefix?: string
  err?: Error | null
130
  autoExport?: boolean
131 132
  nextExport?: boolean
  dev?: boolean
J
Joe Haddad 已提交
133
  ampMode?: any
134 135 136
  ampPath?: string
  inAmpMode?: boolean
  hybridAmp?: boolean
J
Joe Haddad 已提交
137 138
  ErrorDebug?: React.ComponentType<{ error: Error }>
  ampValidator?: (html: string, pathname: string) => Promise<void>
139 140
  ampSkipValidation?: boolean
  ampOptimizerConfig?: { [key: string]: any }
141 142
  isDataReq?: boolean
  params?: ParsedUrlQuery
J
Joe Haddad 已提交
143
  previewProps: __ApiPreviewProps
144
  basePath: string
145
  unstable_runtimeJS?: false
146 147
}

148 149
export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial

150
function renderDocument(
151
  Document: DocumentType,
152
  {
153
    buildManifest,
154 155 156 157 158
    props,
    docProps,
    pathname,
    query,
    buildId,
159
    canonicalBase,
160 161 162
    assetPrefix,
    runtimeConfig,
    nextExport,
163
    autoExport,
164
    isFallback,
165
    dynamicImportsIds,
166
    dangerousAsPath,
167 168
    err,
    dev,
169
    ampPath,
170 171 172
    ampState,
    inAmpMode,
    hybridAmp,
173 174
    files,
    dynamicImports,
175
    headTags,
176 177
    gsp,
    gssp,
178
    customServer,
179 180
    gip,
    appGip,
181
    unstable_runtimeJS,
182 183
  }: RenderOpts & {
    props: any
184
    docProps: DocumentInitialProps
185 186
    pathname: string
    query: ParsedUrlQuery
187
    dangerousAsPath: string
188
    ampState: any
J
Joe Haddad 已提交
189
    ampPath: string
190 191
    inAmpMode: boolean
    hybridAmp: boolean
192 193
    dynamicImportsIds: string[]
    dynamicImports: ManifestItem[]
194
    files: string[]
195
    headTags: any
196
    isFallback?: boolean
197 198
    gsp?: boolean
    gssp?: boolean
199
    customServer?: boolean
200 201
    gip?: boolean
    appGip?: boolean
J
Joe Haddad 已提交
202
  }
203 204 205 206
): string {
  return (
    '<!DOCTYPE html>' +
    renderToStaticMarkup(
207
      <AmpStateContext.Provider value={ampState}>
G
Gerald Monaco 已提交
208 209
        {Document.renderDocument(Document, {
          __NEXT_DATA__: {
210 211 212 213 214 215 216
            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, // If this is a page exported by `next export`
217
            autoExport, // If this is an auto exported page
218
            isFallback,
J
Joe Haddad 已提交
219 220
            dynamicIds:
              dynamicImportsIds.length === 0 ? undefined : dynamicImportsIds,
221
            err: err ? serializeError(dev, err) : undefined, // Error if one happened, otherwise don't sent in the resulting HTML
222 223
            gsp, // whether the page is getStaticProps
            gssp, // whether the page is getServerSideProps
224
            customServer, // whether the user is using a custom server
225 226
            gip, // whether the page has getInitialProps
            appGip, // whether the _app has getInitialProps
G
Gerald Monaco 已提交
227
          },
228
          buildManifest,
G
Gerald Monaco 已提交
229 230 231 232 233 234 235 236 237
          dangerousAsPath,
          canonicalBase,
          ampPath,
          inAmpMode,
          isDevelopment: !!dev,
          hybridAmp,
          files,
          dynamicImports,
          assetPrefix,
238
          headTags,
239
          unstable_runtimeJS,
G
Gerald Monaco 已提交
240 241
          ...docProps,
        })}
242
      </AmpStateContext.Provider>
243
    )
244 245 246
  )
}

247 248 249 250
const invalidKeysMsg = (methodName: string, invalidKeys: string[]) => {
  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: '...' } }` +
251 252
    `\n\nKeys that need to be moved: ${invalidKeys.join(', ')}.` +
    `\nRead more: https://err.sh/next.js/invalid-getstaticprops-value`
253 254 255
  )
}

256 257 258 259 260
export async function renderToHTML(
  req: IncomingMessage,
  res: ServerResponse,
  pathname: string,
  query: ParsedUrlQuery,
J
Joe Haddad 已提交
261
  renderOpts: RenderOpts
262
): Promise<string | null> {
263 264 265
  const {
    err,
    dev = false,
266
    ampPath = '',
267 268
    App,
    Document,
269
    pageConfig = {},
270 271 272
    Component,
    buildManifest,
    reactLoadableManifest,
273
    ErrorDebug,
274 275 276
    getStaticProps,
    getStaticPaths,
    getServerSideProps,
277 278
    isDataReq,
    params,
J
Joe Haddad 已提交
279
    previewProps,
280
    basePath,
281
  } = renderOpts
282

283 284 285 286
  const callMiddleware = async (method: string, args: any[], props = false) => {
    let results: any = props ? {} : []

    if ((Document as any)[`${method}Middleware`]) {
287 288 289 290
      let middlewareFunc = await (Document as any)[`${method}Middleware`]
      middlewareFunc = middlewareFunc.default || middlewareFunc

      const curResults = await middlewareFunc(...args)
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
      if (props) {
        for (const result of curResults) {
          results = {
            ...results,
            ...result,
          }
        }
      } else {
        results = curResults
      }
    }
    return results
  }

  const headTags = (...args: any) => callMiddleware('headTags', args)

307
  const didRewrite = (req as any)._nextDidRewrite
308 309 310
  const isFallback = !!query.__nextFallback
  delete query.__nextFallback

311
  const isSSG = !!getStaticProps
312
  const isBuildTimeSSG = isSSG && renderOpts.nextExport
313 314 315
  const defaultAppGetInitialProps =
    App.getInitialProps === (App as any).origGetInitialProps

J
JJ Kasper 已提交
316
  const hasPageGetInitialProps = !!(Component as any).getInitialProps
317

318 319
  const pageIsDynamic = isDynamicRoute(pathname)

J
JJ Kasper 已提交
320
  const isAutoExport =
321 322
    !hasPageGetInitialProps &&
    defaultAppGetInitialProps &&
323
    !isSSG &&
324
    !getServerSideProps
J
JJ Kasper 已提交
325

326 327 328 329 330 331 332 333 334 335 336 337
  for (const methodName of [
    'getStaticProps',
    'getServerSideProps',
    'getStaticPaths',
  ]) {
    if ((Component as any)[methodName]) {
      throw new Error(
        `page ${pathname} ${methodName} ${GSSP_COMPONENT_MEMBER_ERROR}`
      )
    }
  }

338 339
  if (
    process.env.NODE_ENV !== 'production' &&
340
    (isAutoExport || isFallback) &&
341
    pageIsDynamic &&
342
    didRewrite
343
  ) {
344 345
    // TODO: If we decide to ship rewrites to the client we could
    // solve this by running over the rewrites and getting the params.
346
    throw new Error(
347 348
      `Rewrites don't support${
        isFallback ? ' ' : ' auto-exported '
349 350 351
      }dynamic pages${isFallback ? ' with getStaticProps ' : ' '}yet.\n` +
        `Using this will cause the page to fail to parse the params on the client\n` +
        `See more info: https://err.sh/next.js/rewrite-auto-export-fallback`
352 353 354
    )
  }

355
  if (hasPageGetInitialProps && isSSG) {
356
    throw new Error(SSG_GET_INITIAL_PROPS_CONFLICT + ` ${pathname}`)
J
JJ Kasper 已提交
357
  }
358

359
  if (hasPageGetInitialProps && getServerSideProps) {
360 361 362
    throw new Error(SERVER_PROPS_GET_INIT_PROPS_CONFLICT + ` ${pathname}`)
  }

363
  if (getServerSideProps && isSSG) {
364 365 366
    throw new Error(SERVER_PROPS_SSG_CONFLICT + ` ${pathname}`)
  }

367
  if (!!getStaticPaths && !isSSG) {
368
    throw new Error(
369
      `getStaticPaths was added without a getStaticProps in ${pathname}. Without getStaticProps, getStaticPaths does nothing`
370 371 372
    )
  }

373
  if (isSSG && pageIsDynamic && !getStaticPaths) {
374
    throw new Error(
375
      `getStaticPaths is required for dynamic SSG pages and is missing for '${pathname}'.` +
376 377 378 379
        `\nRead more: https://err.sh/next.js/invalid-getstaticpaths-value`
    )
  }

380 381 382
  if (dev) {
    const { isValidElementType } = require('react-is')
    if (!isValidElementType(Component)) {
383
      throw new Error(
J
Joe Haddad 已提交
384
        `The default export is not a React Component in page: "${pathname}"`
385
      )
386 387
    }

388
    if (!isValidElementType(App)) {
389
      throw new Error(
J
Joe Haddad 已提交
390
        `The default export is not a React Component in page: "/_app"`
391
      )
392 393 394
    }

    if (!isValidElementType(Document)) {
395
      throw new Error(
J
Joe Haddad 已提交
396
        `The default export is not a React Component in page: "/_document"`
397
      )
398
    }
J
JJ Kasper 已提交
399

J
JJ Kasper 已提交
400
    if (isAutoExport) {
401 402
      // remove query values except ones that will be set during export
      query = {
403 404 405 406 407
        ...(query.amp
          ? {
              amp: query.amp,
            }
          : {}),
J
JJ Kasper 已提交
408
      }
409
      req.url = pathname
410
      renderOpts.nextExport = true
J
JJ Kasper 已提交
411
    }
412

413
    if (pathname === '/404' && (hasPageGetInitialProps || getServerSideProps)) {
414 415
      throw new Error(PAGES_404_GET_INITIAL_PROPS_ERROR)
    }
416
  }
417
  if (isAutoExport) renderOpts.autoExport = true
418
  if (isSSG) renderOpts.nextExport = false
419

J
JJ Kasper 已提交
420 421
  await Loadable.preloadAll() // Make sure all dynamic imports are loaded

422
  // url will always be set
423 424 425 426 427 428 429 430 431 432
  const asPath: string = req.url as string
  const router = new ServerRouter(
    pathname,
    query,
    asPath,
    {
      isFallback: isFallback,
    },
    basePath
  )
433 434
  const ctx = {
    err,
J
JJ Kasper 已提交
435 436
    req: isAutoExport ? undefined : req,
    res: isAutoExport ? undefined : res,
437 438 439
    pathname,
    query,
    asPath,
440 441 442 443 444 445 446
    AppTree: (props: any) => {
      return (
        <AppContainer>
          <App {...props} Component={Component} router={router} />
        </AppContainer>
      )
    },
447
  }
448
  let props: any
449

450 451 452 453 454 455
  const ampState = {
    ampFirst: pageConfig.amp === true,
    hasQuery: Boolean(query.amp),
    hybrid: pageConfig.amp === 'hybrid',
  }

456 457
  const inAmpMode = isInAmpMode(ampState)

458 459
  const reactLoadableModules: string[] = []

460 461
  let head: JSX.Element[] = defaultHead(inAmpMode)

462
  const AppContainer = ({ children }: any) => (
463
    <RouterContext.Provider value={router}>
T
Tim Neutkens 已提交
464
      <AmpStateContext.Provider value={ampState}>
465 466 467 468 469 470 471
        <HeadManagerContext.Provider
          value={{
            updateHead: (state) => {
              head = state
            },
            mountedInstances: new Set(),
          }}
T
Tim Neutkens 已提交
472
        >
473 474 475 476 477 478
          <LoadableContext.Provider
            value={(moduleName) => reactLoadableModules.push(moduleName)}
          >
            {children}
          </LoadableContext.Provider>
        </HeadManagerContext.Provider>
T
Tim Neutkens 已提交
479
      </AmpStateContext.Provider>
480
    </RouterContext.Provider>
481 482
  )

483
  try {
J
JJ Kasper 已提交
484 485 486 487 488 489
    props = await loadGetInitialProps(App, {
      AppTree: ctx.AppTree,
      Component,
      router,
      ctx,
    })
490 491 492 493 494

    if (isSSG) {
      props[STATIC_PROPS_ID] = true
    }

495
    let previewData: string | false | object | undefined
J
JJ Kasper 已提交
496

497
    if ((isSSG || getServerSideProps) && !isFallback) {
J
Joe Haddad 已提交
498 499 500
      // 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.
501 502 503 504
      previewData = tryGetPreviewData(req, res, previewProps)
    }

    if (isSSG && !isFallback) {
505 506 507 508 509 510 511 512 513
      let data: UnwrapPromise<ReturnType<GetStaticProps>>

      try {
        data = await getStaticProps!({
          ...(pageIsDynamic ? { params: query as ParsedUrlQuery } : undefined),
          ...(previewData !== false
            ? { preview: true, previewData: previewData }
            : undefined),
        })
514
      } catch (staticPropsError) {
515 516
        // remove not found error code to prevent triggering legacy
        // 404 rendering
517 518
        if (staticPropsError.code === 'ENOENT') {
          delete staticPropsError.code
519
        }
520
        throw staticPropsError
521
      }
J
JJ Kasper 已提交
522

523 524 525 526
      if (data == null) {
        throw new Error(GSP_NO_RETURNED_VALUE)
      }

J
JJ Kasper 已提交
527
      const invalidKeys = Object.keys(data).filter(
T
Tim Neutkens 已提交
528
        (key) => key !== 'revalidate' && key !== 'props'
J
JJ Kasper 已提交
529 530
      )

T
Tim Neutkens 已提交
531
      if (invalidKeys.includes('unstable_revalidate')) {
J
JJ Kasper 已提交
532 533 534
        throw new Error(UNSTABLE_REVALIDATE_RENAME_ERROR)
      }

J
JJ Kasper 已提交
535
      if (invalidKeys.length) {
536
        throw new Error(invalidKeysMsg('getStaticProps', invalidKeys))
J
JJ Kasper 已提交
537 538
      }

539 540 541 542 543 544 545 546 547 548
      if (
        (dev || isBuildTimeSSG) &&
        !isSerializableProps(pathname, 'getStaticProps', data.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.'
        )
      }

T
Tim Neutkens 已提交
549 550
      if (typeof data.revalidate === 'number') {
        if (!Number.isInteger(data.revalidate)) {
J
JJ Kasper 已提交
551
          throw new Error(
T
Tim Neutkens 已提交
552
            `A page's revalidate option must be seconds expressed as a natural number. Mixed numbers, such as '${data.revalidate}', cannot be used.` +
553
              `\nTry changing the value to '${Math.ceil(
T
Tim Neutkens 已提交
554
                data.revalidate
555
              )}' or using \`Math.ceil()\` if you're computing the value.`
556
          )
T
Tim Neutkens 已提交
557
        } else if (data.revalidate <= 0) {
558
          throw new Error(
559 560 561
            `A page's revalidate option can not be less than or equal to zero. 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\`.`
J
JJ Kasper 已提交
562
          )
T
Tim Neutkens 已提交
563
        } else if (data.revalidate > 31536000) {
J
JJ Kasper 已提交
564 565
          // if it's greater than a year for some reason error
          console.warn(
566 567
            `Warning: A page's revalidate option was set to more than a year. 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\`!`
J
JJ Kasper 已提交
568 569
          )
        }
T
Tim Neutkens 已提交
570
      } else if (data.revalidate === true) {
J
Joe Haddad 已提交
571
        // When enabled, revalidate after 1 second. This value is optimal for
572 573
        // the most up-to-date page possible, but without a 1-to-1
        // request-refresh ratio.
T
Tim Neutkens 已提交
574
        data.revalidate = 1
J
Joe Haddad 已提交
575 576
      } else {
        // By default, we never revalidate.
T
Tim Neutkens 已提交
577
        data.revalidate = false
J
JJ Kasper 已提交
578 579
      }

580
      props.pageProps = Object.assign({}, props.pageProps, data.props)
J
JJ Kasper 已提交
581
      // pass up revalidate and props for export
582
      // TODO: change this to a different passing mechanism
T
Tim Neutkens 已提交
583
      ;(renderOpts as any).revalidate = data.revalidate
584
      ;(renderOpts as any).pageData = props
J
JJ Kasper 已提交
585
    }
586

587 588 589 590
    if (getServerSideProps) {
      props[SERVER_PROPS_ID] = true
    }

591
    if (getServerSideProps && !isFallback) {
592 593 594 595 596 597 598 599 600 601 602 603
      let data: UnwrapPromise<ReturnType<GetServerSideProps>>

      try {
        data = await getServerSideProps({
          req,
          res,
          query,
          ...(pageIsDynamic ? { params: params as ParsedUrlQuery } : undefined),
          ...(previewData !== false
            ? { preview: true, previewData: previewData }
            : undefined),
        })
604
      } catch (serverSidePropsError) {
605 606
        // remove not found error code to prevent triggering legacy
        // 404 rendering
607 608
        if (serverSidePropsError.code === 'ENOENT') {
          delete serverSidePropsError.code
609
        }
610
        throw serverSidePropsError
611
      }
612

613 614 615 616
      if (data == null) {
        throw new Error(GSSP_NO_RETURNED_VALUE)
      }

J
Joe Haddad 已提交
617
      const invalidKeys = Object.keys(data).filter((key) => key !== 'props')
618 619 620 621 622

      if (invalidKeys.length) {
        throw new Error(invalidKeysMsg('getServerSideProps', invalidKeys))
      }

623 624 625 626 627 628 629 630 631 632
      if (
        (dev || isBuildTimeSSG) &&
        !isSerializableProps(pathname, 'getServerSideProps', data.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.'
        )
      }

633
      props.pageProps = Object.assign({}, props.pageProps, data.props)
634 635
      ;(renderOpts as any).pageData = props
    }
636 637 638 639 640
  } catch (dataFetchError) {
    if (isDataReq || !dev || !dataFetchError) throw dataFetchError
    ctx.err = dataFetchError
    renderOpts.err = dataFetchError
    console.error(dataFetchError)
641
  }
642

643
  if (
644
    !isSSG && // we only show this warning for legacy pages
645
    !getServerSideProps &&
646 647 648 649 650
    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` +
651
        `See more info here: https://err.sh/vercel/next.js/reserved-page-prop`
652 653 654
    )
  }

655
  // We only need to do this if we want to support calling
656
  // _app's getInitialProps for getServerSideProps if not this can be removed
657
  if (isDataReq && !isSSG) return props
658

659
  // We don't call getStaticProps or getServerSideProps while generating
660 661 662 663 664
  // the fallback so make sure to set pageProps to an empty object
  if (isFallback) {
    props.pageProps = {}
  }

665
  // the response might be finished on the getInitialProps call
666
  if (isResSent(res) && !isSSG) return null
667

668 669 670 671 672 673 674 675 676 677 678
  // AMP First pages do not have client-side JavaScript files
  const files = ampState.ampFirst
    ? []
    : [
        ...new Set([
          ...getPageFiles(buildManifest, '/_app'),
          ...(pathname !== '/_error'
            ? getPageFiles(buildManifest, pathname)
            : []),
        ]),
      ]
T
Tim Neutkens 已提交
679

680 681 682
  const renderPage: RenderPage = (
    options: ComponentsEnhancer = {}
  ): { html: string; head: any } => {
683
    if (ctx.err && ErrorDebug) {
684
      return { html: renderToString(<ErrorDebug error={ctx.err} />), head }
685 686 687 688
    }

    if (dev && (props.router || props.Component)) {
      throw new Error(
689
        `'router' and 'Component' can not be returned in getInitialProps from _app.js https://err.sh/vercel/next.js/cant-override-next-props`
690 691
      )
    }
T
Tim Neutkens 已提交
692 693 694 695 696 697

    const {
      App: EnhancedApp,
      Component: EnhancedComponent,
    } = enhanceComponents(options, App, Component)

698
    const html = renderToString(
T
Tim Neutkens 已提交
699 700
      <AppContainer>
        <EnhancedApp Component={EnhancedComponent} router={router} {...props} />
701
      </AppContainer>
T
Tim Neutkens 已提交
702
    )
703 704

    return { html, head }
T
Tim Neutkens 已提交
705
  }
706
  const documentCtx = { ...ctx, renderPage }
707 708 709 710
  const docProps: DocumentInitialProps = await loadGetInitialProps(
    Document,
    documentCtx
  )
K
Kévin Dunglas 已提交
711
  // the response might be finished on the getInitialProps call
712
  if (isResSent(res) && !isSSG) return null
713

714
  if (!docProps || typeof docProps.html !== 'string') {
J
Joe Haddad 已提交
715 716 717
    const message = `"${getDisplayName(
      Document
    )}.getInitialProps()" should resolve to an object with a "html" prop set with a valid html string`
718 719 720
    throw new Error(message)
  }

721 722 723 724
  const dynamicImportIdsSet = new Set<string>()
  const dynamicImports: ManifestItem[] = []

  for (const mod of reactLoadableModules) {
725
    const manifestItem: ManifestItem[] = reactLoadableManifest[mod]
726 727

    if (manifestItem) {
J
Joe Haddad 已提交
728
      manifestItem.forEach((item) => {
729 730 731 732 733 734 735
        dynamicImports.push(item)
        dynamicImportIdsSet.add(item.id as string)
      })
    }
  }

  const dynamicImportsIds = [...dynamicImportIdsSet]
736 737
  const hybridAmp = ampState.hybrid

738
  // update renderOpts so export knows current state
739 740
  renderOpts.inAmpMode = inAmpMode
  renderOpts.hybridAmp = hybridAmp
741

J
JJ Kasper 已提交
742
  let html = renderDocument(Document, {
743
    ...renderOpts,
744 745 746 747 748
    // 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,
749
    dangerousAsPath: router.asPath,
750
    ampState,
751
    props,
752
    headTags: await headTags(documentCtx),
753
    isFallback,
754 755
    docProps,
    pathname,
756
    ampPath,
757
    query,
758 759
    inAmpMode,
    hybridAmp,
760 761
    dynamicImportsIds,
    dynamicImports,
762
    files,
763 764
    gsp: !!getStaticProps ? true : undefined,
    gssp: !!getServerSideProps ? true : undefined,
765 766
    gip: hasPageGetInitialProps ? true : undefined,
    appGip: !defaultAppGetInitialProps ? true : undefined,
767
  })
J
JJ Kasper 已提交
768

769
  if (inAmpMode && html) {
J
JJ Kasper 已提交
770 771 772 773 774 775 776
    // inject HTML to AMP_RENDER_TARGET to allow rendering
    // directly to body in AMP mode
    const ampRenderIndex = html.indexOf(AMP_RENDER_TARGET)
    html =
      html.substring(0, ampRenderIndex) +
      `<!-- __NEXT_DATA__ -->${docProps.html}` +
      html.substring(ampRenderIndex + AMP_RENDER_TARGET.length)
777
    html = await optimizeAmp(html, renderOpts.ampOptimizerConfig)
778

779
    if (!renderOpts.ampSkipValidation && renderOpts.ampValidator) {
780 781
      await renderOpts.ampValidator(html, pathname)
    }
J
JJ Kasper 已提交
782
  }
J
JJ Kasper 已提交
783

784
  if (inAmpMode || hybridAmp) {
J
JJ Kasper 已提交
785 786 787
    // fix &amp being escaped for amphtml rel link
    html = html.replace(/&amp;amp=1/g, '&amp=1')
  }
J
JJ Kasper 已提交
788

J
JJ Kasper 已提交
789
  return html
790 791
}

792
function errorToJSON(err: Error): Error {
793 794 795 796
  const { name, message, stack } = err
  return { name, message, stack }
}

797 798
function serializeError(
  dev: boolean | undefined,
J
Joe Haddad 已提交
799
  err: Error
800
): Error & { statusCode?: number } {
801 802 803 804
  if (dev) {
    return errorToJSON(err)
  }

805 806 807 808 809
  return {
    name: 'Internal Server Error.',
    message: '500 - Internal Server Error.',
    statusCode: 500,
  }
810
}