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'
2020-02-12 02:16:42 +01:00
import { renderToStaticMarkup , renderToString } from 'react-dom/server'
2019-05-30 03:19:32 +02:00
import {
2020-02-12 02:16:42 +01:00
PAGES_404_GET_INITIAL_PROPS_ERROR ,
SERVER_PROPS_GET_INIT_PROPS_CONFLICT ,
SERVER_PROPS_SSG_CONFLICT ,
SSG_GET_INITIAL_PROPS_CONFLICT ,
} from '../../lib/constants'
2020-03-09 18:30:44 +01:00
import { isSerializableProps } from '../../lib/is-serializable-props'
2020-02-12 02:16:42 +01:00
import { isInAmpMode } from '../lib/amp'
import { AmpStateContext } from '../lib/amp-context'
2020-03-06 05:15:10 +01:00
import {
AMP_RENDER_TARGET ,
STATIC_PROPS_ID ,
SERVER_PROPS_ID ,
} from '../lib/constants'
2018-12-13 01:00:46 +01:00
import Head , { defaultHead } from '../lib/head'
import Loadable from '../lib/loadable'
2019-04-22 19:55:03 +02:00
import { LoadableContext } from '../lib/loadable-context'
2020-02-12 02:16:42 +01:00
import mitt , { MittEmitter } from '../lib/mitt'
2019-04-02 16:09:34 +02:00
import { RouterContext } from '../lib/router-context'
2020-02-12 02:16:42 +01:00
import { NextRouter } from '../lib/router/router'
2019-09-24 10:50:04 +02:00
import { isDynamicRoute } from '../lib/router/utils/is-dynamic'
2020-01-27 23:50:59 +01:00
import {
2020-02-12 02:16:42 +01:00
AppType ,
ComponentsEnhancer ,
DocumentInitialProps ,
DocumentType ,
getDisplayName ,
isResSent ,
loadGetInitialProps ,
NextComponentType ,
RenderPage ,
} from '../lib/utils'
import { tryGetPreviewData , __ApiPreviewProps } from './api-utils'
import { getPageFiles } from './get-page-files'
2020-01-25 05:25:11 +01:00
import { LoadComponentsReturnType , ManifestItem } from './load-components'
2020-02-12 02:16:42 +01:00
import optimizeAmp from './optimize-amp'
2019-06-29 02:48:28 +02:00
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
2020-02-15 19:01:10 +01:00
isFallback : boolean
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 ( )
2020-02-15 19:01:10 +01:00
constructor (
pathname : string ,
query : ParsedUrlQuery ,
as : string ,
{ isFallback } : { isFallback : boolean }
) {
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
2020-02-15 19:01:10 +01:00
this . isFallback = isFallback
2019-03-13 15:56:20 +01:00
}
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 }
}
2020-03-02 11:58:47 +01:00
export type RenderOptsPartial = {
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 }
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
dev? : boolean
2019-05-30 03:19:32 +02:00
ampMode? : any
2019-06-27 16:22:24 +02:00
ampPath? : string
inAmpMode? : boolean
hybridAmp? : boolean
2019-05-30 03:19:32 +02:00
ErrorDebug? : React.ComponentType < { error : Error } >
ampValidator ? : ( html : string , pathname : string ) = > Promise < void >
2020-03-24 09:31:04 +01:00
ampSkipValidation? : boolean
ampOptimizerConfig ? : { [ key : string ] : any }
2020-01-25 05:25:11 +01:00
documentMiddlewareEnabled? : boolean
2020-01-27 23:50:59 +01:00
isDataReq? : boolean
params? : ParsedUrlQuery
2020-02-12 02:16:42 +01:00
previewProps : __ApiPreviewProps
2018-12-13 01:00:46 +01:00
}
2020-03-02 11:58:47 +01:00
export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
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
{
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 ,
2020-02-07 14:09:06 +01:00
isFallback ,
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 ,
2020-02-29 23:09:42 +01:00
lowPriorityFiles ,
2019-11-02 02:00:56 +01:00
polyfillFiles ,
2019-02-14 16:22:57 +01:00
dynamicImports ,
2019-11-01 20:13:13 +01:00
htmlProps ,
bodyTags ,
headTags ,
2020-03-03 19:39:08 +01:00
gsp ,
gssp ,
2020-03-06 17:14:39 +01:00
customServer ,
2019-02-14 16:22:57 +01:00
} : RenderOpts & {
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 [ ]
2019-05-30 03:19:32 +02:00
devFiles : string [ ]
2020-02-29 23:09:42 +01:00
files : string [ ]
lowPriorityFiles : string [ ]
2019-11-02 02:00:56 +01:00
polyfillFiles : string [ ]
2019-11-01 20:13:13 +01:00
htmlProps : any
bodyTags : any
headTags : any
2020-02-07 14:09:06 +01:00
isFallback? : boolean
2020-03-03 19:39:08 +01:00
gsp? : boolean
gssp? : boolean
2020-03-06 17:14:39 +01:00
customServer? : boolean
2019-05-30 03:19:32 +02:00
}
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-10-14 18:45:56 +02:00
{ Document . renderDocument ( Document , {
__NEXT_DATA__ : {
2019-04-13 02:04:52 +02:00
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
2020-02-07 14:09:06 +01:00
isFallback ,
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
2020-03-03 19:39:08 +01:00
gsp , // whether the page is getStaticProps
gssp , // whether the page is getServerSideProps
2020-03-06 17:14:39 +01:00
customServer , // whether the user is using a custom server
2019-10-14 18:45:56 +02:00
} ,
dangerousAsPath ,
canonicalBase ,
ampPath ,
inAmpMode ,
isDevelopment : ! ! dev ,
hasCssMode ,
hybridAmp ,
staticMarkup ,
devFiles ,
files ,
2020-02-29 23:09:42 +01:00
lowPriorityFiles ,
2019-11-02 02:00:56 +01:00
polyfillFiles ,
2019-10-14 18:45:56 +02:00
dynamicImports ,
assetPrefix ,
2019-11-01 20:13:13 +01:00
htmlProps ,
bodyTags ,
headTags ,
2019-10-14 18:45:56 +02:00
. . . 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
)
}
2020-01-27 23:50:59 +01:00
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: '...' } } ` +
2020-02-21 06:45:50 +01:00
` \ n \ nKeys that need to be moved: ${ invalidKeys . join ( ', ' ) } . ` +
` \ nRead more: https://err.sh/next.js/invalid-getstaticprops-value `
2020-01-27 23:50:59 +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 ,
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 ,
2020-02-27 18:57:39 +01:00
getStaticProps ,
getStaticPaths ,
getServerSideProps ,
2020-01-27 23:50:59 +01:00
isDataReq ,
params ,
2020-02-12 02:16:42 +01:00
previewProps ,
2018-12-13 01:00:46 +01:00
} = renderOpts
2018-12-18 17:12:49 +01:00
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 ` ] ) {
2019-11-15 08:33:52 +01:00
let middlewareFunc = await ( Document as any ) [ ` ${ method } Middleware ` ]
middlewareFunc = middlewareFunc . default || middlewareFunc
const curResults = await middlewareFunc ( . . . args )
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 bodyTags = ( . . . args : any ) = > callMiddleware ( 'bodyTags' , args )
const htmlProps = ( . . . args : any ) = > callMiddleware ( 'htmlProps' , args , true )
2020-02-08 04:23:15 +01:00
const didRewrite = ( req as any ) . _nextDidRewrite
2020-02-07 14:09:06 +01:00
const isFallback = ! ! query . __nextFallback
delete query . __nextFallback
2020-03-04 13:58:12 +01:00
const isSSG = ! ! getStaticProps
2020-03-09 18:30:44 +01:00
const isBuildTimeSSG = isSSG && renderOpts . nextExport
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
2020-02-21 06:57:10 +01:00
const pageIsDynamic = isDynamicRoute ( pathname )
2019-09-24 10:50:04 +02:00
const isAutoExport =
2020-01-27 23:50:59 +01:00
! hasPageGetInitialProps &&
defaultAppGetInitialProps &&
2020-03-04 13:58:12 +01:00
! isSSG &&
2020-02-27 18:57:39 +01:00
! getServerSideProps
2019-09-24 10:50:04 +02:00
2019-12-30 20:06:38 +01:00
if (
process . env . NODE_ENV !== 'production' &&
2020-02-08 04:23:15 +01:00
( isAutoExport || isFallback ) &&
2020-02-21 06:57:10 +01:00
pageIsDynamic &&
2020-02-08 04:23:15 +01:00
didRewrite
2019-12-30 20:06:38 +01:00
) {
// TODO: add err.sh when rewrites go stable
2020-02-08 04:23:15 +01:00
// Behavior might change before then (prefer SSR in this case).
// If we decide to ship rewrites to the client we could solve this
// by running over the rewrites and getting the params.
2019-12-30 20:06:38 +01:00
throw new Error (
2020-02-08 04:23:15 +01:00
` Rewrites don't support ${
isFallback ? ' ' : ' auto-exported '
} dynamic pages $ { isFallback ? ' with getStaticProps ' : ' ' } yet . ` +
` Using this will cause the page to fail to parse the params on the client ${
isFallback ? ' for the fallback page ' : ''
} `
2019-12-30 20:06:38 +01:00
)
}
2020-03-04 13:58:12 +01:00
if ( hasPageGetInitialProps && isSSG ) {
2020-01-15 02:22:15 +01:00
throw new Error ( SSG_GET_INITIAL_PROPS_CONFLICT + ` ${ pathname } ` )
2019-09-24 10:50:04 +02:00
}
2018-12-13 01:00:46 +01:00
2020-02-27 18:57:39 +01:00
if ( hasPageGetInitialProps && getServerSideProps ) {
2020-01-27 23:50:59 +01:00
throw new Error ( SERVER_PROPS_GET_INIT_PROPS_CONFLICT + ` ${ pathname } ` )
}
2020-03-04 13:58:12 +01:00
if ( getServerSideProps && isSSG ) {
2020-01-27 23:50:59 +01:00
throw new Error ( SERVER_PROPS_SSG_CONFLICT + ` ${ pathname } ` )
}
2020-03-04 13:58:12 +01:00
if ( ! ! getStaticPaths && ! isSSG ) {
2019-10-28 18:24:29 +01:00
throw new Error (
2020-02-27 18:57:39 +01:00
` getStaticPaths was added without a getStaticProps in ${ pathname } . Without getStaticProps, getStaticPaths does nothing `
2019-10-28 18:24:29 +01:00
)
}
2020-03-04 13:58:12 +01:00
if ( isSSG && pageIsDynamic && ! getStaticPaths ) {
2020-02-21 06:57:10 +01:00
throw new Error (
2020-02-27 18:57:39 +01:00
` getStaticPaths is required for dynamic SSG pages and is missing for ' ${ pathname } '. ` +
2020-02-21 06:57:10 +01:00
` \ nRead more: https://err.sh/next.js/invalid-getstaticpaths-value `
)
}
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
}
2020-02-01 15:47:42 +01:00
2020-03-12 10:48:14 +01:00
if ( pathname === '/404' && ( hasPageGetInitialProps || getServerSideProps ) ) {
2020-02-01 15:47:42 +01:00
throw new Error ( PAGES_404_GET_INITIAL_PROPS_ERROR )
}
2018-12-17 17:42:40 +01:00
}
2019-09-15 20:35:14 +02:00
if ( isAutoExport ) renderOpts . autoExport = true
2020-03-04 13:58:12 +01:00
if ( isSSG ) renderOpts . nextExport = false
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
2020-02-17 22:16:19 +01:00
// url will always be set
const asPath = req . url as string
2020-02-15 19:01:10 +01:00
const router = new ServerRouter ( pathname , query , asPath , {
isFallback : isFallback ,
} )
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
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 } >
2020-01-04 17:40:18 +01:00
< AmpStateContext.Provider value = { ampState } >
< LoadableContext.Provider
value = { moduleName = > reactLoadableModules . push ( moduleName ) }
>
{ children }
< / LoadableContext.Provider >
< / AmpStateContext.Provider >
2019-07-30 20:00:19 +02:00
< / 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 ,
} )
2020-03-06 05:15:10 +01:00
if ( isSSG ) {
props [ STATIC_PROPS_ID ] = true
}
2020-03-04 13:58:12 +01:00
let previewData : string | false | object | undefined
2019-09-24 10:50:04 +02:00
2020-03-04 13:58:12 +01:00
if ( ( isSSG || getServerSideProps ) && ! isFallback ) {
2020-02-12 02:16:42 +01:00
// 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.
2020-03-04 13:58:12 +01:00
previewData = tryGetPreviewData ( req , res , previewProps )
}
if ( isSSG && ! isFallback ) {
2020-02-27 18:57:39 +01:00
const data = await getStaticProps ! ( {
2020-02-29 23:36:49 +01:00
. . . ( pageIsDynamic ? { params : query as ParsedUrlQuery } : undefined ) ,
2020-02-12 02:16:42 +01:00
. . . ( previewData !== false
? { preview : true , previewData : previewData }
: undefined ) ,
2019-09-24 10:50:04 +02:00
} )
2019-09-25 17:29:22 +02:00
const invalidKeys = Object . keys ( data ) . filter (
key = > key !== 'revalidate' && key !== 'props'
)
if ( invalidKeys . length ) {
2020-01-27 23:50:59 +01:00
throw new Error ( invalidKeysMsg ( 'getStaticProps' , invalidKeys ) )
2019-09-25 17:29:22 +02:00
}
2020-03-09 18:30:44 +01:00
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.'
)
}
2019-09-25 17:29:22 +02:00
if ( typeof data . revalidate === 'number' ) {
2019-09-27 18:46:19 +02:00
if ( ! Number . isInteger ( data . revalidate ) ) {
2019-09-25 17:29:22 +02:00
throw new Error (
2019-11-11 04:24:53 +01:00
` A page's revalidate option must be seconds expressed as a natural number. Mixed numbers, such as ' ${ data . revalidate } ', cannot be used. ` +
2019-09-27 18:46:19 +02:00
` \ nTry changing the value to ' ${ Math . ceil (
data . revalidate
2019-11-01 20:38:02 +01:00
) } ' or using \`Math.ceil()\` if you' re computing the value . `
2019-09-27 18:46:19 +02:00
)
2019-11-01 20:38:02 +01:00
} else if ( data . revalidate <= 0 ) {
2019-09-27 18:46:19 +02:00
throw new Error (
2019-11-01 20:38:02 +01:00
` 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 \` . `
2019-09-25 17:29:22 +02:00
)
} else if ( data . revalidate > 31536000 ) {
// if it's greater than a year for some reason error
console . warn (
2019-09-27 18:46:19 +02:00
` 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-25 17:29:22 +02:00
)
}
2019-12-11 22:32:16 +01:00
} else if ( data . revalidate === true ) {
// When enabled, revalidate after 1 second. This value is optimal for
2019-09-27 19:36:00 +02:00
// the most up-to-date page possible, but without a 1-to-1
// request-refresh ratio.
data . revalidate = 1
2019-12-11 22:32:16 +01:00
} else {
// By default, we never revalidate.
data . revalidate = false
2019-09-25 17:29:22 +02:00
}
2019-09-24 10:50:04 +02:00
props . pageProps = data . props
// pass up revalidate and props for export
2020-03-02 11:58:47 +01:00
// TODO: change this to a different passing mechanism
2019-09-24 10:50:04 +02:00
; ( renderOpts as any ) . revalidate = data . revalidate
2020-01-15 02:22:15 +01:00
; ( renderOpts as any ) . pageData = props
2019-09-24 10:50:04 +02:00
}
2020-03-04 13:58:12 +01:00
2020-03-06 05:15:10 +01:00
if ( getServerSideProps ) {
props [ SERVER_PROPS_ID ] = true
}
2020-03-04 13:58:12 +01:00
if ( getServerSideProps && ! isFallback ) {
const data = await getServerSideProps ( {
req ,
res ,
query ,
. . . ( pageIsDynamic ? { params : params as ParsedUrlQuery } : undefined ) ,
. . . ( previewData !== false
? { preview : true , previewData : previewData }
: undefined ) ,
} )
const invalidKeys = Object . keys ( data ) . filter ( key = > key !== 'props' )
if ( invalidKeys . length ) {
throw new Error ( invalidKeysMsg ( 'getServerSideProps' , invalidKeys ) )
}
2020-03-09 18:30:44 +01:00
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.'
)
}
2020-03-04 13:58:12 +01:00
props . pageProps = data . props
; ( renderOpts as any ) . pageData = props
}
2019-03-17 17:43:03 +01:00
} catch ( err ) {
2020-03-06 06:53:11 +01:00
if ( isDataReq || ! dev || ! err ) throw err
2019-03-17 17:43:03 +01:00
ctx . err = err
renderOpts . err = err
2020-02-24 10:14:46 +01:00
console . error ( err )
2019-03-17 17:43:03 +01:00
}
2018-12-13 01:00:46 +01:00
2020-02-26 19:26:55 +01:00
if (
2020-03-04 13:58:12 +01:00
! isSSG && // we only show this warning for legacy pages
2020-02-27 18:57:39 +01:00
! getServerSideProps &&
2020-02-26 19:26:55 +01:00
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://err.sh/zeit/next.js/reserved-page-prop `
)
}
2020-01-27 23:50:59 +01:00
// We only need to do this if we want to support calling
2020-02-27 18:04:30 +01:00
// _app's getInitialProps for getServerSideProps if not this can be removed
2020-01-27 23:50:59 +01:00
if ( isDataReq ) return props
2020-02-27 18:04:30 +01:00
// We don't call getStaticProps or getServerSideProps while generating
2020-02-07 14:09:06 +01:00
// the fallback so make sure to set pageProps to an empty object
if ( isFallback ) {
props . pageProps = { }
}
2018-12-17 16:09:23 +01:00
// the response might be finished on the getInitialProps call
2020-03-04 13:58:12 +01:00
if ( isResSent ( res ) && ! isSSG ) return null
2018-12-13 01:00:46 +01:00
const devFiles = buildManifest . devFiles
const files = [
. . . new Set ( [
. . . getPageFiles ( buildManifest , '/_app' ) ,
2020-01-03 18:00:06 +01:00
. . . getPageFiles ( buildManifest , pathname ) ,
2019-02-08 11:57:29 +01:00
] ) ,
2018-12-13 01:00:46 +01:00
]
2020-02-29 23:09:42 +01:00
const lowPriorityFiles = buildManifest . lowPriorityFiles
2019-11-02 02:00:56 +01:00
const polyfillFiles = getPageFiles ( buildManifest , '/_polyfills' )
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
)
}
}
2020-01-04 17:40:18 +01:00
let renderPage : RenderPage = (
options : ComponentsEnhancer = { }
) : { html : string ; head : any } = > {
const renderError = renderPageError ( )
if ( renderError ) return renderError
const {
App : EnhancedApp ,
Component : EnhancedComponent ,
} = enhanceComponents ( options , App , Component )
return render (
renderElementToString ,
< AppContainer >
< EnhancedApp Component = { EnhancedComponent } router = { router } { ...props } / >
< / AppContainer > ,
ampState
)
2019-04-02 16:09:34 +02:00
}
2019-11-01 20:13:13 +01:00
const documentCtx = { . . . ctx , renderPage }
2020-01-27 23:50:59 +01:00
const docProps : DocumentInitialProps = await loadGetInitialProps (
Document ,
documentCtx
)
2019-01-02 20:21:57 +01:00
// the response might be finished on the getInitialProps call
2020-03-04 13:58:12 +01:00
if ( isResSent ( res ) && ! isSSG ) return null
2018-12-13 01:00:46 +01:00
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-06-29 02:48:28 +02:00
const dynamicImportIdsSet = new Set < string > ( )
const dynamicImports : ManifestItem [ ] = [ ]
for ( const mod of reactLoadableModules ) {
2020-01-27 23:50:59 +01:00
const manifestItem : ManifestItem [ ] = reactLoadableManifest [ mod ]
2019-06-29 02:48:28 +02:00
if ( manifestItem ) {
2019-11-11 04:24:53 +01:00
manifestItem . forEach ( item = > {
2019-06-29 02:48:28 +02:00
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-06-27 16:22:24 +02:00
ampState ,
2018-12-13 01:00:46 +01:00
props ,
2019-11-01 20:13:13 +01:00
headTags : await headTags ( documentCtx ) ,
bodyTags : await bodyTags ( documentCtx ) ,
htmlProps : await htmlProps ( documentCtx ) ,
2020-02-07 14:09:06 +01:00
isFallback ,
2018-12-13 01:00:46 +01:00
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 ,
2019-02-08 11:57:29 +01:00
devFiles ,
2020-02-29 23:09:42 +01:00
files ,
lowPriorityFiles ,
2019-11-02 02:00:56 +01:00
polyfillFiles ,
2020-03-03 19:39:08 +01:00
gsp : ! ! getStaticProps ? true : undefined ,
gssp : ! ! getServerSideProps ? true : undefined ,
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-11-25 21:05:52 +01:00
// 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 )
2020-03-24 09:31:04 +01:00
html = await optimizeAmp ( html , renderOpts . ampOptimizerConfig )
2019-04-11 20:59:26 +02:00
2020-03-24 09:31:04 +01:00
if ( ! renderOpts . ampSkipValidation && 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
}