all Technical posts

Bringing Structure to Your Next.js Application

An opinionated guide on organizing your Next.js or React projects—made even easier with a little help from Nx.

When it comes to structuring your application, there’s no single ‘right’ way to do it. Some approaches work better than others depending on your project’s specific needs. This guide shares one approach that’s worked for us, but it’s just one option. If it doesn’t quite fit your style, that’s totally okay! If you have any suggestions, we’re always open to hearing them.

Starting a new project

When you kick off a new Next.js or React project, you’re often staring at a blank canvas. Where should the components go? How do you organize pages, services, and utilities? It can feel overwhelming, especially if you’re just starting out with the framework.

React doesn’t come with a built-in way to structure your application, so you’ll need to establish your own conventions. Next.js, on the other hand, is a bit more open. It offers some structure, but still leaves plenty of room for interpretation.

While this flexibility is great, it also makes it easy to end up with a tangled mess of files and folders that become difficult to navigate.

So, more often than not, people come up with a project structure like this:

This layout is a solid starting point, but as your application grows, managing it can become challenging. To tackle this, you might consider introducing modules or features directories, which is a step in the right direction:

We’ve tried both approaches in the past and, more often than not, ended up with something similar across projects—but not always identical. Sometimes it worked great, other times not so much. As we move between projects, navigating the codebase can sometimes become a bit of a challenge.

That’s why we’ve developed a structure that we believe hits the sweet spot. It’s one we’ve used in multiple projects and has proven to be both scalable and maintainable. While it might seem a bit overwhelming at first—especially if you’re new to the framework—it’s designed to grow along with your project.

Introducing the NX build system

NX (https://nx.dev) is a versatile set of development tools designed for monorepos, helping you build projects with the same efficiency as some of the top players in the industry. While it’s particularly useful for large-scale projects with multiple teams, Nx is also valuable for adding structure to any project, whether big or small.

Though Nx offers a lot more, we mainly use it for its organizational features and code generation capabilities. It’s a fantastic way to start a new project, giving you a solid foundation right from the beginning.

One of Nx’s standout features is its robust CLI commands for generating and managing code. We use these tools to kickstart our projects, and during development, we rely on custom-built generators based on Nx to scaffold new components, services, and utilities. This way, we can focus on building out features without getting bogged down by project structure.

While we can’t go into the nitty-gritty details of the custom scripts we’ve developed, we can give you a peek at how it all works. For example, we use a command like generate workspace, which leverages the Nx CLI to create a new workspace. Through a series of prompts, we can tailor the workspace to our needs, setting up all the necessary tools, libraries, and dependencies.

Once the workspace is up and running, generating new components, services, or utilities becomes as easy as running a single command. We’ve got configurations for running tests, linting, building, and running the application all set up seamlessly. This streamlined setup process lets us concentrate on what truly matters.

The project structure

Once the workspace is up and running, we organize our project with a structure that looks something like this:

This layout helps keep a clear separation between app-specific code and shared libraries, ensuring the project stays modular and scalable as it grows.

With Nx, we can define module boundaries using ESLint, which helps ensure that each feature remains self-contained and doesn’t unintentionally impact other parts of the application. For instance, core libraries should avoid depending on app-specific code to maintain the integrity of shared components. Plus, with build validation during Pull Requests, you’ll get notified if you inadvertently break any of these rules.

We also adhere to a set of best practices with this structure. For example, we keep page layouts within the apps directory as simple as possible, with most of the business logic and templates managed within the feature libraries. Features only expose the essential components to the app, keeping their internal details hidden and maintaining a clean, manageable codebase.

When it comes to components, we typically co-locate them with their related subcomponents. This approach simplifies code management and prevents the components root folder from becoming overcrowded. For instance, if you have an Overview component, we often place its related and internal Grid component in the same directory, like this:

In this setup, only the Overview component is exposed to the outside, while internal components like Grid stay “hidden.” This keeps the internal structure of your components contained and ensures that only what’s necessary is exposed to the rest of the application. Whether you choose to use index files in this setup is entirely up to you.

This structure helps keep the components directory well-organized and easy to navigate, even as the project grows. It also allows you to keep related files—like tests, styles, and utilities—together with the component, keeping everything consolidated and manageable.

Our core UI library is another crucial part of the project structure. It centralizes the styling and shared components used throughout the application, ensuring a consistent look and feel. By keeping this library separate from app-specific code, we can easily reuse components and styles across different features or even multiple applications, maintaining a cohesive design system.

You can use any styling solution with this library—be it CSS modules, Tailwind CSS, or comprehensive design systems like Chakra UI or Material-UI. The key is to use only the components from the core library within each feature to maintain a unified appearance across the application. For instance, an Input component in the core library might include several internal components like a label, input field, and error message. Within the feature of the application, you would only interact with the exposed Input component, keeping its internal structure abstracted.

So, instead of directly using components from a library like Chakra UI:

You would use components from the core library:

This approach helps maintain design consistency and lets the core library manage all UI abstractions. As a result, the JSX templates within the features remain cleaner, which significantly improves readability and maintainability.

We know that in theory, this all sounds great, but in practice, you’ll often start by building components within your feature libraries because it’s the quickest way to see results. While this approach is perfectly fine and often necessary, it’s a good habit to ask yourself whether each component is highly specific to that feature or if it has the potential to be reused elsewhere in the application or even in other applications.

Taking a moment to reflect on this can help you manage your components more effectively and keep your codebase organized. Plus, it can save you time in the long run by avoiding redundant work. As you develop, try to keep an eye out for components that might benefit from being part of the core library or shared across different features.

Conclusion

Whether you choose to use Nx or not, the principles outlined in this guide can help you structure your Next.js or React projects more effectively. By organizing your codebase into clear modules and libraries, you’ll create a scalable and maintainable architecture that can grow with your project. While Nx provides a robust foundation and powerful tools to streamline this process, similar results can be achieved with other build systems or custom scripts.

We hope you found this guide helpful! If you have any questions or suggestions, don’t hesitate to reach out. We’d love to hear about how you structure your projects and what strategies work best for you.

Subscribe to our RSS feed

Talk to the author

Contact Erwin

Architect

Hi there,
how can we help?

Got a project in mind?

Connect with us

Let's talk

Let's talk

Thanks, we'll be in touch soon!

Call us

Thanks, we've sent the link to your inbox

Invalid email address

Submit

Your download should start shortly!

Stay in Touch - Subscribe to Our Newsletter

Keep up to date with industry trends, events and the latest customer stories

Invalid email address

Submit

Great you’re on the list!