diff --git a/packages/next/next-server/lib/utils.ts b/packages/next/next-server/lib/utils.ts index 590cf8a33cfa7ec5cef15f887807d5d443f3f387..b1c79ce2d7200069cd2064e53d8f2b32f0466892 100644 --- a/packages/next/next-server/lib/utils.ts +++ b/packages/next/next-server/lib/utils.ts @@ -240,11 +240,11 @@ export async function loadGetInitialProps< C extends BaseContext, IP = {}, P = {} ->(Component: NextComponentType, ctx: C): Promise { +>(App: NextComponentType, ctx: C): Promise { if (process.env.NODE_ENV !== 'production') { - if (Component.prototype && Component.prototype.getInitialProps) { + if (App.prototype && App.prototype.getInitialProps) { const message = `"${getDisplayName( - Component + App )}.getInitialProps()" is defined as an instance method - visit https://err.sh/zeit/next.js/get-initial-props-as-an-instance-method for more information.` throw new Error(message) } @@ -252,11 +252,17 @@ export async function loadGetInitialProps< // when called from _app `ctx` is nested in `ctx` const res = ctx.res || (ctx.ctx && ctx.ctx.res) - if (!Component.getInitialProps) { + if (!App.getInitialProps) { + if (ctx.ctx && ctx.Component) { + // @ts-ignore pageProps default + return { + pageProps: await loadGetInitialProps(ctx.Component, ctx.ctx), + } + } return {} as any } - const props = await Component.getInitialProps(ctx) + const props = await App.getInitialProps(ctx) if (res && isResSent(res)) { return props @@ -264,7 +270,7 @@ export async function loadGetInitialProps< if (!props) { const message = `"${getDisplayName( - Component + App )}.getInitialProps()" should resolve to an object. But found "${props}" instead.` throw new Error(message) } @@ -273,7 +279,7 @@ export async function loadGetInitialProps< if (Object.keys(props).length === 0 && !ctx.ctx) { console.warn( `${getDisplayName( - Component + App )} returned an empty object from \`getInitialProps\`. This de-optimizes and prevents automatic static optimization. https://err.sh/zeit/next.js/empty-object-getInitialProps` ) } diff --git a/test/integration/app-functional/next.config.js b/test/integration/app-functional/next.config.js new file mode 100644 index 0000000000000000000000000000000000000000..45e0e07d1d98d17d38a6e778165ace899ea1e2a5 --- /dev/null +++ b/test/integration/app-functional/next.config.js @@ -0,0 +1,3 @@ +module.exports = { + crossOrigin: 'anonymous' +} diff --git a/test/integration/app-functional/pages/_app.js b/test/integration/app-functional/pages/_app.js new file mode 100644 index 0000000000000000000000000000000000000000..80a095185e1a2230c43108423bec2caf7195ab08 --- /dev/null +++ b/test/integration/app-functional/pages/_app.js @@ -0,0 +1,17 @@ +function MyApp ({ Component, pageProps }) { + return +} + +// Only uncomment this method if you have blocking data requirements for +// every single page in your application. This disables the ability to +// perform automatic static optimization, causing every page in your app to +// be server-side rendered. +// +// MyApp.getInitialProps = async (appContext) => { +// // calls page's `getInitialProps` and fills `appProps.pageProps` +// const appProps = await App.getInitialProps(appContext); +// +// return { ...appProps } +// } + +export default MyApp diff --git a/test/integration/app-functional/pages/index.js b/test/integration/app-functional/pages/index.js new file mode 100644 index 0000000000000000000000000000000000000000..a53d3efb38fa6732614dee6b765f27ab7625adec --- /dev/null +++ b/test/integration/app-functional/pages/index.js @@ -0,0 +1,11 @@ +function Page ({ message }) { + return
{message}
+} + +Page.getInitialProps = async ({ req }) => { + return { + message: 'Hello World!!!' + } +} + +export default Page diff --git a/test/integration/app-functional/shared-module.js b/test/integration/app-functional/shared-module.js new file mode 100644 index 0000000000000000000000000000000000000000..26bfe7ef59ebaa64dd62883ee67b352cd32c9e99 --- /dev/null +++ b/test/integration/app-functional/shared-module.js @@ -0,0 +1,9 @@ +let moduleState = 'INITIAL' + +export function setState (state) { + moduleState = state +} + +export default function currentState () { + return moduleState +} diff --git a/test/integration/app-functional/test/index.test.js b/test/integration/app-functional/test/index.test.js new file mode 100644 index 0000000000000000000000000000000000000000..100f33b23173e18d91fe408fd95b75ec3c765dfc --- /dev/null +++ b/test/integration/app-functional/test/index.test.js @@ -0,0 +1,32 @@ +/* eslint-env jest */ +/* global jasmine */ +import { join } from 'path' +import { renderViaHTTP, findPort, launchApp, killApp } from 'next-test-utils' + +const context = { + output: '' +} +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5 + +const collectOutput = message => { + context.output += message +} + +describe('Document and App', () => { + beforeAll(async () => { + context.appPort = await findPort() + context.server = await launchApp(join(__dirname, '../'), context.appPort, { + onStdout: collectOutput, + onStderr: collectOutput + }) + + // pre-build all pages at the start + await Promise.all([renderViaHTTP(context.appPort, '/')]) + }) + afterAll(() => killApp(context.server)) + + it('should not have any missing key warnings', async () => { + const html = await renderViaHTTP(context.appPort, '/') + expect(html).toMatch(/
Hello World!!!<\/div>/) + }) +})