DEV Community

loading...

A 2021 guide about structuring your Next.js project in a flexible and efficient way

Vadorequest
See Github Profile: https://github.com/Vadorequest/Vadorequest
・4 min read

Next.js

Next.js is very unopinionated about how to structure your Next.js project.

The only thing you must really be careful about is to not have anything under pages that aren't actual pages (e.g: tests, components, etc.), because there is no way to ignore them and Next will bundle and deploy them as actual pages.

You can choose to have your code at the root level (/pages) or under src (/src/pages). We prefer the latter, because it makes it more obvious what is actual app-related code and what isn't.

The usual folder structure

Most people will organize their project using a folder structure such as:

/public
    favicon.ico
/src
    /components
        /elements
            /auth
                AuthForm.tsx
                AuthForm.test.ts
            /[Name]
                [Name].tsx
                [Name].test.ts
    /hooks
    /types
    /utils
    /test
        /api
            authAPI.test.js
            [name]API.test.js
        /pages
            index.test.js

    /pages
        /api
          /authAPI
              authAPI.js
          /[name]API
              [name]API.js
        _app.tsx
        _document.tsx
        index.tsx
Enter fullscreen mode Exit fullscreen mode

This design pattern is very common, and it's great for small project because it makes it obvious where your files should be located. They're grouped by "kind of files". It's very similar to the MVC design pattern many developers are familiar with.

The main issue with this design pattern is its lack of scalability.

While it's great at the beginning, and can be a good fit depending on the size of your project, you'll realize a some point you'd find your files faster if they were grouped together by "module".

Once you reach 6-10 unrelated features, you'll wish to have the components files close to the utilities and to the TS type that are specific to your UI components, or even maybe your data model.

Also, you might prefer having all files related to a particular 3rd party within the same folder, as a module. (e.g: Sentry, Amplitude, etc.)

At some point, splitting files based on their kind will not be good enough. That's where you'll need modules.

The flexible and "modular" folder structure

Another way to organize things is to introduce modules.
Modules help group together code that is related to each other. They're not a replacement for what's common.

Here is how we might convert our previous folder structure to something a bit more modular:

/public
    favicon.ico
/src
    /common
        /components
            /elements
                /[Name]
                    [Name].tsx
                    [Name].test.ts
        /hooks
        /types
        /utils
    /modules
        /auth
            /api
                AuthAPI.js
                AuthAPI.test.js
            /components
                AuthForm.tsx
                AuthForm.test.ts
            auth.js
    /pages
        /api
          /authAPI
              authAPI.js
          /[Name]API
              [Name]API.js
        _app.tsx
        _document.tsx
        index.tsx
Enter fullscreen mode Exit fullscreen mode

We added a new src/modules/ folder where we group all files related to the same feature (here, the authentication as "auth"), instead of splitting the "auth" code everywhere, it is now centralized into a single folder.

You might have noticed the only thing that hasn't changed is the src/pages/ directory. I'll repeat myself, but you must not have anything there that isn't either a page or an API endpoint.

Everything related to the authentication is now in /src/modules/auth, it's much easier/faster to understand the code being used to authenticate now!

But, you don't want to always use modules, right? Sometimes you're writing some kind of utility that doesn't really fit in any module, something you'll want to write some code quickly and be done with it.

Modules introduce "thoughts complexity", because now you have a conscious choice to make about where your file should be. It was easier before to make this decision, because grouping files by kind is effortless, it's a choice that has an actual answer to it. Moving a component to the hooks folder is wrong, while moving it to the components is correct.

It's easy to know you did it right. (or wrong)

But, with modules, there is no right/wrong answer! Making it harder to make decisions. Sometimes you won't know, it won't be obvious at first (it may never be). Sometimes you'll figure it out afterwards ("oh, that's actually a module").

And because modules aren't the universal solution to this problem, the key is to allow both.

The common directory should be used for everything that isn't a module, while the modules directory should be used by everything you feel should be a "module".

This way, you get the best of both worlds:

  • The ability to quickly add code without thinking much about where it should be (common).
  • The ability to organize at your own pace when you feel like that thing has grown too big and all those pieces of code should be brought together (converting from common to modules).
  • The ability to quickly find your code in your existing modules and to have an overview of how big a module is.

I'm the author of Next Right Now, a Next.js production-ready boilerplate, and the above folder structure is what we're using since January 20, 2021. It is the result of feedbacks from the NRN community.

Alongside this new folder structure, Next Right Now has also migrated to "Module path aliases", which uses absolute paths instead of relative paths for importing modules. (e.g: import ('@/common/hooks/useStuff') instead of import ('../../../common/hooks/useStuff').

If you want to learn more on the topic, read the NRN folder structure documentation!

Discussion (1)

Collapse
alexman profile image
Alexander Hofstede

Thanks for taking the time to do a write up, we're all looking for good places to "put our stuff"!

From your folder structure however, I cannot deduce whether /modules/auth/api/AuthAPI.js would be something imported in /modules/auth/components/AuthForm.tsx (as a helper that exposes the API to the front-end components) or /pages/api/authAPI/authAPI.js (as a utility file that the page Javascript module defers to for actual authing) or somewhere else. In other words, is it front- or backend code?

The use of the term "module" is also tricky territory in a Javascript context, since it is often referred to when talking about a single file. (I had to call it "Javascript module" above for example! :)