С появлением App Router и серверных компонентов (React Server Components) фреймворк Next.js стирает грань между традиционным фронтендом и бэкендом. Разработчики получили возможность обращаться к базе данных, сторонним API и микросервисам напрямую из компонента или Server Action.
Однако свобода имеет обратную сторону: в крупных корпоративных веб-приложениях бизнес-логика, UI-верстка и SQL-запросы быстро смешиваются в один «спагетти-код». В результате рефакторинг становится болезненным, а написание unit-тестов — практически невозможным.
В этой статье команда EvaLab детально опишет внедрение чистой архитектуры (Clean Architecture) и концепции Feature-Sliced Design (FSD) в экосистему Next.js App Router.
Почему стандартная папочная структура ломается на масштабе
По умолчанию Next.js предлагает файловый роутинг в папке app/. В небольших проектах на 5–10 страниц создатели размещают Server Actions прямо рядом с компонентами или создают хаотичные папки components/, lib/, utils/.
С ростом проекта возникают циклические импорты, сильная связность (tight coupling) между UI и базами данных, а также дублирование бизнес-правил.
Цели Чистой Архитектуры в Next.js:
- Независимость от фреймворка: core-логика бизнеса не должна зависеть от того, используете ли вы Next.js, Express или NestJS.
- Тестируемость: возможность протестировать доменную модель и юзкейсы без запуска серверного окружения Next.js или браузера.
- Заменяемость инфраструктуры: лёгкая смена ORM (например, Prisma на Drizzle) или провайдера базы данных без изменения UI-компонентов.
Слои архитектуры и адаптация Feature-Sliced Design (FSD)
Мы рекомендуем гибридный подход: адаптированный Feature-Sliced Design (FSD) с четким слоистым делением по правилам Дяди Боба (Uncle Bob Clean Architecture).
src/
├── app/ # Next.js App Router (только роутинг, layouts, metadata)
├── processes/ # Сложные сквозные сценарии (авторизация, оформление заказа)
├── pages/ # Страничные композиции (Page Slices)
├── widgets/ # Крупные самостоятельные блоки UI (Header, Sidebar, CatalogGrid)
├── features/ # Пользовательские фичи (AddToCart, FilterProducts, ToggleTheme)
├── entities/ # Бизнес-сущности (User, Order, Product, Cart)
└── shared/ # Инфраструктура, UI-kit, API-клиенты, базовые утилиты
Главные правила импортов:
- Модули из высших слоев могут импортировать только модули из нижних слоев (
app->pages->widgets->features->entities->shared). - Импорт «снизу вверх» или горизонтальный импорт между модулями одного слоя запрещен.
Разделение Domain, Application и Infrastructure слоев
Внутри слоя entities или отдельного модуля бизнес-логики мы выделяем 3 подслоя:
- Domain Layer: интерфейсы, доменные типы, валидаторы чистых сущностей.
- Application Layer (Use Cases): сценарии использования бизнеса (например,
createOrderUseCase). - Infrastructure Layer: реализация работы с базами данных (Prisma, PostgreSQL), внешними API и почтовыми сервисами.
Реализация Pattern Repository с TypeScript и Server Actions
Рассмотрим пример управления заказами.
1. Доменный слой (entities/order/domain/types.ts)
export interface Order {
id: string;
userId: string;
totalAmount: number;
status: 'pending' | 'paid' | 'shipped';
createdAt: Date;
}
export interface IOrderRepository {
findById(id: string): Promise<Order | null>;
create(data: Omit<Order, 'id' | 'createdAt' | 'status'>): Promise<Order>;
}
2. Инфраструктурный слой (entities/order/infrastructure/prisma-order.repository.ts)
import { IOrderRepository, Order } from '../domain/types';
import { db } from '@/shared/lib/db'; // Prisma client instance
export class PrismaOrderRepository implements IOrderRepository {
async findById(id: string): Promise<Order | null> {
const record = await db.order.findUnique({ where: { id } });
if (!record) return null;
return {
id: record.id,
userId: record.userId,
totalAmount: record.totalAmount,
status: record.status as Order['status'],
createdAt: record.createdAt,
};
}
async create(data: Omit<Order, 'id' | 'createdAt' | 'status'>): Promise<Order> {
const created = await db.order.create({
data: {
userId: data.userId,
totalAmount: data.totalAmount,
status: 'pending',
},
});
return {
id: created.id,
userId: created.userId,
totalAmount: created.totalAmount,
status: created.status as Order['status'],
createdAt: created.createdAt,
};
}
}
3. Слой приложения и Server Action (features/create-order/actions/create-order.action.ts)
'use server';
import { PrismaOrderRepository } from '@/entities/order/infrastructure/prisma-order.repository';
import { revalidatePath } from 'next/cache';
import { z } from 'zod';
const createOrderSchema = z.object({
userId: z.string().uuid(),
totalAmount: z.number().positive(),
});
export async function createOrderAction(formData: FormData) {
const repository = new PrismaOrderRepository();
const validated = createOrderSchema.parse({
userId: formData.get('userId'),
totalAmount: Number(formData.get('totalAmount')),
});
// Бизнес-логика создания
const newOrder = await repository.create(validated);
revalidatePath('/dashboard/orders');
return { success: true, orderId: newOrder.id };
}
Тестируемость архитектуры
Благодаря тому, что бизнес-логика опирается на интерфейс IOrderRepository, мы можем написать мгновенные Unit-тесты с использованием MockOrderRepository без поднятия базы данных или моков Next.js:
import { describe, it, expect } from 'vitest';
import { MockOrderRepository } from './mock-order.repository';
describe('Order UseCases', () => {
it('should calculate discount correctly', async () => {
const mockRepo = new MockOrderRepository();
// Тестирование чистой доменной логики
const result = await mockRepo.create({ userId: 'user-1', totalAmount: 1000 });
expect(result.status).toBe('pending');
});
});
Заключение
Чистая архитектура и Feature-Sliced Design в Next.js App Router дают предсказуемость, легкую масштабируемость и высокое качество кода в продуктах любой сложности. Такой подход избавляет команду от хаоса при росте проекта и снижает технический долг до минимума.
Планируете разработку сложного сервиса на Next.js или хотите отрефакторить имеющийся монолит? Закажите проектирование архитектуры в веб-агентстве EvaLab — мы строим отказоустойчивые веб-системы по мировым стандартам!



