Inspired by the video "How I structure my next.js applications" by Web Dev Cody explains a Next.js architecture setup that is heavily inspired by Clean Architecture and Layered Architecture. The core idea is to separate concerns into distinct layers to future proof your build.
### **Next.js Application Architecture Guidelines** This document outlines a recommended Next.js application architecture inspired by Clean Architecture and Layered Architecture principles. The goal is to separate concerns, improve maintainability, and enhance testability and flexibility. The architecture is divided into four distinct layers: *** #### **1. Presentational Layer (Controllers)** This layer is the entry point for all user interactions and houses all Next.js-specific code. Its primary responsibility is to handle the presentation of data and manage user input. * **Responsibilities**: * Contain all Next.js/React-specific code, such as **Server Actions**, React Server Components, `revalidatePath`, `revalidateTag`, and `redirect`. * Act as a **controller** that receives user input and orchestrates the response. * Handle authentication, input validation, and rate limiting before invoking the business logic. * **Best Practices**: * Keep this layer **thin** and focused on its core responsibilities. * Avoid including business logic within this layer. * **Collocate** components and actions within the same directory as their respective routes to improve maintainability and discoverability. Only create a separate components folder for truly reusable elements. *** #### **2. Business Layer (Use Cases/Interactors)** The business layer is the core of the application, containing all the business logic and rules. It is completely independent of Next.js and the specific database technology. * **Responsibilities**: * Implement **use cases** that represent the business operations (e.g., `updateGroupInfoUseCase`). * Enforce **authorization** and access control checks (e.g., verifying if a user is an "owner" or "admin"). * Contain all the core business rules and logic. * Return generic error objects or throw simple, non-framework-specific errors. * **Best Practices**: * This layer should have **no knowledge** of the Presentational or Persistence layers. * It should accept only plain data objects and not Next.js-specific objects. * Consider using **Entities** to encapsulate business rules and validation for your data models. *** #### **3. Persistence Layer** The persistence layer is responsible for all data storage and retrieval operations. It acts as an abstraction layer between the business logic and the database. * **Responsibilities**: * Provide simple, reusable methods for saving, updating, and fetching data (e.g., `updateGroup`, `getGroupById`). * Handle the logic for interacting with the specific **Object-Relational Mapper (ORM)** or database driver. * **Best Practices**: * This layer must **not expose ORM-specific details** to the business layer. For example, the business layer should not know if you're using Drizzle, Prisma, or raw SQL. * Consider the **Repository Pattern** to organize data operations. *** #### **4. Database Layer** This layer is the final destination for all data. It represents the actual database itself. * **Responsibilities**: * Store and retrieve data as instructed by the Persistence layer. * **Best Practices**: * This layer is the only one that is coupled to the specific database technology (e.g., PostgreSQL, MongoDB). *** #### **Key Architectural Principles** * **Separation of Concerns**: Each layer has a single, well-defined responsibility. * **Decoupling**: The layers are loosely coupled, with dependencies flowing inward from the Presentational Layer towards the Database Layer. This means the Business Layer doesn't know about Next.js, and the Persistence Layer doesn't know about the business logic. * **Maintainability**: A change in one layer, such as switching databases or ORMs, should not require changes in other layers. This makes the application more robust and easier to maintain long-term. * **Testability**: Each layer can be tested in isolation. For example, you can test your business logic without needing to mock Next.js functions or a live database connection. * **Data Transfer Objects (DTOs)**: Use DTOs to control the data flow between layers. This prevents the "bleeding over" of unnecessary or sensitive data and protects the underlying architecture from being exposed.