From 5ed89d3021d62dcb1165202793718058978e7208 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 17 Jun 2020 05:25:27 -0400 Subject: [PATCH] Render a helpful message for null GS(S)P return (#14252) This makes Next.js render a better error message when `undefined` (or null) is returned from `getStaticProps` or `getServerSideProps`. --- Fixes #11139 --- packages/next/lib/constants.ts | 5 +++ packages/next/next-server/server/render.tsx | 20 ++++++--- .../pages/index.js | 0 .../test/index.test.js | 43 ++++++++++++++++--- 4 files changed, 57 insertions(+), 11 deletions(-) rename test/integration/{ssg-component-members-error => data-fetching-errors}/pages/index.js (100%) rename test/integration/{ssg-component-members-error => data-fetching-errors}/test/index.test.js (74%) diff --git a/packages/next/lib/constants.ts b/packages/next/lib/constants.ts index b50274579f..25cab8bca5 100644 --- a/packages/next/lib/constants.ts +++ b/packages/next/lib/constants.ts @@ -34,6 +34,11 @@ export const PAGES_404_GET_INITIAL_PROPS_ERROR = `\`pages/404\` can not have get export const SERVER_PROPS_EXPORT_ERROR = `pages with \`getServerSideProps\` can not be exported. See more info here: https://err.sh/next.js/gssp-export` +export const GSP_NO_RETURNED_VALUE = + 'Your `getStaticProps` function did not return an object. Did you forget to add a `return`?' +export const GSSP_NO_RETURNED_VALUE = + 'Your `getServerSideProps` function did not return an object. Did you forget to add a `return`?' + export const UNSTABLE_REVALIDATE_RENAME_ERROR = 'The `revalidate` property is not yet available for general use.\n' + 'To try the experimental implementation, please use `unstable_revalidate` instead.\n' + diff --git a/packages/next/next-server/server/render.tsx b/packages/next/next-server/server/render.tsx index 63d3f61f1e..6f33897479 100644 --- a/packages/next/next-server/server/render.tsx +++ b/packages/next/next-server/server/render.tsx @@ -2,26 +2,30 @@ import { IncomingMessage, ServerResponse } from 'http' import { ParsedUrlQuery } from 'querystring' import React from 'react' import { renderToStaticMarkup, renderToString } from 'react-dom/server' +import { UnwrapPromise } from '../../lib/coalesced-function' import { + GSP_NO_RETURNED_VALUE, + GSSP_COMPONENT_MEMBER_ERROR, + GSSP_NO_RETURNED_VALUE, PAGES_404_GET_INITIAL_PROPS_ERROR, SERVER_PROPS_GET_INIT_PROPS_CONFLICT, SERVER_PROPS_SSG_CONFLICT, SSG_GET_INITIAL_PROPS_CONFLICT, UNSTABLE_REVALIDATE_RENAME_ERROR, - GSSP_COMPONENT_MEMBER_ERROR, } from '../../lib/constants' import { isSerializableProps } from '../../lib/is-serializable-props' +import { GetServerSideProps, GetStaticProps } from '../../types' import { isInAmpMode } from '../lib/amp' import { AmpStateContext } from '../lib/amp-context' import { AMP_RENDER_TARGET, - STATIC_PROPS_ID, SERVER_PROPS_ID, + STATIC_PROPS_ID, } from '../lib/constants' import { defaultHead } from '../lib/head' +import { HeadManagerContext } from '../lib/head-manager-context' import Loadable from '../lib/loadable' import { LoadableContext } from '../lib/loadable-context' -import { HeadManagerContext } from '../lib/head-manager-context' import mitt, { MittEmitter } from '../lib/mitt' import { RouterContext } from '../lib/router-context' import { NextRouter } from '../lib/router/router' @@ -41,8 +45,6 @@ import { tryGetPreviewData, __ApiPreviewProps } from './api-utils' import { getPageFiles } from './get-page-files' import { LoadComponentsReturnType, ManifestItem } from './load-components' import optimizeAmp from './optimize-amp' -import { UnwrapPromise } from '../../lib/coalesced-function' -import { GetStaticProps, GetServerSideProps } from '../../types' function noRouter() { const message = @@ -515,6 +517,10 @@ export async function renderToHTML( throw staticPropsError } + if (data == null) { + throw new Error(GSP_NO_RETURNED_VALUE) + } + const invalidKeys = Object.keys(data).filter( (key) => key !== 'unstable_revalidate' && key !== 'props' ) @@ -601,6 +607,10 @@ export async function renderToHTML( throw serverSidePropsError } + if (data == null) { + throw new Error(GSSP_NO_RETURNED_VALUE) + } + const invalidKeys = Object.keys(data).filter((key) => key !== 'props') if (invalidKeys.length) { diff --git a/test/integration/ssg-component-members-error/pages/index.js b/test/integration/data-fetching-errors/pages/index.js similarity index 100% rename from test/integration/ssg-component-members-error/pages/index.js rename to test/integration/data-fetching-errors/pages/index.js diff --git a/test/integration/ssg-component-members-error/test/index.test.js b/test/integration/data-fetching-errors/test/index.test.js similarity index 74% rename from test/integration/ssg-component-members-error/test/index.test.js rename to test/integration/data-fetching-errors/test/index.test.js index 1b1c66433a..e087e3de14 100644 --- a/test/integration/ssg-component-members-error/test/index.test.js +++ b/test/integration/data-fetching-errors/test/index.test.js @@ -1,15 +1,18 @@ /* eslint-env jest */ import fs from 'fs-extra' -import { join } from 'path' import { - nextBuild, - launchApp, findPort, - renderViaHTTP, killApp, + launchApp, + nextBuild, + renderViaHTTP, } from 'next-test-utils' - +import { join } from 'path' +import { + GSP_NO_RETURNED_VALUE, + GSSP_NO_RETURNED_VALUE, +} from '../../../../packages/next/dist/lib/constants' jest.setTimeout(1000 * 60 * 2) const appDir = join(__dirname, '..') @@ -88,9 +91,37 @@ const runTests = (isDev = false) => { `getStaticPaths can not be attached to a page's component and must be exported from the page` ) }) + + it('should show error for undefined getStaticProps', async () => { + await fs.writeFile( + indexPage, + ` + export function getStaticProps() {} + export default function Page() { + return
; + } + ` + ) + expect(await getStderr()).toContain(GSP_NO_RETURNED_VALUE) + }) + + if (isDev) { + it('should show error for undefined getServerSideProps', async () => { + await fs.writeFile( + indexPage, + ` + export function getServerSideProps() {} + export default function Page() { + return
; + } + ` + ) + expect(await getStderr()).toContain(GSSP_NO_RETURNED_VALUE) + }) + } } -describe('GSSP Page Component Member Error', () => { +describe('GS(S)P Page Errors', () => { beforeAll(async () => { origIndexPage = await fs.readFile(indexPage, 'utf8') }) -- GitLab