Table of Contents


You're reading Nexar beta version.

The reader is assumed to have some experience with the Next.js App Router.

For more information on the concepts I built this architecture upon, please read my short article on What is software architecture?

The source code of this website is public and hosted on GitHub.

This content is licensed under a Creative Commons Attribution 4.0 International License.

What is Nexar?

Nexar is a modular and layered application architecture specially designed for building monolithic full-stack applications with the new Next.js App Router, even though it might be adapted and used for similar stacks like Remix.

Monolith applications are an excellent option for new projects because they're faster to build, deploy, and manage as a single unit. And we can scale them for millions of users.

But the codebase of a monolith application doesn't have to be a big ball of mud. We can still have a modular and layered codebase and harvest the benefits of software architecture. And that's what Nexar is, a way to architecture the codebase of your monolith Next.js App Router apps.

It is inspired by industry best practices, including those from Domain-Driven Design and Patterns of Enterprise Application Architecture books. However, it's not an implementation of any particular architecture or pattern.

I designed it to be straightforward, minimal, and pragmatic while offering the benefits of application architectures.

Nexar is created by Flavio Silva, a full-stack software engineer with 20 years of experience building web applications.

Why Nexar?

A well-defined architecture helps developers understand the system and their role in building it. It promotes effective communication, collaboration, and alignment with business goals across different teams while enforcing clear conventions.

There are dozens of ways to organize an application, but every time we start a new one, we face the same problem on how to structure it. How should we break down the logic, components, and modules? Should we have layers?

That adds overhead from the beginning, taking critical time from the engineering team. And a mistaken initial decision is a costly one.

Nexar is another modular and layered application architecture. I'm sure many developers build apps using a similar architecture. I've been building React applications using similar architectures since 2017.

Nonetheless, agreeing on a well-documented architecture that fits your use case can dramatically speed up application development time, especially for small teams.

Nexar is designed to be simple and minimal, arranging the inherent complexity your app will have regardless of using it.

However, it's no silver bullet. If you think it might fit your use case, try it; it's built on solid architectural foundations. You might change it as you like, too; you don't have to follow everything as described here. Adapt it to your needs. And please share your thoughts; let's evolve it together.


Designing an application architecture solely in our minds is a recipe for disaster. We need to design it as we build apps. That's the only way to ensure they are good. And so I did.

OpenTask is a free, open-source, fully functional, and responsive task management app created as part of a Next.js App Router case study, and it currently implements Nexar beta as the codebase in the feb-2024 branch.

Having an open-source application as a reference implementation of Nexar is fantastic. Anyone can investigate its codebase to see how a fully functional and responsive application implements Nexar in its tiny details.

And since we need to talk about an application when explaining an application architecture, what better app to talk about than OpenTask?

So, I'll use the entire OpenTask's codebase as a reference implementation for Nexar. This way, after reading this documentation, you'll learn what Nexar is and how a real-world application implements it in its tiny details.

OpenTask is a straightforward task management app. It has a marketing site, a sign-in page, and application pages for authenticated users who can create projects and tasks. It has a Today page to show tasks due today and overdue and a Settings page where users can delete their accounts. It's a stripped-down version of Todoist.

Naming conventions

Naming conventions are complicated. What names to use and how to format them (Pascal case, Kebab case, etc) are mostly a matter of personal preference.

I like to use the Pascal case for naming React components, and since I like to have one component per file, I like to name the file the same. But those are personal preferences. Nexar has no recommendations regarding naming conventions. If the community comes up with good ones, we can adopt them.

Test suite

OpenTask has no tests, and I consider this its biggest engineering miss.

I postponed writing tests for the MVP because I used new tools and techniques, including React Server Components and Server Actions, and I needed to figure out what the codebase would look like. I also wanted to move fast while experimenting with those new features.

That made sense. But now I plan on adding a test suite, reevaluating tools and techniques to achieve an optimized way to test Next.js App Router apps, including React Server Components and Server Actions. Once I finish it, I'll update this doc to cover testing and a test suite.

Do you have a great way to test Next.js App Router apps? Please share it with us.

Starting a new Next.js App Router app

I will discuss some Next.js App Router implementation details when appropriate to provide a broad understanding of the app structure.

The recommended way to start a new App Router app is using create-next-app.

You may or may not use the src directory. Nexar recommends using it. It's a great way to separate application code from configuration files.

src/ folder structure

After starting a new Next.js App Router app, Next.js automatically creates an app folder inside the root src folder.

That's the root of any Next.js App Router application, and we cannot rename it.

Next.js uses a file-based routing system, where subfolders of the src/app/ folder define route segments. Each route segment is mapped to a corresponding segment in a URL path. Those route segments define the public entry points of our application, the URLs.

Each page.tsx in the src/app/ subtree defines a React component for its route segment and is used to compose the segment UI with reusable UI components. In Nexar, we define these reusable UI components at the top-level src/features/ folder, which is further explained.

Alongside the app folder we create a folder named features, so the following is the structure of our root src/ folder:


That way, Nexar provides a two-fold separation of concerns, with two types of modules: routing modules, which go into src/app/, and business feature modules, which go into src/features/.

Both types of modules should match perfectly whenever possible. In OpenTask, we have the following matches:



Why a separate src/features folder?

You might ask: why should we have a separate src/features folder? Why not put everything inside src/app?

Since we define the public application entry points in src/app, over time, we might have different entry points for the same feature, different entry points for campaigns related to the same feature, etc. After some time, they might get cluttered. Those are our routing modules.

Another issue arises when we implement Parallel Routes and Intercepting Routes.

When we implement a Parallel Route, we must create a special folder to define a named slot, starting its name with @. Then, we create a folder inside the named slot to place our code. That folder would be our business feature module.

However, having business feature modules inside a named slot folder, which represents a technical concept, would be awkward because that's a technical implementation detail from the framework and has nothing to do with the business, contributing to a confusing codebase. We'd mix technical concepts with business concepts. Since each can be complex by themselves, mixing them would be very confusing.

Besides, we might want to present the same business feature as a self-contained page via a regular route, which means we'd have two folders for the same feature. We'd have to choose one folder for our reusable components, while the other would have only Next.js-related files like page.tsx. That's not intuitive.

It'd get even worse when we implement Intercepting Routes. Besides the fact that Intercepting Routes requires a Parallel Route, i.e., the folder for an Intercepting Route must live inside a Parallel Route folder (the named slot), it also requires a special framework syntax, with the name of the folder starting with (.), (..), and so on. Awkward.

Intercepting Routes are the best way to implement dialogs (modals), a common feature in applications. When we do that, we should also implement a regular route for the dialog outside the Parallel / Intercepting folder structure, so when users refresh the page or share its link with someone, they can see it. Otherwise, they get a 404 error.

There are other use cases, like revisiting a discarded tab in Google Chrome. When I tested that use case in OpenTask, revisiting a tab that was rendering the app with an open dialog, the regular route was rendered instead of the Intercepting one, behaving like a hard refresh. If we don't have a regular route set up, users get a 404 error.

Implementing a regular route to support the use cases above requires having two folders for the same feature and, again, choosing one of them for our reusable components. That's not intuitive.

That's why we should decouple the routing code from the business feature code. That allows for a clean, intuitive, and meaningful structure for our business feature modules that reflects business concepts while allowing us to design and build the public entry points of our applications leveraging the framework's conventions.

We do that by having a separate src/features folder to build our business feature modules.

As we evolve our apps, we should refactor both types of modules to keep them clean and matching.

Nexar is a modular architecture

A modular architecture is an architectural style that breaks the system into meaningful subsystems; each subsystem is a self-contained module. There are many modular architectures, each focusing on solving a particular set of problems.

Modules should reflect business features

In Nexar, each module corresponds to a business feature, not a technical one.

So, instead of having modules like:


We have modules like:


Ideally, a business feature module is self-contained, i.e., it doesn't depend on code from any other module. In the real world, dependencies are natural.

For example, tasks belong to projects, meaning those modules have at least some degree of dependency.

Nonetheless, we should strive to design self-contained modules, exposing minimal APIs for their communication when necessary.

Modules can have submodules. For example, app is a module that hosts submodules for the product's application, i.e., the features reserved for authenticated users.

Let's first look at the routing modules and, after them, the business feature modules.

src/app/ folder structure

We should use the src/app/ folder exclusively for routing code (our routing modules), i.e., Next.js' routing files and folders like page.tsx, layout.tsx, loading.tsx, and error.tsx. Everything else goes to the top-level src/features/ folder.

Nexar implements a modular architecture for our routing and business feature code that reflects the application's business features.

The following is the folder structure of the routing modules of OpenTask at src/app:


The src/app/layout.tsx is the root layout of a Next.js App Router application, containing the <html> and <body> tags. Next.js App Router supports nested layouts, with parent layouts wrapping children layouts using React's children prop.

We should leverage App Router's features and best practices as much as possible, using Parallel Routes, Intercepting Routes, and layout.tsx, loading.tsx, error.tsx, etc, files properly.

marketing routing module

The following is the folder structure of the src/app/(marketing) routing module, defining marketing and legal route segments:


The src/app/(marketing)/ folder defines a Route Group for marketing pages. Route Groups (folders in parenthesis) are omitted from the URL while allowing a layout implementation for the group, alongside other Next.js-specific files, including error.tsx and loading.tsx.

The src/app/(marketing)/layout.tsx implements the layout for marketing and legal pages and is wrapped by the root layout at src/app/layout.tsx.

The src/app/(marketing)/page.tsx defines the product's landing page. Since folders in parenthesis are omitted from the URL, that's the root page.tsx file.

auth routing module

The following is the folder structure of the src/app/auth routing module, defining authentication route segments:


The app/auth/layout.tsx implements the layout for authentication pages and is wrapped by the root layout at app/layout.tsx.

The app/auth/error.tsx is rendered in place of app/auth/sign-in/page.tsx when there's a JavaScript error when users try to sign in.

app routing module

The following is the folder structure of the src/app/app/ routing module, defining application route segments for authenticated users:


The src/app/app/layout.tsx implements the layout for application pages and is wrapped by the root layout src/app/layout.tsx.

We define a @dialog Parallel Routing at the root of the product's application that we use to render dialogs (modals) as Intercepting Routes.

When users sign in, they're redirected to, rendering the <OnboardingPage> component defined at src/app/app/onboarding/page.tsx.

If users try to navigate to, they're redirected to /app/onboarding. We handle this redirect in src/app/app/page.tsx.

The src/app/app module has submodules that we see next.

onboarding routing module

The following is the folder structure of the src/app/app/onboarding/ routing module, defining a single onboarding route segment:


projects routing module

The following is the folder structure of the src/app/app/projects/ routing module, defining project route segments:


tasks routing module

The following is the folder structure of the src/app/app/tasks/ routing module, defining task route segments:


today routing module

The following is the folder structure of the src/app/app/today/ routing module, defining a single today route segment:


settings/account routing module

The following is the folder structure of the src/app/app/settings/account/ routing module, defining a single account route segment:


The following is the folder structure of the src/app/app/main-menu/ routing module, defining a single route segment:


This page is accessed only from mobile devices, which opens the main menu fullscreen in a dialog for a better user experience. On large screens, the main menu is always visible as a sidebar.

src/features/ folder structure

So far, we've been discussing only routing modules, which go inside src/app. Now, we'll see the business feature modules inside src/features.

Nexar implements a modular and layered architecture for our business feature modules that reflects the application's business features.

The following is the folder structure of the business feature modules of OpenTask at src/features:


The app feature module hosts all business feature modules for the application routes, i.e., routes for authenticated users.

The auth feature module hosts reusable components imported by authentication routes.

The marketing feature module hosts reusable components imported by marketing routes.

The shared feature module hosts reusable components imported by any route. That's a special "feature" module that's not actually a business feature but a helpful convention among codebases for code reusability.

Nexar is a layered architecture

The layered architecture is one of the most common architectural patterns and a great way to implement the Separation of Concerns principle.

In a layered architecture, we organize components into horizontal layers, each on top of the other. The communication between layers is typically unidirectional, with a higher layer only using the services from its lower layer.

Although there are many variations of the layered architecture, the industry has converged a few layers.

Layers are typically at the top level of codebases; for example:


And inside each layer, we would have code for the application's features.

But on Nexar, it's the other way around. We have the business feature modules at the top-level folder structure as our modular architecture and a layered architecture inside each business feature module.

Nexar's three main layers

projects and tasks are classic business feature modules implementing the product's key features.

We define them as top-level modules of our application as:


We have a layered architecture inside each feature module for the three main layers of Nexar: Domain, Data Access, and UI.

Each file in a feature module implements the logic of a single layer. We do not mix the code of different layers in the same file.

We can have the files of different layers at the root of feature modules, or we might have a folder for each layer.

Having all files at the root of feature modules might be more suitable for small modules with fewer files. A folder for each layer might be more suitable for large modules with many files.

In OpenTask, I decided always to have a folder for each layer to have a uniform codebase. But that's a personal preference, not a recommendation.

The following diagram illustrates the three main layers and their intercommunication:

Nexar Layers Diagram

Now, let's see the details of each layer.

Domain layer

The domain layer is the heart of complex software, providing components to execute business logic and rules.

In Nexar, the domain layer may depend on the domain layer of other modules to enforce its rules, but it should never depend on other layers.

The domain layer and domain modeling are incredibly complex subjects at the center of the fantastic Domain-Driven Design book.

In Nexar, we want a straightforward and lightweight domain layer to isolate the necessary business logic implemented as pure functions.

Although Domain-Driven Design and the domain layer are typically implemented in the OOP paradigm, they can also be implemented in FP, which is more appropriate in the context of JavaScript, React, and Next.js.

Data Access layer

The Data Access layer provides components to access data from different sources, abstracting away the specific details of data storage and CRUD operations. It should validate data before persisting it and implement caching, access control, and security strategies.

It depends on components from the Domain layer to enforce business rules. For example, verify if a user can access or perform some data mutation.

It typically depends on reusable code that abstracts away data access and related problems. For example, OpenTask's current implementation uses Prisma.

The Data Access layer should never depend on the UI layer.

A Data Access layer is recommended by Vercel.

UI layer (a.k.a. Presentation layer)

I've worked on many React applications where I chose to have separate Application and UI layers. Sometimes, it was overkill; other times, it was necessary for the project's success.

Such separation allows the UI layer to provide highly reusable, dumb UI components that only implement UI-related logic and behavior.

In such architectures, the Application layer provides components for application behavior, assembling dumb UI components into functional ones. It translates UI events, like button clicks, into application events and flows, like authenticating users. It fetches data, coordinates, and delegates work by implementing and reusing logic from other layers.

In React, this separation is also known as the Container/Presentational Pattern (Dan Abramov also wrote about it back in 2015).

When I started OpenTask, I experimented with different approaches, including separating Application and UI logic. However, since we can eliminate all client-side data fetching and application-level state management logic with the new App Router, a separate Application layer proved to bring little to no value.

Instead, in Nexar and so OpenTask, the UI layer has two kinds of React components:

  1. Dumb UI components that only render data on the screen and listen to interactive events, like <Dialog> and <ProjectListItem>;
  2. Components with a thin layer of application logic alongside rendering logic, like data fetching, calling a Server Action, or validating forms, like <ProjectList> and <ProjectForm>.

I call the React components that fetch their data full-stack React components: they're self-contained data-driven React Server Components that are fully reusable and composable despite their data dependencies. They weren't possible before React Server Components. <ProjectList> and <TaskList> are two examples from OpenTask.

It's worth noting that this is not the same as the Smart UI Anti-Pattern, as we still have separate Domain and Data Access layers.

The UI layer depends on the Data Access layer to fetch and mutate data by directly calling data fetching functions and Server Actions.

The UI layer depends on the Domain layer to enforce some business rules, like validating forms for faster UX, even though we should always validate forms and data on the server regardless of client validation.

We can have functions in the UI layer that are not React components.

OpenTask has the following function:


It receives two date objects and returns a string. It's not a React component. Nonetheless, its logic belongs to the UI layer.

It's also worth noting that files in the routing modules, i.e., in the Next.js' src/app folder, also belong to the UI layer. page.tsx, layout.tsx, loading.tsx, error.tsx, etc, are all React components that belong to the UI layer. We must define them separately from the rest of the UI components that live in the src/features folder, but that's just an implementation detail we must live with.

Other layers

Routing layer (a.k.a. Navigation layer)

Next.js is a comprehensive framework with a complete routing solution. That's why most apps won't need a routing layer at all.

In OpenTask, we only have a small routing utility I built at src/features/shared/routing. You can read about it here (a link will be added soon).

However, a Routing layer might be necessary for complex cases.

I once led the development of a CRA-based cross-platform React Native Web app. I put together a modular and layered architecture with a Routing layer that used a routing solution for iOS and Android and another for the web to take the most out of each platform. That allowed the team to isolate most of the platform-specific code in the Routing layer and reuse almost the entire codebase among all three platforms.

Will React Native support React Server Components? I can't say, but the community is working on it.

Building a cross-platform Next.js App Router app using React Server Components and a hybrid component tree would be fantastic. In such cases, a Routing layer might be necessary to support different routing strategies, or we might use a cross-platform routing solution like Expo Router.

If we have a concrete case, we might see how a Routing layer fits into Nexar.

Infrastructure layer

React and Next.js provide the infrastructure layer upon which we build our apps. That's why most apps won't need an Infrastructure layer, either.

However, sometimes, we must write some infrastructural code that doesn't fit the other layers. In such cases, we can use the Infrastructure layer to sort it out.

Infrastructure code usually spans several modules, which makes it ideal to go into a shared module, e.g., src/features/shared/infrastructure. But we should place it in specific feature modules when that's the case.

Business feature modules

Let's now return to OpenTask's business feature modules at our root src/features folder.

shared business feature module

The following is the folder structure of the src/features/shared business feature module:


The following is the folder structure of the src/features/shared/data-access layer:


The following is the folder structure of the src/features/shared/routing/ layer:


The following is the folder structure of the src/features/shared/ui/ layer:


To keep it brief, I've omitted UI files. You can see them here.

marketing business feature module

The following is the folder structure of the src/features/marketing/ business feature module:


The src/features/marketing/shared/ module hosts reusable components imported by any marketing route.

We can have reusable components for specific marketing routes in folders such as features/marketing/landing-page and features/marketing/pricing if necessary.

We could've opted not to have a ui layer folder and have the code in the module's root at shared/ for simplicity since that's the only layer of code we have in this module.

auth business feature module

The following is the folder structure of the src/features/auth/ business feature module:


The following is the folder structure of the src/features/auth/data-access layer:


app business feature module

The following is the folder structure of the src/features/app/ business feature module:


The app feature module is an umbrella module for its submodules, comprising the product's application features. OpenTask doesn't contain any code in the app module itself, but it could if needed.

app/shared business feature module

The following is the folder structure of the src/features/app/shared/ business feature module:


That's a special "feature" module that's not actually a business feature but a helpful convention among codebases for code reusability.

We could have a folder for each component above, like ui/header and ui/main-menu. However, since there are fewer components, it's okay to have all of them share the same ui folder. Again, these are personal preferences, not recommendations.

projects business feature module

The following is the folder structure of the src/features/app/projects/ business feature module:


Not all UI files are listed above; you can check them here.

In OpenTask, the ProjectsDomain.ts implements business logic for the project business concept in a task management application.

Naming the file ProjectsDomain.ts is not a recommendation; you can use a different name, and please share your preferences. Besides, a single file works well if we don't have a lot of business logic. Having more than one file would require changing this naming.

OpenTask is very simple, and the implementation of ProjectsDomain.ts reflects that simplicity: data validation to create and update projects using zod.

If needed, we can add pure functions in that file to implement additional business logic. For example, if we had support for team members, we could have a canUserDeleteProject(user, project) pure function to validate if users can delete projects they're members of but not owners.

The ProjectsDataAccess.ts implements the data access logic for projects; it's a classic CRUD implementation. It also implements Data Transfer Object (DTO) TypeScript types inferred from Zod schemas. DTO is a pattern to pass data through processes. In an App Router app, we pass data between the server and the client, and between JavaScript functions.

Naming the file ProjectsDataAccess.ts is not a recommendation either; you can use a different name. Besides, a single file works well if we don't have a lot of data access logic. Having more than one file would require changing this naming.

tasks business feature module

The following is the folder structure of the src/features/app/tasks/ business feature module:


Not all UI files are listed above; you can check them here.

This module is implemented in the same way as the projects business feature module we just saw above, i.e., TasksDomain.ts implements data validation to create and update tasks using zod, and TasksDataAccess.ts is a classic CRUD implementation and also implements DTOs.

today business feature module

The following is the folder structure of the src/features/app/today/ business feature module:


settings/account business feature module

The following is the folder structure of the src/features/app/settings/ business feature module:


users business feature module

The following is the folder structure of the src/features/app/users/ business feature module:



I designed Nexar to take full advantage of the new Next.js App Router and meet OpenTask's needs.

I want to build more apps using it to keep improving it, and I would love to evolve Nexar collectively, exchanging knowledge with the community. To share your thoughts, please open a GitHub discussion. Let's talk about it.

About versioning the architecture

The page will always show the latest version of the architecture.

I plan to keep using and making several minor improvements to Nexar. I'm calling the present version a beta version to make these improvements possible without explicitly versioning them in the next several months (although any changes will always be visible in Nexar's public repo). I also intend to provide more detailed explanations of specific concepts and cover OpenTask's implementation in greater depth.

However, I'm not planning to make any extensive changes to Nexar so that we can consider its overall design stable. I would design a new architecture to meet different use cases that don't fit the current state of Nexar.

If I can keep using and improving it, it might make sense to release a v1.0 by the end of this year (2024). But I'd love to evolve the architecture with the community, so please open a GitHub discussion and give some feedback. Let's talk about it. I'm sure there are tons of improvements to be made to Nexar.

I plan to create versioned URLs for older versions of the architecture for future reference when new versions are released.