diff --git a/packages/next/export/worker.js b/packages/next/export/worker.js index 1948939a69d1a16..30e7db5d67275fb 100644 --- a/packages/next/export/worker.js +++ b/packages/next/export/worker.js @@ -37,12 +37,24 @@ export default async function({ } try { - let { query = {} } = pathMap + const { query: originalQuery = {} } = pathMap const { page } = pathMap const filePath = path === '/' ? '/index' : path const ampPath = `${filePath}.amp` + let query = { ...originalQuery } let params + // We need to show a warning if they try to provide query values + // for an auto-exported page since they won't be available + const hasOrigQueryValues = Object.keys(originalQuery).length > 0 + const queryWithAutoExportWarn = () => { + if (hasOrigQueryValues) { + throw new Error( + `\nError: you provided query values for ${path} which is an auto-exported page. These can not be applied since the page can no longer be re-rendered on the server. To disable auto-export for this page add \`getInitialProps\`\n` + ) + } + } + // Check if the page is a specified dynamic route if (isDynamicRoute(page) && page !== path) { params = getRouteMatcher(getRouteRegex(page))(path) @@ -130,6 +142,7 @@ export default async function({ // if it was auto-exported the HTML is loaded here if (typeof mod === 'string') { html = mod + queryWithAutoExportWarn() } else { // for non-dynamic SSG pages we should have already // prerendered the file @@ -176,6 +189,7 @@ export default async function({ if (typeof components.Component === 'string') { html = components.Component + queryWithAutoExportWarn() } else { curRenderOpts = { ...components, ...renderOpts, ampPath } html = await renderMethod(req, res, page, query, curRenderOpts) diff --git a/test/integration/auto-export-query-error/next.config.js b/test/integration/auto-export-query-error/next.config.js new file mode 100644 index 000000000000000..39584969c6a1b7c --- /dev/null +++ b/test/integration/auto-export-query-error/next.config.js @@ -0,0 +1,10 @@ +module.exports = { + // target: 'serverless', + exportPathMap() { + return { + '/': { page: '/hello', query: { first: 'second' } }, + '/amp': { page: '/amp' }, + '/ssr': { page: '/ssr' }, + } + }, +} diff --git a/test/integration/auto-export-query-error/pages/amp.js b/test/integration/auto-export-query-error/pages/amp.js new file mode 100644 index 000000000000000..e9c90ebfe5ac7b6 --- /dev/null +++ b/test/integration/auto-export-query-error/pages/amp.js @@ -0,0 +1,3 @@ +export const config = { amp: 'hybrid' } + +export default () => 'hi from hybrid AMP' diff --git a/test/integration/auto-export-query-error/pages/hello.js b/test/integration/auto-export-query-error/pages/hello.js new file mode 100644 index 000000000000000..0957a987fc2f227 --- /dev/null +++ b/test/integration/auto-export-query-error/pages/hello.js @@ -0,0 +1 @@ +export default () => 'hi' diff --git a/test/integration/auto-export-query-error/pages/ssg.js b/test/integration/auto-export-query-error/pages/ssg.js new file mode 100644 index 000000000000000..f92e23e36d47857 --- /dev/null +++ b/test/integration/auto-export-query-error/pages/ssg.js @@ -0,0 +1,11 @@ +const Page = () => "I'm SSRed" + +export async function getStaticProps() { + return { + props: { + hello: 'world', + }, + } +} + +export default Page diff --git a/test/integration/auto-export-query-error/pages/ssr.js b/test/integration/auto-export-query-error/pages/ssr.js new file mode 100644 index 000000000000000..31534f6e5a8a374 --- /dev/null +++ b/test/integration/auto-export-query-error/pages/ssr.js @@ -0,0 +1,5 @@ +const Page = () => "I'm SSRed" + +Page.getInitialProps = () => ({ hello: 'world' }) + +export default Page diff --git a/test/integration/auto-export-query-error/test/index.test.js b/test/integration/auto-export-query-error/test/index.test.js new file mode 100644 index 000000000000000..919ca14e4a6968d --- /dev/null +++ b/test/integration/auto-export-query-error/test/index.test.js @@ -0,0 +1,64 @@ +/* eslint-env jest */ +/* global jasmine */ +import path from 'path' +import fs from 'fs-extra' +import { nextBuild, nextExport } from 'next-test-utils' + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 1 +const appDir = path.join(__dirname, '..') +const outdir = path.join(__dirname, 'out') +const nextConfig = path.join(appDir, 'next.config.js') +let stderr +let exitCode + +const runTests = () => { + it('should show warning for query provided for auto exported page correctly', async () => { + expect(exitCode).toBe(1) + expect(stderr).toContain( + 'Error: you provided query values for / which is an auto-exported page. These can not be applied since the page can no longer be re-rendered on the server. To disable auto-export for this page add `getInitialProps`' + ) + + expect(stderr).not.toContain('/amp') + expect(stderr).not.toContain('/ssr') + expect(stderr).not.toContain('/ssg') + expect(stderr).not.toContain('/hello') + }) +} + +let origNextConfig + +describe('Auto Export', () => { + describe('server mode', () => { + beforeAll(async () => { + await nextBuild(appDir) + const { stderr: curStderr, code: curCode } = await nextExport( + appDir, + { outdir }, + { stderr: true } + ) + stderr = curStderr + exitCode = curCode + }) + + runTests() + }) + + describe('serverless mode', () => { + beforeAll(async () => { + origNextConfig = await fs.readFile(nextConfig, 'utf8') + await nextBuild(appDir) + const { stderr: curStderr, code: curCode } = await nextExport( + appDir, + { outdir }, + { stderr: true } + ) + stderr = curStderr + exitCode = curCode + }) + afterAll(async () => { + await fs.writeFile(nextConfig, origNextConfig) + }) + + runTests() + }) +}) diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js index b695ca874062931..a4bfcb75f6d71a0 100644 --- a/test/lib/next-test-utils.js +++ b/test/lib/next-test-utils.js @@ -111,8 +111,9 @@ export function runNextCommand(argv, options = {}) { }) } - instance.on('close', () => { + instance.on('close', code => { resolve({ + code, stdout: stdoutOutput, stderr: stderrOutput, })