Create a Next.js web app with Nx

Juri Strumpflohner - Oct 11 '21 - - Dev Community

In this article, we're going to explore how to create our very first web application with Next.js and Nx. We're going to learn about the anatomy of a Nx based workspace, how to generate one, and specifically how to setup our Next.js application. Finally, we're also going to create our blog's about page and learn about some handy Next.js features.

Next.js meets Nx

In order to create a new Next.js application, we have two options mainly:

We're going to use Nx for this setup because it provides a series of advantages:

These parts will be covered in more detail in the upcoming articles that are part of this series.

To create a new Nx workspace, use the following command.

npx create-nx-workspace juridev --packageManager=yarn
Enter fullscreen mode Exit fullscreen mode

juridev here is the name of my organization and will be your namespace when you import libraries which we'll see later.

When asked, use Next.js as the preset

During the setup, you'll be asked to give the generated application a name. I use "site" for now as this is going to be my main Next.js website. Make sure to choose CSS as the styling framework. Because we’ll be using Tailwind later, we need pure CSS and PostCSS processing.

Once the installation and setup completes, run yarn start (or npm start) to launch the Next.js dev server and navigate to http://localhost:4200. You should see the running application.

Nx Workspace structure

Let's quickly explore the Nx workspace structure to learn some of the fundamentals.

Apps and Libs

An Nx workspace is structured into apps and libs. Instead of having all the different features of our app just within folders of our application folder, we rather split them up into “workspace libraries”. Most of our business and domain logic should reside in those libraries. The apps can be seen as our “deployables”. They import the functionality in the libs as the building blocks to create a deployable app.

Although the libraries can be built and published (see Publishable and Buildable Libraries), they don’t have to. They are referenced via TypeScript path mappings in the tsconfig.base.json configuration at the root of the Nx workspace. When we build the application, all referenced libraries are built into the app via the used bundler (e.g. Webpack or Rollup etc).

Config files: workspace.json and nx.json

Let’s give a fast overview of the main configuration files. All the details can be found on the official docs page: https://nx.dev/latest/react/core-concepts/configuration

The workspace.json is the main configuration file of an Nx workspace. It defines

  • the projects in the workspace (e.g. apps and libs)
  • the Nx executor used to run operations on the projects (e.g. serve the app, build it, run Jest tests, Storybook etc..)

The nx.json defines mostly additional configuration properties used for the Nx dependency graph. Additionally, you can define the base branch (e.g. master or main or whatever you are using) and the task runner to be used.

Serving, building and testing

The Nx workspace.json config defines what you can actually serve, build, test etc. Here’s a quick example of such a configuration for a project called cart.

{
  "projects": {
    "cart": {
      "root": "apps/cart",
      "sourceRoot": "apps/cart/src",
      "projectType": "application",
      "targets": {
        "build": {
          "executor": "@nrwl/web:build",
          "options": {
            "outputPath": "dist/apps/cart",
            ...
          },
          ...
        },
        "serve": {...},
        "test": {
          "executor": "@nrwl/jest:jest",
          "options": {
            ...
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

It defines targets for build, serve and test. These can be invoked using the following syntax:

npx nx run <proj-name>:<target> <options>
Enter fullscreen mode Exit fullscreen mode

Note, we use npx in front because we don't have Nx installed globally. Thus npx will fallback and execute the installed binary in our node_modules folder. You can obviously also install Nx globally and get rid of having to prefix commands with npx

So to serve our app we run nx run cart:serve, to build it nx run cart:build and so on. There are also shortcuts, meaning we can alternatively invoke these commands like nx serve cart or nx build cart.

In Nx "targets" are invocable commands. There are predefined commands such as build, serve, test that get set up when you generate a new application. You can also define your custom ones, either by building your own Nx Executor or use the Nx Run-Commands.

Working on our Next App

Understanding Page Structures: Generating the About Page

When looking at the setup you'll see a "pages" folder. Every file returning a React component in there, instructs Next.js to generate a new page. As you can see there is an index.tsx page, which you see when navigating to the root of the Next website http://localhost:4200. To better understand this, let's create an About page that responds at http://localhost:4200/about.

Nx has some nice generators for that already. Hence, typing..

npx nx generate @nrwl/next:page --name=about --style=css
Enter fullscreen mode Exit fullscreen mode

..generates a new about.tsx (with its according styling file).

import './about.module.scss';

/* eslint-disable-next-line */
export interface AboutProps {}

export function About(props: AboutProps) {
  return (
    <div>
      <h1>Welcome to about!</h1>
    </div>
  );
}

export default About;
Enter fullscreen mode Exit fullscreen mode

Btw, if you're not the terminal kind of person, you can also use the Nx Console VSCode plugin.

If we now serve our app with npx nx serve site and navigate to /about, we should see something like the following:

Understanding getStaticProps

Next.js Docs

getStaticProps allow us to return props to our React component that's going to be pre-rendered by Next.js. It gets the context object as a parameter and should return an object of the form.

return {
  props: { /* your own properties */ }
}
Enter fullscreen mode Exit fullscreen mode

We can write our getStaticProps as follows:

// apps/site/pages/about.tsx
import { GetStaticProps } from 'next';
...

export interface AboutProps {
  name: string;
}
...

export const getStaticProps: GetStaticProps<AboutProps> = async (context) => {
  return {
    props: {
      name: 'Juri'
    },
  };
};
Enter fullscreen mode Exit fullscreen mode

Note how we use TypeScript to type the return value of our function to match our AboutProps from the about.tsx component. You can find more info about how to use the getStaticProps and others with TypeScript on the official Next.js docs.

We can now use the props in our React component:

export function About(props: AboutProps) {
  return (
    <div>
      <h1>Welcome, {props.name}!</h1>
    </div>
  );
}

export const getStaticProps: GetStaticProps<AboutProps> = async (context) => {
  ...
}
Enter fullscreen mode Exit fullscreen mode

Understanding getStaticPaths

Next.js Docs

If we want to create a blog, we'll want to load pages dynamically. So we cannot really give them a static name as we did with our About page (about.tsx).

nx generate @nrwl/next:page --name=[slug] --style=none --directory=articles
Enter fullscreen mode Exit fullscreen mode

This generates a new articles folder with a new [slug].tsx file. The [slug] part is where Next.js understands it is dynamic and needs to be filled accordingly. Let's also clean up the generated part a bit, changing the React component name to Article as well as the corresponding TS interface.

So first of all let's focus on the getStaticPaths function which we define as follows:

// apps/site/pages/articles/[slug].tsx
import { ParsedUrlQuery } from 'querystring';

interface ArticleProps extends ParsedUrlQuery {
  slug: string;
}

export const getStaticPaths: GetStaticPaths<ArticleProps> = async () => {
  ...
}
Enter fullscreen mode Exit fullscreen mode

According to the docs the function needs to return an object, having a paths as well as fallback property:

return {
  paths: [
    { params: { ... } }
  ],
  fallback: true or false
};
Enter fullscreen mode Exit fullscreen mode

The paths section contains the number of pages that should be pre-rendered. So we could have something like

return {
  paths: [
    {
      slug: 'page1'
    },
    {
      slug: 'page2'
    }
  ],
  ...
}
Enter fullscreen mode Exit fullscreen mode

From a mental model, this would instruct Next.js to "generate" (obviously it doesn't) at the place of our [slug].tsx a page1.tsx and page2.tsx which are then converted to pages accessible at /articles/page1 and /articles/page2.

This would be the place where you would go and read your file system or query the API for all the pages you wanna render. But more about that later. To simplify things, let us just generate a set of "pages":

export const getStaticPaths: GetStaticPaths<ArticleProps> = async () => {
  return {
    paths: [1, 2, 3].map((idx) => {
      return {
        params: {
          slug: `page${idx}`,
        },
      };
    }),
    fallback: false,
  };
};
Enter fullscreen mode Exit fullscreen mode

The returned params object can be accessed from within the getStaticProps which we've seen before and potentially remapped to something else. Here's the place where you could further elaborate the content, say we get the content in markdown, we could process and convert it to HTML here.

In this simple scenario we just pass it along:

export const getStaticProps: GetStaticProps<ArticleProps> = async ({
  params,
}: {
  params: ArticleProps;
}) => {
  return {
    props: {
      slug: params.slug,
    },
  };
};
Enter fullscreen mode Exit fullscreen mode

And finally we can access it from within the page React component:

export function Article(props: ArticleProps) {
  return (
    <div>
      <h1>Visiting {props.slug}</h1>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

What about fallback?

There's another property returned by the getStaticPaths function: fallback. The Next.js docs are pretty clear about it, so make sure to check them out.

In short, fallback: false renders only the set of pages returned by the paths property. If a given page doesn't find a match, a 404 page (that comes with Next.js) is being rendered.

It’s also useful when the new pages are not added often. If you add more items to the data source and need to render the new pages, you’d need to run the build again.

If fallback: true the difference is that pages that have not been rendered during build time (e.g. that are not in the paths property) will not result in a 404 page. Rather, Next.js returns a Fallback page (e.g. a page where you could display a loading indicator) and then statically generates the page and the corresponding HTML and sends it back to the client, where the fallback page is swapped with the real one. Furthermore, it will be added to the sets of pre-rendered pages, s.t. any subsequent call will be immediate.

Building and Exporting our Next.js application with Nx

Next.js defines two main options when it comes to generating your deployable:

  • build - allows to generate an optimized bundle that can be served by the next CLI, e.g. when deploying to some Vercel infrastructure. It requires a Node environment that can run the application. We will talk more about deployment of Next.js apps in an upcoming article
  • export - allows to generate a static site out of your Next.js application. This is ideal if you don't have a Node environment and you just want to serve the app from some static CDN.

Hence, also the Nx configuration (in workspace.json) has matching Nx targets (see the section about "Nx Workspace structure" to learn more).

We can invoke the "build" with

nx run site:build --configuration=production
Enter fullscreen mode Exit fullscreen mode

or alternatively with nx build site.

Similarly, the export can be invoked with

nx run site:export --configuration=production
Enter fullscreen mode Exit fullscreen mode

or nx export site. Using the export command will automatically build the Next.js app first.

By passing --configuration=production (or --prod) the production configuration is being used which is defined in the workspace.json and which can set additional production environment only properties:

"build": {
    "executor": "@nrwl/next:build",
    "outputs": ["{options.outputPath}"],
    "options": {
        "root": "apps/site",
        "outputPath": "dist/apps/site"
    },
    "configurations": {
        "production": {}
    }
},
Enter fullscreen mode Exit fullscreen mode

Conclusion

So here's what we learned:

  • How to generate a Next.js project with Nx
  • About the Nx workspace anatomy
  • How to generate new pages such as our site's about page
  • How to generate pages dynamically based on some slug and what role getStaticProps and getStaticPaths play with that. This part will be particularly useful to generate our blog post articles

GitHub repository

All the sources for this article can be found in this GitHub repository's branch: https://github.com/juristr/blog-series-nextjs-nx


Learn more

🧠 Nx Docs
👩‍💻 Nx GitHub
💬 Nrwl Community Slack
📹 Nrwl Youtube Channel
🥚 Free Egghead course
🧐 Need help with Angular, React, Monorepos, Lerna or Nx? Talk to us 😃

Also, if you liked this, click the ❤️ and make sure to follow Juri and Nx on Twitter for more!

#nx

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .