2019-02-14 16:22:57 +01:00
import { IncomingMessage , ServerResponse } from 'http'
2018-12-13 01:00:46 +01:00
import { ParsedUrlQuery } from 'querystring'
import React from 'react'
import { renderToString , renderToStaticMarkup } from 'react-dom/server'
2019-07-11 19:35:39 +02:00
import { NextRouter } from '../lib/router/router'
2019-05-30 03:19:32 +02:00
import mitt , { MittEmitter } from '../lib/mitt'
import {
loadGetInitialProps ,
isResSent ,
getDisplayName ,
ComponentsEnhancer ,
RenderPage ,
DocumentInitialProps ,
NextComponentType ,
DocumentType ,
AppType ,
NextPageContext ,
} from '../lib/utils'
2018-12-13 01:00:46 +01:00
import Head , { defaultHead } from '../lib/head'
2019-04-24 16:47:50 +02:00
// @ts-ignore types will be added later as it's an internal module
2018-12-13 01:00:46 +01:00
import Loadable from '../lib/loadable'
2019-04-02 16:09:34 +02:00
import { DataManagerContext } from '../lib/data-manager-context'
2019-04-22 19:55:03 +02:00
import { LoadableContext } from '../lib/loadable-context'
2019-04-02 16:09:34 +02:00
import { RouterContext } from '../lib/router-context'
2019-04-05 21:43:40 +02:00
import { DataManager } from '../lib/data-manager'
2019-02-14 16:22:57 +01:00
import { getPageFiles , BuildManifest } from './get-page-files'
2019-06-27 16:22:24 +02:00
import { AmpStateContext } from '../lib/amp-context'
2019-04-02 20:01:34 +02:00
import optimizeAmp from './optimize-amp'
2019-06-27 16:22:24 +02:00
import { isInAmpMode } from '../lib/amp'
2019-09-04 16:00:54 +02:00
// Uses a module path because of the compiled output directory location
import { PageConfig } from 'next/types'
2019-09-24 10:50:04 +02:00
import { isDynamicRoute } from '../lib/router/utils/is-dynamic'
import { SPR_GET_INITIAL_PROPS_CONFLICT } from '../../lib/constants'
2018-12-13 01:00:46 +01:00
2019-06-29 02:48:28 +02:00
export type ManifestItem = {
id : number | string
name : string
file : string
publicPath : string
}
type ReactLoadableManifest = { [ moduleId : string ] : ManifestItem [ ] }
2019-03-07 17:13:38 +01:00
function noRouter() {
2019-05-30 03:19:32 +02:00
const message =
'No router instance found. you should only use "next/router" inside the client side of your app. https://err.sh/zeit/next.js/no-router-instance'
2019-03-07 17:13:38 +01:00
throw new Error ( message )
}
2019-07-11 19:35:39 +02:00
class ServerRouter implements NextRouter {
2019-03-13 15:56:20 +01:00
route : string
pathname : string
2019-05-20 17:21:15 +02:00
query : ParsedUrlQuery
2019-03-13 15:56:20 +01:00
asPath : string
2019-07-11 19:35:39 +02:00
events : any
2019-03-13 15:56:20 +01:00
// 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 ( )
2019-05-20 17:21:15 +02:00
constructor ( pathname : string , query : ParsedUrlQuery , as : string ) {
2019-03-23 23:00:46 +01:00
this . route = pathname . replace ( /\/$/ , '' ) || '/'
2019-03-13 15:56:20 +01:00
this . pathname = pathname
this . query = query
this . asPath = as
}
2019-07-11 19:35:39 +02:00
push ( ) : any {
2019-03-07 17:13:38 +01:00
noRouter ( )
}
2019-07-11 19:35:39 +02:00
replace ( ) : any {
2019-03-07 17:13:38 +01:00
noRouter ( )
}
reload() {
noRouter ( )
}
back() {
noRouter ( )
}
2019-07-11 19:35:39 +02:00
prefetch ( ) : any {
2019-03-07 17:13:38 +01:00
noRouter ( )
}
beforePopState() {
noRouter ( )
}
}
2019-02-14 16:22:57 +01:00
function enhanceComponents (
options : ComponentsEnhancer ,
2019-04-22 19:55:03 +02:00
App : AppType ,
2019-05-30 03:19:32 +02:00
Component : NextComponentType
2019-02-14 16:22:57 +01:00
) : {
2019-05-30 03:19:32 +02:00
App : AppType
Component : NextComponentType
2018-12-13 01:00:46 +01:00
} {
// For backwards compatibility
2019-02-08 11:57:29 +01:00
if ( typeof options === 'function' ) {
2018-12-13 01:00:46 +01:00
return {
2019-02-08 11:57:29 +01:00
App ,
Component : options ( Component ) ,
2018-12-13 01:00:46 +01:00
}
}
return {
App : options.enhanceApp ? options . enhanceApp ( App ) : App ,
2019-02-14 16:22:57 +01:00
Component : options.enhanceComponent
? options . enhanceComponent ( Component )
: Component ,
2018-12-13 01:00:46 +01:00
}
}
2019-02-14 16:22:57 +01:00
function render (
renderElementToString : ( element : React.ReactElement < any > ) = > string ,
element : React.ReactElement < any > ,
2019-05-30 03:19:32 +02:00
ampMode : any
2019-04-22 19:55:03 +02:00
) : { html : string ; head : React.ReactElement [ ] } {
2018-12-13 01:00:46 +01:00
let html
let head
try {
html = renderElementToString ( element )
} finally {
2019-07-25 18:39:09 +02:00
head = Head . rewind ( ) || defaultHead ( isInAmpMode ( ampMode ) )
2018-12-13 01:00:46 +01:00
}
return { html , head }
}
type RenderOpts = {
2019-06-24 17:31:27 +02:00
documentMiddlewareEnabled : boolean
2019-04-02 16:09:34 +02:00
ampBindInitData : boolean
2019-02-14 16:22:57 +01:00
staticMarkup : boolean
buildId : string
2019-05-29 02:32:18 +02:00
canonicalBase : string
2019-02-14 16:22:57 +01:00
runtimeConfig ? : { [ key : string ] : any }
2019-04-15 11:26:23 +02:00
dangerousAsPath : string
2019-02-14 16:22:57 +01:00
assetPrefix? : string
2019-09-17 22:05:20 +02:00
hasCssMode : boolean
2019-02-14 16:22:57 +01:00
err? : Error | null
2019-09-15 20:35:14 +02:00
autoExport? : boolean
2019-02-14 16:22:57 +01:00
nextExport? : boolean
2019-08-06 22:26:01 +02:00
skeleton? : boolean
2019-02-14 16:22:57 +01:00
dev? : boolean
2019-05-30 03:19:32 +02:00
ampMode? : any
2019-06-27 16:22:24 +02:00
ampPath? : string
2019-05-30 03:19:32 +02:00
dataOnly? : boolean
2019-06-27 16:22:24 +02:00
inAmpMode? : boolean
hybridAmp? : boolean
2019-02-14 16:22:57 +01:00
buildManifest : BuildManifest
reactLoadableManifest : ReactLoadableManifest
2019-07-01 23:13:52 +02:00
pageConfig : PageConfig
2019-02-14 16:22:57 +01:00
Component : React.ComponentType
2019-04-22 19:55:03 +02:00
Document : DocumentType
2019-05-24 04:05:08 +02:00
DocumentMiddleware : ( ctx : NextPageContext ) = > void
2019-04-22 19:55:03 +02:00
App : AppType
2019-05-30 03:19:32 +02:00
ErrorDebug? : React.ComponentType < { error : Error } >
ampValidator ? : ( html : string , pathname : string ) = > Promise < void >
2019-09-24 10:50:04 +02:00
unstable_getStaticProps ? : ( params : {
params : any | undefined
} ) = > {
props : any
revalidate : number | false
}
2018-12-13 01:00:46 +01:00
}
2019-02-14 16:22:57 +01:00
function renderDocument (
2019-04-22 19:55:03 +02:00
Document : DocumentType ,
2019-02-14 16:22:57 +01:00
{
2019-04-02 16:09:34 +02:00
dataManagerData ,
2019-02-14 16:22:57 +01:00
props ,
docProps ,
pathname ,
query ,
buildId ,
2019-05-29 02:32:18 +02:00
canonicalBase ,
2019-02-14 16:22:57 +01:00
assetPrefix ,
runtimeConfig ,
nextExport ,
2019-09-15 20:35:14 +02:00
autoExport ,
2019-08-06 22:26:01 +02:00
skeleton ,
2019-02-14 16:22:57 +01:00
dynamicImportsIds ,
2019-04-15 11:26:23 +02:00
dangerousAsPath ,
2019-09-17 22:05:20 +02:00
hasCssMode ,
2019-02-14 16:22:57 +01:00
err ,
dev ,
2019-03-20 04:53:47 +01:00
ampPath ,
2019-06-27 16:22:24 +02:00
ampState ,
inAmpMode ,
hybridAmp ,
2019-02-14 16:22:57 +01:00
staticMarkup ,
devFiles ,
files ,
dynamicImports ,
} : RenderOpts & {
2019-05-30 03:19:32 +02:00
dataManagerData : string
2019-02-14 16:22:57 +01:00
props : any
2019-04-26 09:37:57 +02:00
docProps : DocumentInitialProps
2019-02-14 16:22:57 +01:00
pathname : string
query : ParsedUrlQuery
2019-04-15 11:26:23 +02:00
dangerousAsPath : string
2019-06-27 16:22:24 +02:00
ampState : any
2019-05-30 03:19:32 +02:00
ampPath : string
2019-06-27 16:22:24 +02:00
inAmpMode : boolean
hybridAmp : boolean
2019-02-14 16:22:57 +01:00
dynamicImportsIds : string [ ]
dynamicImports : ManifestItem [ ]
files : string [ ]
2019-05-30 03:19:32 +02:00
devFiles : string [ ]
}
2019-02-14 16:22:57 +01:00
) : string {
return (
'<!DOCTYPE html>' +
renderToStaticMarkup (
2019-06-27 16:22:24 +02:00
< AmpStateContext.Provider value = { ampState } >
2019-04-13 02:04:52 +02:00
< Document
__NEXT_DATA__ = { {
dataManager : dataManagerData ,
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`
2019-09-15 20:35:14 +02:00
autoExport , // If this is an auto exported page
2019-08-06 22:26:01 +02:00
skeleton , // If this is a skeleton page for experimentalPrerender
2019-05-30 03:19:32 +02:00
dynamicIds :
dynamicImportsIds . length === 0 ? undefined : dynamicImportsIds ,
2019-04-13 02:04:52 +02:00
err : err ? serializeError ( dev , err ) : undefined , // Error if one happened, otherwise don't sent in the resulting HTML
} }
2019-04-15 11:26:23 +02:00
dangerousAsPath = { dangerousAsPath }
2019-05-29 02:32:18 +02:00
canonicalBase = { canonicalBase }
2019-04-13 02:04:52 +02:00
ampPath = { ampPath }
2019-06-27 16:22:24 +02:00
inAmpMode = { inAmpMode }
2019-09-17 22:05:20 +02:00
isDevelopment = { ! ! dev }
hasCssMode = { hasCssMode }
2019-06-27 16:22:24 +02:00
hybridAmp = { hybridAmp }
2019-04-13 02:04:52 +02:00
staticMarkup = { staticMarkup }
devFiles = { devFiles }
files = { files }
dynamicImports = { dynamicImports }
assetPrefix = { assetPrefix }
{ . . . docProps }
/ >
2019-06-27 16:22:24 +02:00
< / AmpStateContext.Provider >
2019-02-14 16:22:57 +01:00
)
2018-12-13 01:00:46 +01:00
)
}
2019-02-14 16:22:57 +01:00
export async function renderToHTML (
req : IncomingMessage ,
res : ServerResponse ,
pathname : string ,
query : ParsedUrlQuery ,
2019-05-30 03:19:32 +02:00
renderOpts : RenderOpts
2019-02-14 16:22:57 +01:00
) : Promise < string | null > {
2019-03-12 12:40:49 +01:00
pathname = pathname === '/index' ? '/' : pathname
2018-12-13 01:00:46 +01:00
const {
err ,
dev = false ,
2019-06-24 17:31:27 +02:00
documentMiddlewareEnabled = false ,
2019-04-02 16:09:34 +02:00
ampBindInitData = false ,
2018-12-18 17:12:49 +01:00
staticMarkup = false ,
2019-03-20 04:53:47 +01:00
ampPath = '' ,
2018-12-18 17:12:49 +01:00
App ,
Document ,
2019-07-16 02:06:16 +02:00
pageConfig = { } ,
2019-05-24 04:05:08 +02:00
DocumentMiddleware ,
2018-12-18 17:12:49 +01:00
Component ,
buildManifest ,
reactLoadableManifest ,
2019-02-08 11:57:29 +01:00
ErrorDebug ,
2019-09-24 10:50:04 +02:00
unstable_getStaticProps ,
2018-12-13 01:00:46 +01:00
} = renderOpts
2018-12-18 17:12:49 +01:00
2019-09-24 10:50:04 +02:00
const isSpr = ! ! unstable_getStaticProps
2019-09-15 20:35:14 +02:00
const defaultAppGetInitialProps =
App . getInitialProps === ( App as any ) . origGetInitialProps
2019-09-24 10:50:04 +02:00
const hasPageGetInitialProps = ! ! ( Component as any ) . getInitialProps
2019-09-15 20:35:14 +02:00
2019-09-24 10:50:04 +02:00
const isAutoExport =
! hasPageGetInitialProps && defaultAppGetInitialProps && ! isSpr
if ( hasPageGetInitialProps && isSpr ) {
throw new Error ( SPR_GET_INITIAL_PROPS_CONFLICT + ` ${ pathname } ` )
}
2018-12-13 01:00:46 +01:00
2018-12-17 16:09:23 +01:00
if ( dev ) {
const { isValidElementType } = require ( 'react-is' )
if ( ! isValidElementType ( Component ) ) {
2019-02-14 16:22:57 +01:00
throw new Error (
2019-05-30 03:19:32 +02:00
` The default export is not a React Component in page: " ${ pathname } " `
2019-02-14 16:22:57 +01:00
)
2018-12-17 16:09:23 +01:00
}
2018-12-17 17:42:40 +01:00
if ( ! isValidElementType ( App ) ) {
2019-02-14 16:22:57 +01:00
throw new Error (
2019-05-30 03:19:32 +02:00
` The default export is not a React Component in page: "/_app" `
2019-02-14 16:22:57 +01:00
)
2018-12-17 17:42:40 +01:00
}
if ( ! isValidElementType ( Document ) ) {
2019-02-14 16:22:57 +01:00
throw new Error (
2019-05-30 03:19:32 +02:00
` The default export is not a React Component in page: "/_document" `
2019-02-14 16:22:57 +01:00
)
2018-12-17 17:42:40 +01:00
}
2019-05-22 18:36:53 +02:00
2019-09-24 10:50:04 +02:00
if ( isAutoExport ) {
2019-06-28 22:01:11 +02:00
// remove query values except ones that will be set during export
query = {
amp : query.amp ,
2019-05-22 18:36:53 +02:00
}
2019-08-16 18:53:47 +02:00
req . url = pathname
2019-06-28 22:01:11 +02:00
renderOpts . nextExport = true
2019-05-22 18:36:53 +02:00
}
2018-12-17 17:42:40 +01:00
}
2019-09-15 20:35:14 +02:00
if ( isAutoExport ) renderOpts . autoExport = true
2018-12-13 01:00:46 +01:00
2019-09-24 10:50:04 +02:00
await Loadable . preloadAll ( ) // Make sure all dynamic imports are loaded
2019-03-13 15:56:20 +01:00
// @ts-ignore url will always be set
const asPath : string = req . url
2019-08-13 11:33:48 +02:00
const router = new ServerRouter ( pathname , query , asPath )
2019-05-31 02:34:05 +02:00
const ctx = {
err ,
2019-09-24 10:50:04 +02:00
req : isAutoExport ? undefined : req ,
res : isAutoExport ? undefined : res ,
2019-05-31 02:34:05 +02:00
pathname ,
query ,
asPath ,
2019-08-13 11:33:48 +02:00
AppTree : ( props : any ) = > {
return (
< AppContainer >
< App { ...props } Component = { Component } router = { router } / >
< / AppContainer >
)
} ,
2019-05-31 02:34:05 +02:00
}
2019-08-13 11:33:48 +02:00
let props : any
2019-03-17 17:43:03 +01:00
2019-06-24 17:31:27 +02:00
if ( documentMiddlewareEnabled && typeof DocumentMiddleware === 'function' ) {
2019-05-24 04:05:08 +02:00
await DocumentMiddleware ( ctx )
}
2019-07-03 04:16:12 +02:00
let dataManager : DataManager | undefined
if ( ampBindInitData ) {
dataManager = new DataManager ( )
}
const ampState = {
ampFirst : pageConfig.amp === true ,
hasQuery : Boolean ( query . amp ) ,
hybrid : pageConfig.amp === 'hybrid' ,
}
const reactLoadableModules : string [ ] = [ ]
const AppContainer = ( { children } : any ) = > (
2019-07-30 20:00:19 +02:00
< RouterContext.Provider value = { router } >
< DataManagerContext.Provider value = { dataManager } >
< AmpStateContext.Provider value = { ampState } >
< LoadableContext.Provider
value = { moduleName = > reactLoadableModules . push ( moduleName ) }
>
{ children }
< / LoadableContext.Provider >
< / AmpStateContext.Provider >
< / DataManagerContext.Provider >
< / RouterContext.Provider >
2019-07-03 04:16:12 +02:00
)
2019-03-17 17:43:03 +01:00
try {
2019-09-24 10:50:04 +02:00
props = await loadGetInitialProps ( App , {
AppTree : ctx.AppTree ,
Component ,
router ,
ctx ,
} )
if ( isSpr ) {
const data = await unstable_getStaticProps ! ( {
params : isDynamicRoute ( pathname ) ? query : undefined ,
} )
2019-09-25 17:29:22 +02:00
const invalidKeys = Object . keys ( data ) . filter (
key = > key !== 'revalidate' && key !== 'props'
)
if ( invalidKeys . length ) {
throw new Error (
` Additional keys were returned from \` getStaticProps \` . 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 moved: ${ invalidKeys . join (
', '
) } .
`
)
}
if ( typeof data . revalidate === 'number' ) {
if ( data . revalidate < 0 ) {
throw new Error (
` A page's revalidate option can not be less than zero. A revalidate option of zero means to revalidate _after_ every request. To never revalidate, you can set revalidate to \` false \` (only ran once at build-time). `
)
} 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. 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 \` ! `
)
}
}
2019-09-24 10:50:04 +02:00
props . pageProps = data . props
// pass up revalidate and props for export
; ( renderOpts as any ) . revalidate = data . revalidate
; ( renderOpts as any ) . sprData = props
}
2019-03-17 17:43:03 +01:00
} catch ( err ) {
if ( ! dev || ! err ) throw err
ctx . err = err
renderOpts . err = err
}
2018-12-13 01:00:46 +01:00
2018-12-17 16:09:23 +01:00
// the response might be finished on the getInitialProps call
2019-09-24 10:50:04 +02:00
if ( isResSent ( res ) && ! isSpr ) return null
2018-12-13 01:00:46 +01:00
const devFiles = buildManifest . devFiles
const files = [
. . . new Set ( [
. . . getPageFiles ( buildManifest , pathname ) ,
. . . getPageFiles ( buildManifest , '/_app' ) ,
2019-02-08 11:57:29 +01:00
] ) ,
2018-12-13 01:00:46 +01:00
]
2019-04-02 16:09:34 +02:00
const renderElementToString = staticMarkup
? renderToStaticMarkup
: renderToString
2019-05-30 03:19:32 +02:00
const renderPageError = ( ) : { html : string ; head : any } | void = > {
2019-04-03 00:32:07 +02:00
if ( ctx . err && ErrorDebug ) {
2019-05-30 03:19:32 +02:00
return render (
renderElementToString ,
< ErrorDebug error = { ctx . err } / > ,
2019-06-27 16:22:24 +02:00
ampState
2019-05-30 03:19:32 +02:00
)
2019-04-03 00:32:07 +02:00
}
if ( dev && ( props . router || props . Component ) ) {
throw new Error (
2019-05-30 03:19:32 +02:00
` 'router' and 'Component' can not be returned in getInitialProps from _app.js https://err.sh/zeit/next.js/cant-override-next-props `
2019-04-03 00:32:07 +02:00
)
}
}
2019-04-22 19:55:03 +02:00
let renderPage : RenderPage
2019-04-02 16:09:34 +02:00
if ( ampBindInitData ) {
2019-04-21 20:47:02 +02:00
const ssrPrepass = require ( 'react-ssr-prepass' )
2019-04-02 16:09:34 +02:00
renderPage = async (
2019-05-30 03:19:32 +02:00
options : ComponentsEnhancer = { }
) : Promise < { html : string ; head : any ; dataOnly? : true } > = > {
2019-04-03 00:32:07 +02:00
const renderError = renderPageError ( )
if ( renderError ) return renderError
2019-04-02 16:09:34 +02:00
const {
App : EnhancedApp ,
Component : EnhancedComponent ,
} = enhanceComponents ( options , App , Component )
2019-05-30 03:19:32 +02:00
const Application = ( ) = > (
2019-07-03 04:16:12 +02:00
< AppContainer >
< EnhancedApp
Component = { EnhancedComponent }
router = { router }
{ . . . props }
/ >
< / AppContainer >
2019-05-30 03:19:32 +02:00
)
2019-04-05 12:32:00 +02:00
2019-05-30 03:19:32 +02:00
const element = < Application / >
2019-04-05 19:43:30 +02:00
try {
2019-06-27 16:22:24 +02:00
return render ( renderElementToString , element , ampState )
2019-04-05 19:43:30 +02:00
} catch ( err ) {
if ( err && typeof err === 'object' && typeof err . then === 'function' ) {
await ssrPrepass ( element )
if ( renderOpts . dataOnly ) {
return {
html : '' ,
head : [ ] ,
dataOnly : true ,
}
} else {
2019-06-27 16:22:24 +02:00
return render ( renderElementToString , element , ampState )
2019-04-05 19:43:30 +02:00
}
2019-04-02 16:09:34 +02:00
}
2019-04-05 19:43:30 +02:00
throw err
2019-04-02 16:09:34 +02:00
}
}
} else {
renderPage = (
2019-05-30 03:19:32 +02:00
options : ComponentsEnhancer = { }
2019-04-03 00:32:07 +02:00
) : { html : string ; head : any } = > {
const renderError = renderPageError ( )
if ( renderError ) return renderError
2018-12-13 01:00:46 +01:00
2019-04-03 00:32:07 +02:00
const {
App : EnhancedApp ,
Component : EnhancedComponent ,
} = enhanceComponents ( options , App , Component )
return render (
renderElementToString ,
2019-07-03 04:16:12 +02:00
< AppContainer >
< EnhancedApp
Component = { EnhancedComponent }
router = { router }
{ . . . props }
/ >
< / AppContainer > ,
2019-06-27 16:22:24 +02:00
ampState
2019-03-01 20:51:13 +01:00
)
2019-03-01 18:08:27 +01:00
}
2019-04-02 16:09:34 +02:00
}
2018-12-13 01:00:46 +01:00
const docProps = await loadGetInitialProps ( Document , { . . . ctx , renderPage } )
2019-01-02 20:21:57 +01:00
// the response might be finished on the getInitialProps call
2019-09-24 10:50:04 +02:00
if ( isResSent ( res ) && ! isSpr ) return null
2018-12-13 01:00:46 +01:00
2019-04-02 16:09:34 +02:00
let dataManagerData = '[]'
if ( dataManager ) {
dataManagerData = JSON . stringify ( [ . . . dataManager . getData ( ) ] )
}
2019-04-22 19:55:03 +02:00
if ( ! docProps || typeof docProps . html !== 'string' ) {
2019-05-30 03:19:32 +02:00
const message = ` " ${ getDisplayName (
Document
) } . getInitialProps ( ) " should resolve to an object with a " html " prop set with a valid html string `
2019-04-22 19:55:03 +02:00
throw new Error ( message )
}
2019-04-05 12:32:00 +02:00
if ( docProps . dataOnly ) {
2019-04-02 16:09:34 +02:00
return dataManagerData
}
2019-06-29 02:48:28 +02:00
const dynamicImportIdsSet = new Set < string > ( )
const dynamicImports : ManifestItem [ ] = [ ]
for ( const mod of reactLoadableModules ) {
const manifestItem = reactLoadableManifest [ mod ]
if ( manifestItem ) {
manifestItem . map ( item = > {
dynamicImports . push ( item )
dynamicImportIdsSet . add ( item . id as string )
} )
}
}
const dynamicImportsIds = [ . . . dynamicImportIdsSet ]
2019-06-27 16:22:24 +02:00
const inAmpMode = isInAmpMode ( ampState )
const hybridAmp = ampState . hybrid
2019-07-01 23:13:52 +02:00
// update renderOpts so export knows current state
2019-06-27 16:22:24 +02:00
renderOpts . inAmpMode = inAmpMode
renderOpts . hybridAmp = hybridAmp
2019-04-05 12:32:00 +02:00
2019-04-02 20:01:34 +02:00
let html = renderDocument ( Document , {
2018-12-13 01:00:46 +01:00
. . . renderOpts ,
2019-04-15 11:26:23 +02:00
dangerousAsPath : router.asPath ,
2019-04-02 16:09:34 +02:00
dataManagerData ,
2019-06-27 16:22:24 +02:00
ampState ,
2018-12-13 01:00:46 +01:00
props ,
docProps ,
pathname ,
2019-03-20 04:53:47 +01:00
ampPath ,
2018-12-13 01:00:46 +01:00
query ,
2019-06-27 16:22:24 +02:00
inAmpMode ,
hybridAmp ,
2018-12-13 01:00:46 +01:00
dynamicImportsIds ,
dynamicImports ,
files ,
2019-02-08 11:57:29 +01:00
devFiles ,
2018-12-13 01:00:46 +01:00
} )
2019-04-02 20:01:34 +02:00
2019-06-27 16:22:24 +02:00
if ( inAmpMode && html ) {
2019-04-25 10:14:52 +02:00
// use replace to allow rendering directly to body in AMP mode
2019-06-02 11:38:47 +02:00
html = html . replace (
'__NEXT_AMP_RENDER_TARGET__' ,
` <!-- __NEXT_DATA__ --> ${ docProps . html } `
)
2019-05-30 04:53:41 +02:00
html = await optimizeAmp ( html )
2019-04-11 20:59:26 +02:00
2019-04-20 03:33:20 +02:00
if ( renderOpts . ampValidator ) {
2019-04-11 20:59:26 +02:00
await renderOpts . ampValidator ( html , pathname )
}
2019-04-02 20:01:34 +02:00
}
2019-04-16 15:57:17 +02:00
2019-06-27 16:22:24 +02:00
if ( inAmpMode || hybridAmp ) {
2019-04-16 15:57:17 +02:00
// fix & being escaped for amphtml rel link
html = html . replace ( /&amp=1/g , '&=1' )
}
2019-09-24 10:50:04 +02:00
2019-04-02 20:01:34 +02:00
return html
2018-12-13 01:00:46 +01:00
}
2019-02-08 11:57:29 +01:00
function errorToJSON ( err : Error ) : Error {
2018-12-13 01:00:46 +01:00
const { name , message , stack } = err
return { name , message , stack }
}
2019-02-14 16:22:57 +01:00
function serializeError (
dev : boolean | undefined ,
2019-05-30 03:19:32 +02:00
err : Error
2019-02-14 16:22:57 +01:00
) : Error & { statusCode? : number } {
2018-12-13 01:00:46 +01:00
if ( dev ) {
return errorToJSON ( err )
}
2019-02-14 16:22:57 +01:00
return {
name : 'Internal Server Error.' ,
message : '500 - Internal Server Error.' ,
statusCode : 500 ,
}
2018-12-13 01:00:46 +01:00
}