From 6d53832f9034b3a780acb5843fcbb7916e507916 Mon Sep 17 00:00:00 2001
From: Nicholas Cunningham <ndcunningham@gmail.com>
Date: Fri, 2 Jun 2023 11:44:16 -0600
Subject: [PATCH] fix(nextjs): Update Next.js page generator to support app/
 router

closes: #17352
---
 .../next/src/generators/page/page.spec.ts     | 102 +++++++++++++-----
 packages/next/src/generators/page/page.ts     |  23 ++--
 2 files changed, 93 insertions(+), 32 deletions(-)

diff --git a/packages/next/src/generators/page/page.spec.ts b/packages/next/src/generators/page/page.spec.ts
index 9aa9d36013f4e..bfff240c2a3e2 100644
--- a/packages/next/src/generators/page/page.spec.ts
+++ b/packages/next/src/generators/page/page.spec.ts
@@ -6,47 +6,97 @@ import { Tree } from '@nx/devkit';
 describe('component', () => {
   let tree: Tree;
   let projectName: string;
+  let appRouterProjectName;
 
   beforeEach(async () => {
     projectName = 'my-app';
+    appRouterProjectName = 'my-app-router';
     tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
     await applicationGenerator(tree, {
       name: projectName,
       style: 'css',
+      appDir: false,
     });
-  });
 
-  it('should generate component in pages directory', async () => {
-    await pageGenerator(tree, {
-      name: 'hello',
-      project: projectName,
+    await applicationGenerator(tree, {
+      name: appRouterProjectName,
       style: 'css',
     });
+  });
+
+  describe('page router', () => {
+    it('should generate component in pages directory', async () => {
+      await pageGenerator(tree, {
+        name: 'hello',
+        project: projectName,
+        style: 'css',
+      });
+
+      expect(tree.exists('apps/my-app/pages/hello/index.tsx')).toBeTruthy();
+      expect(
+        tree.exists('apps/my-app/pages/hello/index.module.css')
+      ).toBeTruthy();
+    });
+
+    it('should support dynamic routes and directories', async () => {
+      await pageGenerator(tree, {
+        name: '[dynamic]',
+        directory: 'posts',
+        project: projectName,
+        style: 'css',
+      });
 
-    expect(tree.exists('apps/my-app/pages/hello/index.tsx')).toBeTruthy();
-    expect(
-      tree.exists('apps/my-app/pages/hello/index.module.css')
-    ).toBeTruthy();
+      expect(
+        tree.exists('apps/my-app/pages/posts/[dynamic]/index.tsx')
+      ).toBeTruthy();
+      expect(
+        tree.exists('apps/my-app/pages/posts/[dynamic]/index.module.css')
+      ).toBeTruthy();
+
+      const content = tree
+        .read('apps/my-app/pages/posts/[dynamic]/index.tsx')
+        .toString();
+      expect(content).toMatch(/DynamicProps/);
+    });
   });
 
-  it('should support dynamic routes and directories', async () => {
-    await pageGenerator(tree, {
-      name: '[dynamic]',
-      directory: 'posts',
-      project: projectName,
-      style: 'css',
+  describe('app router', () => {
+    it('should generate component in app directory', async () => {
+      await pageGenerator(tree, {
+        name: 'about',
+        project: appRouterProjectName,
+        style: 'css',
+      });
+
+      expect(
+        tree.exists(`apps/${appRouterProjectName}/app/about/page.tsx`)
+      ).toBeTruthy();
+      expect(
+        tree.exists(`apps/${appRouterProjectName}/app/about/page.module.css`)
+      ).toBeTruthy();
     });
 
-    expect(
-      tree.exists('apps/my-app/pages/posts/[dynamic]/index.tsx')
-    ).toBeTruthy();
-    expect(
-      tree.exists('apps/my-app/pages/posts/[dynamic]/index.module.css')
-    ).toBeTruthy();
-
-    const content = tree
-      .read('apps/my-app/pages/posts/[dynamic]/index.tsx')
-      .toString();
-    expect(content).toMatch(/DynamicProps/);
+    it('should support dynamic routes and directories', async () => {
+      await pageGenerator(tree, {
+        name: '[dynamic]',
+        project: appRouterProjectName,
+        directory: 'posts',
+        style: 'css',
+      });
+
+      expect(
+        tree.exists(`apps/${appRouterProjectName}/app/posts/[dynamic]/page.tsx`)
+      ).toBeTruthy();
+      expect(
+        tree.exists(
+          `apps/${appRouterProjectName}/app/posts/[dynamic]/page.module.css`
+        )
+      ).toBeTruthy();
+
+      const content = tree
+        .read(`apps/${appRouterProjectName}/app/posts/[dynamic]/page.tsx`)
+        .toString();
+      expect(content).toMatch(/DynamicProps/);
+    });
   });
 });
diff --git a/packages/next/src/generators/page/page.ts b/packages/next/src/generators/page/page.ts
index 9732a0cfa6a80..04aa8e6cca996 100644
--- a/packages/next/src/generators/page/page.ts
+++ b/packages/next/src/generators/page/page.ts
@@ -16,25 +16,23 @@ import { Schema } from './schema';
  * extra dependencies for css, sass, less, styl style options, and make sure
  * it is under `pages` folder.
  */
-export async function pageGenerator(host: Tree, options: Schema) {
-  const project = readProjectConfiguration(host, options.project);
-  const directory = options.directory ? `pages/${options.directory}` : 'pages';
+export async function pageGenerator(host: Tree, schema: Schema) {
+  const options = normalizeOptions(host, schema);
   const componentTask = await reactComponentGenerator(host, {
     ...options,
-    directory,
+    project: schema.project,
     pascalCaseFiles: false,
     export: false,
     classComponent: false,
     routing: false,
     skipTests: !options.withTests,
     flat: !!options.flat,
-    fileName: !options.flat ? 'index' : undefined,
     skipFormat: true,
   });
 
   const styledTask = addStyleDependencies(host, {
     style: options.style,
-    swc: !host.exists(joinPathFragments(project.root, '.babelrc')),
+    swc: !host.exists(joinPathFragments(options.project.root, '.babelrc')),
   });
 
   if (!options.skipFormat) {
@@ -44,5 +42,18 @@ export async function pageGenerator(host: Tree, options: Schema) {
   return runTasksInSerial(componentTask, styledTask);
 }
 
+function normalizeOptions(host: Tree, options: Schema) {
+  const project = readProjectConfiguration(host, options.project);
+
+  // app/ is a reserved folder in nextjs so it is safe to check it's existence
+  const isAppRouter = host.exists(`${project.root}/app`);
+  const routerDirectory = isAppRouter ? 'app' : 'pages';
+  const directory = options.directory
+    ? `${routerDirectory}/${options.directory}`
+    : `${routerDirectory}`;
+  const fileName = isAppRouter ? 'page' : !options.flat ? 'index' : undefined;
+  return { ...options, project, directory, fileName };
+}
+
 export default pageGenerator;
 export const pageSchematic = convertNxGenerator(pageGenerator);