diff --git a/docs/generated/cli/create-nx-workspace.md b/docs/generated/cli/create-nx-workspace.md index 6d16d6a18d13e..734d962e99c48 100644 --- a/docs/generated/cli/create-nx-workspace.md +++ b/docs/generated/cli/create-nx-workspace.md @@ -113,7 +113,7 @@ Workspace name (e.g. org name) Type: `boolean` -Add Experimental app/ layout for next.js +Enable the App Router for Next.js ### nxCloud diff --git a/docs/generated/packages/next/generators/application.json b/docs/generated/packages/next/generators/application.json index 6089512e1b5c7..f5531bc6db6ab 100644 --- a/docs/generated/packages/next/generators/application.json +++ b/docs/generated/packages/next/generators/application.json @@ -118,9 +118,9 @@ }, "appDir": { "type": "boolean", - "default": false, - "description": "Enable experimental app directory for the project", - "x-prompt": "Do you want to use experimental app/ in this project?" + "default": true, + "description": "Enable the App Router for this project.", + "x-prompt": "Would you like to use the App Router (recommended)?" }, "rootProject": { "description": "Create an application at the root of the workspace.", diff --git a/docs/generated/packages/nx/documents/create-nx-workspace.md b/docs/generated/packages/nx/documents/create-nx-workspace.md index 6d16d6a18d13e..734d962e99c48 100644 --- a/docs/generated/packages/nx/documents/create-nx-workspace.md +++ b/docs/generated/packages/nx/documents/create-nx-workspace.md @@ -113,7 +113,7 @@ Workspace name (e.g. org name) Type: `boolean` -Add Experimental app/ layout for next.js +Enable the App Router for Next.js ### nxCloud diff --git a/docs/generated/packages/workspace/generators/new.json b/docs/generated/packages/workspace/generators/new.json index 592c94bc15cfb..4a131d0f7bb18 100644 --- a/docs/generated/packages/workspace/generators/new.json +++ b/docs/generated/packages/workspace/generators/new.json @@ -70,9 +70,9 @@ "enum": ["express", "koa", "fastify", "nest", "none"] }, "nextAppDir": { - "description": "Enable experimental app directory for the project", + "description": "Enable the App Router for this project.", "type": "boolean", - "default": false + "default": true }, "e2eTestRunner": { "description": "The tool to use for running e2e tests.", diff --git a/docs/generated/packages/workspace/generators/preset.json b/docs/generated/packages/workspace/generators/preset.json index cc8405b93eb59..fdec817222661 100644 --- a/docs/generated/packages/workspace/generators/preset.json +++ b/docs/generated/packages/workspace/generators/preset.json @@ -82,9 +82,9 @@ "default": false }, "nextAppDir": { - "description": "Enable experimental app/ for the project", + "description": "Enable the App Router for this project.", "type": "boolean", - "default": false + "default": true }, "e2eTestRunner": { "description": "The tool to use for running e2e tests.", diff --git a/e2e/next/src/next-appdir.test.ts b/e2e/next/src/next-appdir.test.ts new file mode 100644 index 0000000000000..9a7b47e32732f --- /dev/null +++ b/e2e/next/src/next-appdir.test.ts @@ -0,0 +1,56 @@ +import { + cleanupProject, + isNotWindows, + killPorts, + newProject, + runCLI, + runCommandUntil, + tmpProjPath, + uniq, + updateFile, +} from '@nx/e2e/utils'; +import { getData } from 'ajv/dist/compile/validate'; +import { detectPackageManager } from 'nx/src/utils/package-manager'; +import { checkApp } from './utils'; +import { p } from 'vitest/dist/types-b7007192'; + +describe('Next.js App Router', () => { + let proj: string; + + beforeEach(() => { + proj = newProject(); + }); + + afterEach(() => { + cleanupProject(); + }); + + it('should be able to generate and build app with default App Router', async () => { + const appName = uniq('app'); + const jsLib = uniq('tslib'); + + runCLI(`generate @nx/next:app ${appName}`); + runCLI(`generate @nx/js:lib ${jsLib} --no-interactive`); + + updateFile( + `apps/${appName}/app/page.tsx`, + ` + import React from 'react'; + import { ${jsLib} } from '@${proj}/${jsLib}'; + + export default async function Page() { + return ( +

{${jsLib}()}

+ ); + }; + ` + ); + + await checkApp(appName, { + checkUnitTest: false, + checkLint: true, + checkE2E: false, + checkExport: false, + }); + }, 300_000); +}); diff --git a/e2e/next/src/next-component-tests.test.ts b/e2e/next/src/next-component-tests.test.ts index a5ec4f0142ab2..1c7ce8ea1a914 100644 --- a/e2e/next/src/next-component-tests.test.ts +++ b/e2e/next/src/next-component-tests.test.ts @@ -65,7 +65,7 @@ describe('NextJs Component Testing', () => { }); function createAppWithCt(appName: string) { - runCLI(`generate @nx/next:app ${appName} --no-interactive`); + runCLI(`generate @nx/next:app ${appName} --no-interactive --appDir=false`); runCLI( `generate @nx/next:component button --project=${appName} --directory=components --flat --no-interactive` ); diff --git a/e2e/next/src/next-styles.test.ts b/e2e/next/src/next-styles.test.ts index ed48cc993f9e2..6e1002c7fde6f 100644 --- a/e2e/next/src/next-styles.test.ts +++ b/e2e/next/src/next-styles.test.ts @@ -21,7 +21,9 @@ describe('Next.js apps', () => { it('should support different --style options', async () => { const lessApp = uniq('app'); - runCLI(`generate @nx/next:app ${lessApp} --no-interactive --style=less`); + runCLI( + `generate @nx/next:app ${lessApp} --no-interactive --style=less --appDir=false` + ); await checkApp(lessApp, { checkUnitTest: false, @@ -32,7 +34,9 @@ describe('Next.js apps', () => { const stylusApp = uniq('app'); - runCLI(`generate @nx/next:app ${stylusApp} --no-interactive --style=styl`); + runCLI( + `generate @nx/next:app ${stylusApp} --no-interactive --style=styl --appDir=false` + ); await checkApp(stylusApp, { checkUnitTest: false, @@ -44,7 +48,7 @@ describe('Next.js apps', () => { const scApp = uniq('app'); runCLI( - `generate @nx/next:app ${scApp} --no-interactive --style=styled-components` + `generate @nx/next:app ${scApp} --no-interactive --style=styled-components --appDir=false` ); await checkApp(scApp, { @@ -57,7 +61,7 @@ describe('Next.js apps', () => { const emotionApp = uniq('app'); runCLI( - `generate @nx/next:app ${emotionApp} --no-interactive --style=@emotion/styled` + `generate @nx/next:app ${emotionApp} --no-interactive --style=@emotion/styled --appDir=false` ); await checkApp(emotionApp, { diff --git a/e2e/next/src/next.test.ts b/e2e/next/src/next.test.ts index 7136362c045ac..fa9416ed026cb 100644 --- a/e2e/next/src/next.test.ts +++ b/e2e/next/src/next.test.ts @@ -52,7 +52,9 @@ describe('Next.js Applications', () => { const jsLib = uniq('tslib'); const buildableLib = uniq('buildablelib'); - runCLI(`generate @nx/next:app ${appName} --no-interactive --style=css`); + runCLI( + `generate @nx/next:app ${appName} --no-interactive --style=css --appDir=false` + ); runCLI(`generate @nx/next:lib ${nextLib} --no-interactive`); runCLI(`generate @nx/js:lib ${jsLib} --no-interactive`); runCLI( @@ -231,7 +233,7 @@ describe('Next.js Applications', () => { const port = 4200; - runCLI(`generate @nx/next:app ${appName}`); + runCLI(`generate @nx/next:app ${appName} --appDir=false`); runCLI(`generate @nx/js:lib ${jsLib} --no-interactive`); const proxyConf = { @@ -297,7 +299,9 @@ describe('Next.js Applications', () => { it('should support custom next.config.js and output it in dist', async () => { const appName = uniq('app'); - runCLI(`generate @nx/next:app ${appName} --no-interactive --style=css`); + runCLI( + `generate @nx/next:app ${appName} --no-interactive --style=css --appDir=false` + ); updateFile( `apps/${appName}/next.config.js`, @@ -354,7 +358,9 @@ describe('Next.js Applications', () => { it('should support --js flag', async () => { const appName = uniq('app'); - runCLI(`generate @nx/next:app ${appName} --no-interactive --js`); + runCLI( + `generate @nx/next:app ${appName} --no-interactive --js --appDir=false` + ); checkFilesExist(`apps/${appName}/pages/index.js`); diff --git a/e2e/next/src/utils.ts b/e2e/next/src/utils.ts index dc4ef27ebf7f3..b0351ac4d5eff 100644 --- a/e2e/next/src/utils.ts +++ b/e2e/next/src/utils.ts @@ -18,14 +18,6 @@ export async function checkApp( } ) { const appsDir = opts.appsDir ?? 'apps'; - const buildResult = runCLI(`build ${appName}`); - expect(buildResult).toContain(`Compiled successfully`); - checkFilesExist(`dist/${appsDir}/${appName}/.next/build-manifest.json`); - - const packageJson = readJson(`dist/${appsDir}/${appName}/package.json`); - expect(packageJson.dependencies.react).toBeDefined(); - expect(packageJson.dependencies['react-dom']).toBeDefined(); - expect(packageJson.dependencies.next).toBeDefined(); if (opts.checkLint) { const lintResults = runCLI(`lint ${appName}`); @@ -39,8 +31,19 @@ export async function checkApp( ); } + const buildResult = runCLI(`build ${appName}`); + expect(buildResult).toContain(`Successfully ran target build`); + checkFilesExist(`dist/${appsDir}/${appName}/.next/build-manifest.json`); + + const packageJson = readJson(`dist/${appsDir}/${appName}/package.json`); + expect(packageJson.dependencies.react).toBeDefined(); + expect(packageJson.dependencies['react-dom']).toBeDefined(); + expect(packageJson.dependencies.next).toBeDefined(); + if (opts.checkE2E && runCypressTests()) { - const e2eResults = runCLI(`e2e ${appName}-e2e --no-watch`); + const e2eResults = runCLI( + `e2e ${appName}-e2e --no-watch --configuration=production` + ); expect(e2eResults).toContain('All specs passed!'); expect(await killPorts()).toBeTruthy(); } diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index 899723404a9c1..fcc1be357012d 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -107,7 +107,7 @@ export const commandsObject: yargs.Argv = yargs type: 'boolean', }) .option('nextAppDir', { - describe: chalk.dim`Add Experimental app/ layout for next.js`, + describe: chalk.dim`Enable the App Router for Next.js`, type: 'boolean', }), withNxCloud, @@ -638,8 +638,8 @@ async function isNextAppDir(parsedArgs: yargs.Arguments) { .prompt<{ appDir: 'Yes' | 'No' }>([ { name: 'appDir', - message: 'Do you want to use experimental app/ in this project?', - type: 'autocomplete', + message: 'Would you like to use the App Router (recommended)?', + type: 'autocomplete' as const, choices: [ { name: 'No', @@ -648,7 +648,7 @@ async function isNextAppDir(parsedArgs: yargs.Arguments) { name: 'Yes', }, ], - initial: 'No' as any, + initial: 'Yes' as any, }, ]) .then((choice) => choice.appDir === 'Yes'); diff --git a/packages/next/migrations.json b/packages/next/migrations.json index daad88978238b..092f0ce2ddcef 100644 --- a/packages/next/migrations.json +++ b/packages/next/migrations.json @@ -316,6 +316,22 @@ "alwaysAddToPackageJson": false } } + }, + "16.2.0": { + "version": "16.2.0-beta.0", + "requires": { + "next": ">=13.0.0" + }, + "packages": { + "next": { + "version": "13.4.1", + "alwaysAddToPackageJson": false + }, + "eslint-config-next": { + "version": "13.4.1", + "alwaysAddToPackageJson": false + } + } } } } diff --git a/packages/next/plugins/with-less.ts b/packages/next/plugins/with-less.ts index ef67e610cb94b..6df9a3c666ebc 100644 --- a/packages/next/plugins/with-less.ts +++ b/packages/next/plugins/with-less.ts @@ -6,14 +6,6 @@ import { WithNxOptions } from './with-nx'; const addLessToRegExp = (rx) => new RegExp(rx.source.replace('|sass', '|sass|less'), rx.flags); -function patchNextCSSWithLess( - nextCSSModule: any = require('next/dist/build/webpack/config/blocks/css') -) { - nextCSSModule.regexLikeCss = addLessToRegExp(nextCSSModule.regexLikeCss); -} - -patchNextCSSWithLess(); - export function withLess( configOrFn: NextConfigFn | WithNxOptions ): NextConfigFn { @@ -108,4 +100,3 @@ export function withLess( module.exports = withLess; module.exports.withLess = withLess; -module.exports.patchNext = patchNextCSSWithLess; diff --git a/packages/next/plugins/with-stylus.ts b/packages/next/plugins/with-stylus.ts index 3491cf4c4e6e0..a7322588491a6 100644 --- a/packages/next/plugins/with-stylus.ts +++ b/packages/next/plugins/with-stylus.ts @@ -6,14 +6,6 @@ import { WithNxOptions } from './with-nx'; const addStylusToRegExp = (rx) => new RegExp(rx.source.replace('|sass', '|sass|styl'), rx.flags); -function patchNextCSSWithStylus( - nextCSSModule = require('next/dist/build/webpack/config/blocks/css') as any -) { - nextCSSModule.regexLikeCss = addStylusToRegExp(nextCSSModule.regexLikeCss); -} - -patchNextCSSWithStylus(); - export function withStylus( configOrFn: WithNxOptions | NextConfigFn ): NextConfigFn { @@ -107,4 +99,3 @@ export function withStylus( module.exports = withStylus; module.exports.withStylus = withStylus; -module.exports.patchNext = patchNextCSSWithStylus; diff --git a/packages/next/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/next/src/generators/application/__snapshots__/application.spec.ts.snap index c9c272447c351..37486b754a5fe 100644 --- a/packages/next/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/next/src/generators/application/__snapshots__/application.spec.ts.snap @@ -1,7 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`app --style styled-jsx should use