diff --git a/build/webpack.js b/build/webpack.js index 3fa51d755b02..ec333e96af94 100644 --- a/build/webpack.js +++ b/build/webpack.js @@ -245,6 +245,15 @@ export default async function getBaseWebpackConfig (dir: string, {dev = false, i }), dev && !isServer && new FriendlyErrorsWebpackPlugin(), new webpack.IgnorePlugin(/(precomputed)/, /node_modules.+(elliptic)/), + // This removes react-is in production, as it's not used there. + !dev && new webpack.IgnorePlugin({ + checkResource: (resource) => { + return /react-is/.test(resource) + }, + checkContext: (context) => { + return context.indexOf(path.join(NEXT_PROJECT_ROOT_DIST, 'lib', 'router')) !== -1 + } + }), // Even though require.cache is server only we have to clear assets from both compilations // This is because the client compilation generates the build manifest that's used on the server side dev && new NextJsRequireCacheHotReloader(), diff --git a/client/index.js b/client/index.js index 4fb3edcdb6c1..bfaa913b9221 100644 --- a/client/index.js +++ b/client/index.js @@ -82,8 +82,11 @@ export default async ({ try { Component = await pageLoader.loadPage(page) - if (typeof Component !== 'function') { - throw new Error(`The default export is not a React Component in page: "${pathname}"`) + if (process.env.NODE_ENV === 'development') { + const { isValidElementType } = require('react-is') + if (!isValidElementType(Component)) { + throw new Error(`The default export is not a React Component in page: "${pathname}"`) + } } } catch (error) { // This catches errors like throwing in the top level of a module diff --git a/lib/router/router.js b/lib/router/router.js index 61e6a07e7c6f..c30bb8a3d057 100644 --- a/lib/router/router.js +++ b/lib/router/router.js @@ -245,8 +245,12 @@ export default class Router { const { Component } = routeInfo - if (typeof Component !== 'function') { - throw new Error(`The default export is not a React Component in page: "${pathname}"`) + if (process.env.NODE_ENV === 'development') { + // This package is ignored for this file using webpack.IgnorePlugin in production + const { isValidElementType } = require('react-is') + if (!isValidElementType(Component)) { + throw new Error(`The default export is not a React Component in page: "${pathname}"`) + } } const ctx = { pathname, query, asPath: as } diff --git a/package.json b/package.json index 8dfcff3d8ebe..5d9abded4589 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "prop-types": "15.6.2", "prop-types-exact": "1.2.0", "react-error-overlay": "4.0.0", + "react-is": "16.5.0", "recursive-copy": "2.0.6", "resolve": "1.5.0", "send": "0.16.1", diff --git a/server/render.js b/server/render.js index f82a7f65983b..e1acc769228b 100644 --- a/server/render.js +++ b/server/render.js @@ -87,8 +87,11 @@ async function doRender (req, res, pathname, query, { Component = Component.default || Component - if (typeof Component !== 'function') { - throw new Error(`The default export is not a React Component in page: "${pathname}"`) + if (process.env.NODE_ENV === 'development') { + const { isValidElementType } = require('react-is') + if (!isValidElementType(Component)) { + throw new Error(`The default export is not a React Component in page: "${pathname}"`) + } } App = App.default || App diff --git a/test/integration/basic/test/error-recovery.js b/test/integration/basic/test/error-recovery.js index 12a9da06d5a1..85cc776642ab 100644 --- a/test/integration/basic/test/error-recovery.js +++ b/test/integration/basic/test/error-recovery.js @@ -177,7 +177,7 @@ export default (context, render) => { expect(text).toBe('This is the about page.') const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js')) - aboutPage.replace('export default', 'export default "not-a-page"\nexport const fn = ') + aboutPage.replace('export default', 'export default { not: "a-page" }\nexport const fn = ') await waitFor(3000)