# Next.js Enterprise Project Structure

> Set yourself up for success in large Next.js project structures by following this opinionated layout.

- Canonical: https://dennisokeeffe.com/blog/2021-12-06-nextjs-enterprise-project-structure
- Published: 2021-12-06
- Updated: 2021-12-06
- Tags: nextjs, typescript

During a course on [Enterprise Architecture Patterns](https://frontendmasters.com/courses/enterprise-patterns/) by [Łukasz Ruebbelke](https://twitter.com/simpulton), I was introduced to the idea of **"The Iron Triangle of Programming"**.

The concept is around about three pillars of code that when left poorly managed tend towards programmer purgatory. These concepts are:

1. **Code volume**: the larger the codebase, the more unmanageable it can become.
2. **State management**: complex state management leads towards this aforementioned purgatory.
3. **Flow control**: the less readable your conditional logic is, the more difficult it becomes to maintain.

It is undeniable that code bases will continue to grow, so we can set ourselves up for success with **code volume** by focusing on the project layout.

This post will display an overview of an opinionated project structure and how to set it up for Next.js. This code structure is one that has made it more manageable for me to separate application code from configuration as well as infrastructure code when maintaining a large, mono-repo.

Source code can be found on my [**okeeffed/nextjs-enterprise-project-structure** GitHub repo](https://github.com/okeeffed/nextjs-enterprise-project-structure).

## Prerequisites

1. Basic familiarity with [Create Next App](https://nextjs.org/docs/api-reference/create-next-app).
2. Basic familiarity with [Next.js](https://nextjs.org/).
3. Basic familiarity with [TypeScript](https://www.typescriptlang.org/).

## Getting started

We will let `create-next-app` create the project directory `nextjs-enterprise-project-structure` for us:

```s
$ npx create-next-app@latest --ts nextjs-enterprise-project-structure
# ... creates Next.js app for us
$ cd nextjs-enterprise-project-structure
```

The `ts` flag will enable TypeScript support.

At this stage, a working Next.js app is ready for us. We can check this by running `npm run dev` and checking the app runs on localhost.

## Opting for a `src` directory

By default, using the [tree](<https://en.wikipedia.org/wiki/Tree_(command)>) command, we can see that our project structure looks like the following:

```s
# Outputs the tree ignoring node_modules and .git.
$ tree -I "node_modules|.git"
.
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── pages
│   ├── _app.tsx
│   ├── api
│   │   └── hello.ts
│   └── index.tsx
├── public
│   ├── favicon.ico
│   └── vercel.svg
├── styles
│   ├── Home.module.css
│   └── globals.css
└── tsconfig.json
```

The first change that we are aiming to make to opt to use a `src` folder to host our Next.js code.

Using `src` is documented under the [Next.js src directory documentation](https://nextjs.org/docs/advanced-features/src-directory).

The aim of this is to separate our application code from our top-level configuration.

If you have written a large Next.js project before, you'll notice that the top-level folders start to become unruly as you add more and more top-level folders to separate our application concerns.

Placing them within `src` enables us to keep our top-level folders clean and organized as well as have a mutual understanding across developers about where code for the application belongs.

For this project, we can move `pages` and `styles` into `src`.

```s
# Create `src` folder
$ mkdir src
# Move desired folders into `src`
$ mv pages styles src
```

If we now run `npm run dev` again, then you'll see that without changing our configuration more than moving the `pages` folder, then the Next.js config will know where to find our project entry point (as per the documentation).

> Note: as the documentation says, configuration files should not be moved to `src` nor the `public` folder.

At this stage, if we re-run our `tree` command the project structure will look like the following:

```s
# Output updated structure
$ tree -I "node_modules|.git"
.
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   └── vercel.svg
├── src
│   ├── pages
│   │   ├── _app.tsx
│   │   ├── api
│   │   │   └── hello.ts
│   │   └── index.tsx
│   └── styles
│       ├── Home.module.css
│       └── globals.css
└── tsconfig.json
```

## Breaking up our `src` folder

Now that we have our `src` folder, it is worth breaking things down even further into logical pieces.

In general, the folder structure that I generally opt for with larger projects follows the concept of [**Colocation**](https://kentcdodds.com/blog/colocation) but still using the design principle [**Separation of Concerns**](https://en.wikipedia.org/wiki/Separation_of_concerns) as a guideline.

For my larger projects, this normally means that my `src` folder is split into four folders:

1. `pages`: This is still used for my `pages` layout (and is required for Next.js, so it ain't going no where).
2. `common`: This folder consists of common code that I use across my application. I will speak more to this in a latter section but it generally contains most of the code.
3. `modules`: "Modules" for my is synonymous to features or logical groupings of code that make up the larger pages.
4. `content`: This itself can be optional or moved to elsewhere, but it is generally where I put my copy, internationalization and images etc. that I do not keep public.

The `common` folder itself generally becomes the most complex. Within that, I also define a number of different folders based on the project I am running:

For example, a current larger project I am working on currently has the following folders within `common`:

```s
$ tree src/common -L 1
src/common
├── components
├── context
├── hooks
├── mocks
├── queries
├── styles
├── types
└── utils
```

Within `components` again, I generally break down my components into three areas:

1. `application`: components that pertain to only being used within the application. This includes things like login components, page shells, etc.
2. `marketing`: any component that is generally used for marketing purposes like pricing tables, etc.
3. `ecommerce`: all components related to e-commerce work. Think carts, checkout, etc.

It is entirely up to you how you structure your `common` folder, but the same principle applies of colocating when directly related or following the separation of concerns.

Once this has been set up (and if you are using TypeScript), the last change that I make to the initial setup is to use TS configuration paths.

## Setting up TypeScript configuration paths

Within `tsconfig.json`, you can set the compiler option with `baseUrl` to be `.` and then set a `paths` array to help with the import statements.

My personal rule of thumb is to set a path for `modules` and `content` and then for each of the folders within `common`:

```json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/components/*": ["src/common/components/*"],
      "@styles/*": ["src/common/styles/*"],
      "@modules/*": ["src/modules/*"],
      "@content/*": ["src/content/*"]
    }
    // ... omitted for brevity
  }
  // ... omitted for brevity
}
```

This now means that if we have a named export component available in `src/common/components/application/my-component.tsx`, we can import it as `import { MyComponent } from '@/components/application/my-component'`.

To see some of this structure in action, let's update the default Next.js app to reflect some of the concepts we've spoken about.

## Breaking the default app up

At this point, we want our `src` to have the following structure:

```s
$ tree src
src
├── common
│   ├── components
│   │   └── marketing
│   │       ├── Card
│   │       │   ├── Card.module.css
│   │       │   ├── Card.tsx
│   │       │   └── index.ts
│   │       ├── Footer
│   │       │   ├── Footer.module.css
│   │       │   ├── Footer.tsx
│   │       │   └── index.ts
│   │       └── SimpleGrid
│   │           ├── SimpleGrid.module.css
│   │           ├── SimpleGrid.tsx
│   │           └── index.ts
│   └── styles
│       └── globals.css
├── modules
│   └── home
│       ├── Home.module.css
│       ├── Home.tsx
│       └── index.ts
└── pages
    ├── _app.tsx
    ├── api
    │   └── hello.ts
    └── index.tsx
```

Within `common` you will need to create a `components` folder and a `styles` folder and within `modules` we need to create a `home` folder.

Given that everything displayed is what I would consider "marketing" content, I will create a `marketing` folder within `common` and within that, I will create a `Card`, `Footer` and `SimpleGrid` component to reflect some of the "re-useable" components that I identified within the home page.

> You could possibly go further with the "title" and "description" of the home page, but this will be enough for now.

Below will go into depth of each file we create and what to add to it.

Note that I personally opt to use [barrels](https://github.com/basarat/typescript-book/blob/master/docs/tips/barrel.md) and enforce it with [ESLint rules](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-internal-modules.md), but you may argue it goes against the concept of `Code Volume` that we spoke about earlier with the Iron Triangle of Programming.

## src/common/components/marketing/Card

The following files and content were created.

**Card.module.css**:

```css
.card {
  margin: 1rem;
  padding: 1.5rem;
  text-align: left;
  color: inherit;
  text-decoration: none;
  border: 1px solid #eaeaea;
  border-radius: 10px;
  transition: color 0.15s ease, border-color 0.15s ease;
  max-width: 300px;
}

.card:hover,
.card:focus,
.card:active {
  color: #0070f3;
  border-color: #0070f3;
}

.card h2 {
  margin: 0 0 1rem 0;
  font-size: 1.5rem;
}

.card p {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.5;
}
```

**Card.tsx**:

```tsx
import * as React from "react";
import styles from "./Card.module.css";

interface CardProps {
  href: string;
  title: string;
  body: string;
}

export const Card: React.FC<CardProps> = ({ href, title, body }) => {
  return (
    <a href={href} className={styles.card}>
      <h2>{title}</h2>
      <p>{body}</p>
    </a>
  );
};
```

**index.ts**:

```ts
export { Card } from "./Card";
```

## src/common/components/marketing/Footer

**Footer.module.css**:

```css
.footer {
  display: flex;
  flex: 1;
  padding: 2rem 0;
  border-top: 1px solid #eaeaea;
  justify-content: center;
  align-items: center;
}

.footer a {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-grow: 1;
}

.logo {
  height: 1em;
  margin-left: 0.5rem;
}
```

**Footer.tsx**:

```tsx
import * as React from "react";
import Image from "next/image";
import styles from "./Footer.module.css";

export const Footer: React.FC = () => {
  return (
    <footer className={styles.footer}>
      <a
        href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
        target="_blank"
        rel="noopener noreferrer"
      >
        Powered by{" "}
        <span className={styles.logo}>
          <Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
        </span>
      </a>
    </footer>
  );
};
```

**index.ts**:

```ts
export { Footer } from "./Footer";
```

## src/common/components/marketing/SimpleGrid

**SimpleGrid.module.css**:

```css
.grid {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  max-width: 800px;
}

@media (max-width: 600px) {
  .grid {
    width: 100%;
    flex-direction: column;
  }
}
```

**SimpleGrid.tsx**:

```tsx
import * as React from "react";
import styles from "./SimpleGrid.module.css";

export const SimpleGrid: React.FC = ({ children }) => {
  return <div className={styles.grid}>{children}</div>;
};
```

**index.ts**:

```ts
export { SimpleGrid } from "./SimpleGrid";
```

## src/common/styles

**globals.css**:

```css
html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
    Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

a {
  color: inherit;
  text-decoration: none;
}

* {
  box-sizing: border-box;
}
```

## src/common/components/modules/Home

**Home.module.css**:

```css
.main {
  min-height: 100vh;
  padding: 4rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.title a {
  color: #0070f3;
  text-decoration: none;
}

.title a:hover,
.title a:focus,
.title a:active {
  text-decoration: underline;
}

.title {
  margin: 0;
  line-height: 1.15;
  font-size: 4rem;
}

.title,
.description {
  text-align: center;
}

.description {
  margin: 4rem 0;
  line-height: 1.5;
  font-size: 1.5rem;
}

.code {
  background: #fafafa;
  border-radius: 5px;
  padding: 0.75rem;
  font-size: 1.1rem;
  font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
    Bitstream Vera Sans Mono, Courier New, monospace;
}
```

**Home.tsx**:

```tsx
import * as React from "react";
import { Card } from "@/components/marketing/Card";
import { SimpleGrid } from "@/components/marketing/SimpleGrid";
import styles from "./Home.module.css";

export const Home: React.FC = () => {
  return (
    <main className={styles.main}>
      <h1 className={styles.title}>
        Welcome to <a href="https://nextjs.org">Next.js!</a>
      </h1>

      <p className={styles.description}>
        Get started by editing{" "}
        <code className={styles.code}>pages/index.tsx</code>
      </p>

      <SimpleGrid>
        <Card
          title="Documentation"
          body="Find in-depth information about Next.js features and API."
          href="https://nextjs.org/docs"
        />
        <Card
          title="Learn &rarr;"
          body="Learn about Next.js in an interactive course with quizzes!"
          href="https://nextjs.org/learn"
        />
        <Card
          title="Examples"
          body="Discover and deploy boilerplate example Next.js projects."
          href="https://github.com/vercel/next.js/tree/master/examples"
        />
        <Card
          title="Deploy &rarr;"
          body="Instantly deploy your Next.js site to a public URL with Vercel."
          href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
        />
      </SimpleGrid>
    </main>
  );
};
```

**index.ts**:

```ts
export { Home } from "./Home";
```

## src/pages

**index.tsx**:

```tsx
import type { NextPage } from "next";
import Head from "next/head";
import { Home } from "@modules/home";
import { Footer } from "@/components/marketing/Footer";

const IndexPage: NextPage = () => {
  return (
    <div
      style={{
        padding: "0 2rem",
      }}
    >
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <Home />
      <Footer />
    </div>
  );
};

export default IndexPage;
```

As for the `_app.tsx` file, we need to update the styles import:

```tsx
import "@styles/globals.css";
import type { AppProps } from "next/app";

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

export default MyApp;
```

## Running the app one last time

At this stage, if you now run the app again, you will see **no changes** to the default app, but we now have an example of the default app working in a format that follows some of the opinionated conventions mentioned above.

## Summary

Today's post demonstrated an opinionated approach to a Next.js project structure that can scale.

Although we used a contrived example with the default application, you can see how the files begin to break up and can be reused.

It is important to note that the example app did not break up things into pieces such as `i18n` for content, nor displayed how the configuration works at the top-level as you add more and more to the mono-repo (things such as infrastructure-as-code or dev-ops files).

The larger the app becomes, the more a layout like the one displayed becomes useful!

The final code can be found at [**okeeffed/nextjs-enterprise-project-structure** GitHub repo](https://github.com/okeeffed/nextjs-enterprise-project-structure).

## Resources and further reading

1. Basic familiarity with [Create Next App](https://nextjs.org/docs/api-reference/create-next-app).
2. Basic familiarity with [Next.js](https://nextjs.org/).
3. Basic familiarity with [TypeScript](https://www.typescriptlang.org/)
4. [okeeffed/nextjs-enterprise-project-structure GitHub repo](https://github.com/okeeffed/nextjs-enterprise-project-structure)

_Photo credit: [`lanceanderson`](https://unsplash.com/@lanceanderson)_
