Fullstack NextJS Project Architecture

5 Minutes

charlie beemy profile picture
By
  • code
  • nextJS
  • React
NextJS logo pofo

Sections

This is how I choose to architect my NextJS projects. Note these are personal design choices!

Organizing Components

My components and elements will always follow the same setup, so creating a new component is a pretty standardized process. The component folder will look something like this:

Component Folder

Component Folder

There are a couple of things to pay attention to:

Multiple Component Files

This folder has multiple .tsx files that correspond to its own component. They are all coupled together into the Article folder because they all belong or relate to an article. However, not all these components are exported out of this folder. The index.ts file actually looks like this:

Note that only the Article component is being exported. This is because the Article component is the only reusable component in this folder, meaning that the other components are partial to the main Article component. The reason they are separated out is that:

  1. Having all that code in one file will make it really big (the biggest component file I have in beemy is close to 900 lines, and that one makes me want to cry).

  2. To decouple state into smaller components to optimize the render cycle or it makes logical sense to have state stored that way.

Managing Types

There is a types.ts file that exports all the relevant types for this component. 95% of the time, the two types exported out of each component folder are Content and Props. Props is self-explanatory, but Content is a personal typing that I use within all my components for logic purposes. It represents any data that is displayed on the component. In other words, it is any content that is displayed on the screen. The types.ts file will look something like this:

These types are then exported from the components folder with another types.ts file. So if we need access to these types (examples of when we might need these types to show up later in the article!), we can import them like this:

Creating Reusable Components

If a component can be reused with small prop changes, reuse that component. However, do not try to over-reuse components. If a component requires additional javascript or a large number of HTML changes, then just create a new component file. I find that for the most part, larger components typically require custom code and are not easily reused unless they appear on similar types of pages.  Smaller building blocks (like buttons) are able to be reused much more easily. I label these smaller building blocks elements and have a separate folder for them in my project.

Handling Different Layouts

If you are building any type of interactive application, you are most likely going to need multiple different layouts for each respective type of page. The most common example would be an AppLayout and a DisplayLayout -> the app layout represents the application content for users and the display layout represents the content for the marketing website. Thus, it makes sense to have a layout folder that is organized in the same way as the components folder. An example layout file would look like this:

OK, let's break this down. This file follows the typical .tsx React component file structure. Note that we are making use of the Content types from the components folder for a more cohesive typing system. The returned HTML renders the children within the <main> tag and the header and footer components surrounding it. This layout can now be used across multiple pages!

Quick note:  If you have a simple website that only makes use of one layout, you can wrap the Layout around each rendered NextJS page through the _app file. Here is an example of how this would look.

Managing the db Folder

The db folder contains all requests made by your application. Since NextJS is a full-stack framework, many pages will make client and server requests within the same page file. This means that you have to pay extra attention to naming conventions and distinguishing between client and server functions. No server function should ever run on the client side, and the same applies to client functions on the server. Because of this, it makes sense to organize the db folder like this:

db Folder

db Folder

We can then couple each request to correspond to each recourse type. When we import from the db folder, we know where each function should be called because of its file location.

Some additional tips:

Managing Multiple APIs (GraphQL and REST)

GraphQL and REST functions will have a different structure. Because of this, I choose to separate the two types of functions into separate files: one called handlers.ts and the other called resolvers.ts. If you have a resource that makes use of both a REST endpoint and GraphQL, I preface all my handler functions with handle -> i.e handleFetchResource

When might you use both REST and GraphQL for a  single resource?

GraphQL is very good at handling structured requests. This means that you have to specify exactly what is sent to the server and sent back. However, sometimes you might need to make an unstructured update: more specifically updating any singular array index. Because the index you are updating depends on the data, it is impossible to provide a structured schema that GraphQL can consume. This means that this specific array update needs to have a REST endpoint.

Naming Convention

From personal experience, the only type of request that will overlap in the page files is GET. This means that you might have an instance where you fetch data on the server and fetch data on the client within the same file. Because of this, you want to have a distinct name for each function. For server GET requests, I use fetch. For client GET requests, I use get. All other request types will either be strictly on the server or strictly on the client.

Utilities

I recently reorganized my lib folder. That's 2 hours of focus I spent just moving files around that I'm never getting back. I highly recommend organizing utility functions and files from the get-go, because pretty quickly I had around 60 files piled into one folder and it was difficult to distinguish what purpose each file served. The most common segmentations for these utility files were:

  • Constants - shared constants that your project uses

  • Data - general data manipulation functions.

  • Resources - these are specific functions pertaining to resources in your project.

  • Utils - functions that don't fit the box for any of the above

These will differ from project to project, but these are the ones I use.

Styling

There are many styling libraries that you can use, and they all pretty much function to do the same thing - provide a framework to make your website look good. I don't really have an opinion on which one should be used, but I would say pick one that you are most comfortable with and have a method for avoiding duplicate styles. I personally use SCSS, and to avoid duplicate styles I use nested styling + name my classNames to correspond to the file name. Because each file name is unique, the classNames will be unique as well.

Made with