diff --git a/e2e/next/src/next.test.ts b/e2e/next/src/next.test.ts index 986a6888c57fb..d5c3ef3c31004 100644 --- a/e2e/next/src/next.test.ts +++ b/e2e/next/src/next.test.ts @@ -168,21 +168,36 @@ describe('Next.js Applications', () => { `dist/apps/${appName}/public/shared/ui/hello.txt` ); + // Check that `nx serve --prod` works with previous production build (e.g. `nx build `). + const prodServePort = 4000; + const prodServeProcess = await runCommandUntil( + `run ${appName}:serve --prod --port=${prodServePort}`, + (output) => { + return output.includes(`localhost:${prodServePort}`); + } + ); + // Check that the output is self-contained (i.e. can run with its own package.json + node_modules) const distPath = joinPathFragments(tmpProjPath(), 'dist/apps', appName); - const port = 3000; + const selfContainedPort = 3000; const pmc = getPackageManagerCommand(); runCommand(`${pmc.install}`, { cwd: distPath, }); runCLI( - `generate @nrwl/workspace:run-commands serve-prod --project ${appName} --cwd=dist/apps/${appName} --command="npx next start --port=${port}"` + `generate @nrwl/workspace:run-commands serve-prod --project ${appName} --cwd=dist/apps/${appName} --command="npx next start --port=${selfContainedPort}"` + ); + const selfContainedProcess = await runCommandUntil( + `run ${appName}:serve-prod`, + (output) => { + return output.includes(`localhost:${selfContainedPort}`); + } ); - await runCommandUntil(`run ${appName}:serve-prod`, (output) => { - return output.includes(`localhost:${port}`); - }); - await killPort(port); + prodServeProcess.kill(); + selfContainedProcess.kill(); + await killPort(prodServePort); + await killPort(selfContainedPort); }, 300_000); it('should build and install pruned lock file', () => { diff --git a/packages/next/plugins/with-nx.ts b/packages/next/plugins/with-nx.ts index f899ac8d1c1b4..8d153357a8c88 100644 --- a/packages/next/plugins/with-nx.ts +++ b/packages/next/plugins/with-nx.ts @@ -18,6 +18,7 @@ import { PHASE_PRODUCTION_SERVER } from 'next/constants'; import * as path from 'path'; import { createWebpackConfig } from '../src/utils/config'; import { NextBuildBuilderOptions } from '../src/utils/types'; + export interface WithNxOptions extends NextConfig { nx?: { svgr?: boolean; @@ -115,6 +116,31 @@ function getNxContext( } } +/** + * Try to read output dir from project, and default to '.next' if executing outside of Nx (e.g. dist is added to a docker image). + */ +async function determineDistDirForProdServer( + nextConfig: NextConfig +): Promise { + const project = process.env.NX_TASK_TARGET_PROJECT; + const target = process.env.NX_TASK_TARGET_TARGET; + const configuration = process.env.NX_TASK_TARGET_CONFIGURATION; + + if (project && target) { + const originalTarget = { project, target, configuration }; + const graph = await createProjectGraphAsync(); + + const { options, node: projectNode } = getNxContext(graph, originalTarget); + const outputDir = `${offsetFromRoot(projectNode.data.root)}${ + options.outputPath + }`; + return nextConfig.distDir && nextConfig.distDir !== '.next' + ? joinPathFragments(outputDir, nextConfig.distDir) + : joinPathFragments(outputDir, '.next'); + } else { + return '.next'; + } +} export function withNx( _nextConfig = {} as WithNxOptions, context: WithNxContext = getWithNxContext() @@ -123,7 +149,10 @@ export function withNx( if (phase === PHASE_PRODUCTION_SERVER) { // If we are running an already built production server, just return the configuration. const { nx, ...validNextConfig } = _nextConfig; - return { distDir: '.next', ...validNextConfig }; + return { + ...validNextConfig, + distDir: await determineDistDirForProdServer(validNextConfig), + }; } else { // Otherwise, add in webpack and eslint configuration for build or test. let dependencies: DependentBuildableProjectNode[] = [];