How to Structure Your Next.js App With the New App Router

Learn how to organize your Next.js project using a feature-driven structure with the new App Router, allowing for greater flexibility and efficiency in development.

Alen Ajam
Better Programming

--

Next.js App Router

Table of Contents

  1. The App Router
  2. Explaining the strategy
  3. What is a feature
  4. Structuring routes
  5. Organizing common files
  6. Composing a feature
  7. Bringing it together
  8. Final recommendations
  9. Wrapping it up

The App Router

Recently, Next.js 13.4 was released, marking an important milestone: the new App Router is now production-ready. This feature provides much greater flexibility in terms of project structure. You are no longer required to have a strictly “pages-only” directory where every JavaScript file inside of it is turned into a route, forcing you to store your other files elsewhere. Instead, you have a single “app” folder where you can organize your pages alongside any other related files. This is called colocation, and it allows for more efficient development.

👉 Disclaimer: The recommendations in this article are based on the author’s experience and opinion, and may not be the only or best way to structure a Next.js project.

Explaining the strategy

In this article, I will guide you through what I believe is the most flexible and scalable way to structure a Next.js project. While this article is tailored for the Next.js’ App Router, many of the basics could apply to any React project.

Most project structures you may come across are role-driven, but in this guide, we will be taking a feature-driven approach. To understand what a feature is exactly, please read the next section.

What is a feature

A feature is a group of files that are related to each other and overall represent an area or topic of your project. As I will explain later on, creating features can improve the maintainability and navigation of your codebase.

👉 Some examples are: the authentication, the landing, the dashboard.

Dividing your project into multiple features should make you think about the Separation of Concern design principle.

There are no set of rules for creating a feature, but here are a few questions you can ask yourself before making one:

  • Do I have at least two related files to start with?
  • Would grouping them improve the maintainability of my codebase?
  • Do these files share a common area or topic in my project?
  • Do these files share a common responsibility in my project?
  • Are all of the existing features in the project unable to do the job?

Note that a feature may or may not correspond to a route folder.

Remember, you are free to create features at any time. Actually, it’s often a good idea to first do the coding, free like a bird, and then think about optimizing. Because at that point, you have the big picture, and it becomes easier.

What benefits does a feature-driven structure bring? According to the Next Right Now documentation:

While MVC (and many other) design pattern groups code together based on “role” of each file, our approach is different and groups related code together.

MVC is very simple to understand because you don’t have to think about it, it’s very intuitive, at first. Therefore, it’s beginner-friendly.

But, it doesn’t scale.

When you reach a dozen different features, all that code is grouped together (e.g: “Components”) even though it’s not related to each other. On the other hand, when a developer wants to do something, it’s often related to a “feature”. But, the code related to the feature is splattered in many folders and sub-folders because of the MVC pattern.

This makes it much harder to locate the code, and doesn’t give an overview of all the related pieces.

Here is an example that demonstrates the aforementioned:

.
├── components/
│ ├── Some other component...
│ ├── Some other component...
│ ├── Some other component...
│ ├── Some other component...
│ ├── NewTweetForm.tsx
│ ├── Some other component...
│ ├── Some other component...
│ ├── Some other component...
│ ├── RecommendedTopics.tsx
│ ├── Some other component...
│ ├── Some other component...
│ ├── Some other component...
│ ├── Some other component...
│ ├── Sidebar.tsx
│ ├── Some other component...
│ ├── Some other component...
│ ├── Some other component...
│ └── TweetsList.tsx
├── hooks/
│ ├── Some other hook...
│ ├── Some other hook...
│ ├── Some other hook...
│ ├── Some other hook...
│ ├── Some other hook...
│ ├── Some other hook...
│ ├── Some other hook...
│ ├── useNewTweet.ts
│ ├── Some other hook...
│ ├── Some other hook...
│ ├── useRecommendedTopics.ts
│ ├── Some other hook...
│ ├── Some other hook...
│ ├── Some other hook...
│ ├── useTweets.ts
│ ├── Some other hook...
│ └── Some other hook...
└── pages/
├── Some other page...
├── Some other page...
├── Some other page...
├── Some other page...
├── Some other page...
├── Some other page...
├── Some other page...
├── Some other page...
├── Home.tsx
├── Some other page...
├── Some other page...
├── Some other page...
├── Some other page...
├── Some other page...
└── Some other page...
.
├── home/
│ ├── Home.tsx
│ ├── NewTweetForm.tsx
│ ├── RecommendedTopics.tsx
│ ├── Sidebar.tsx
│ ├── TweetsList.tsx
│ ├── useNewTweet.ts
│ ├── useRecommendedTopics.ts
│ └── useTweets.ts
├── profile/
│ ├── ...
│ ├── ...
│ ├── ...
│ └── ...
├── search/
│ ├── ...
│ ├── ...
│ ├── ...
│ └── ...
└── settings/
├── ...
├── ...
├── ...
└── ...

If you think about it, the difference lies in grouping your project based on logical reasoning, rather than mechanically categorizing every file, .

I believe this structure is more compatible with the way developers navigate a codebase. The downside, however, is that this approach can be more difficult for beginners, especially the first time they try it.

Taken a look at the comparison above, let’s say that you need to work on the home page. Which structure would make your life easier and point you in the right direction more quickly? I believe I made my point!

Structuring routes

The first thing we usually think about when structuring a web app are the routes and the pages related. Here’s how the app router enables you to structure those:

.
└── app/
├── home
├── landing
└── page.tsx

Suppose you want to render either the landing page or the home page based on the authentication status. A good way to start is by writing the conditional rendering logic inside page.tsx. Then, each case would have its own feature folder, allowing for neat grouping of everything related to it. Keep in mind that a folder is just a folder unless it contains a page.tsx. Only then does it become a route.

Pro tip: A more advanced way to achieve this is with Parallel Routes.

What if you need to place other files related to the root, such as authentication? Let’s see it in action:

.
└── app/
├── home
├── landing
└── (root)/
├── page.tsx
└── useAuth.ts

If you need to store files which will only be used by the root, you may group them in the root folder. In this case, I’m creating a route group by wrapping the folder name in parentheses. This is to avoid affecting the URL path.

Alternatively, if you need to store files common to the entire project, see the next section.

Organizing common files

It is common for projects to require global files, such as a basic button component. These common files are placed at the root level. In this specific case, and only in this case, a role-based grouping is adopted since these files are by definition not related to any feature. Here is an example of how it may look:

.
└── app/
├── ...
├── components
├── hooks
├── models
└── utilities

Please keep in mind that this is just an example, and you may group items in a way that makes sense to you.

There are no set rules for common files, but here are a few questions you can ask yourself before placing files in a common folder:

  • Would this file be used generally by the entire project?
  • Is this file not specific to any particular feature?
  • Would the project benefit from global access to this file?

Composing a feature

Within a feature folder, you can include all relevant files for that feature, such as components, images, constants, hooks, etc. This level of colocation makes it easy to work on features, as everything is readily available. If you want the feature to have a corresponding route, you can also include a page.tsx file.

If things become cluttered within a feature folder, you can further group files. Here is an example:

.
└── app/
├── ...
└── (auth)/
├── LogoutModal/
│ ├── LogoutModal.tsx
│ ├── LogoutModal.test.tsx
│ ├── LogoutModal.stories.tsx
│ └── LogoutModal.module.css
├── useAuth.ts
├── User.ts
├── sign-up/
│ └── page.tsx
└── login/
└── page.tsx

Let’s describe what we are looking at:

  • First, you have the auth feature. It includes everything that concerns the authentication of this app.
  • The sign-up page.
  • The login page.
  • The LogoutModal component. Grouping it in its own folder enables you to keep everything related to it in the same place.
  • The User model.
  • The useAuth hook.

Bringing it together

We have covered a few topics in a step-by-step manner. Now, it’s time to bring everything together:

.
└── app/
├── components/
│ └── Button/
│ └── Button.tsx
├── home/
│ ├── TweetsList/
│ │ └── TweetsList.tsx
│ ├── Sidebar/
│ │ └── Sidebar.tsx
│ ├── page.tsx
│ └── useTweets.ts
├── (auth)/
│ ├── useAuth.ts
│ ├── User.ts
│ ├── sign-up/
│ │ └── page.tsx
│ └── login/
│ └── page.tsx
├── hooks/
│ └── useSomething.ts
└── utils/
└── makeThings.ts

Let’s write a summary of the above:

  • The global folders: components, hooks, and utils.
  • The feature folders: home and auth .
  • The pages: /home, /sign-up, and /login.

Final recommendations

  • I suggest wrapping each component in a folder from the beginning. You will likely need additional files such as a style or test, so it’s best to prepare for them.
  • To reduce complexity, I recommend to avoid deep nesting. Typically, a one level nesting starting from the route or sub-route folder is sufficient.
  • If you have difficulty identifying models, you may name them something like User.model.ts.
  • Remember that you can make use of route groups that do not affect the URL paths.

Wrapping it up

I hope this article was helpful and inspiring for you to structure your project in a way that is scalable yet fits your needs. If I missed anything or if you have any doubts, please leave a comment and I will be happy to get back to you!

If you are interested in learning more about Next.js 13.4, you can check it out here. Also, you can find the documentation to the App Router here.

Thanks for reading.

--

--