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
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:
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).
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
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 usesData
- 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.