image.pngl Architecture для Next.js 13+ (App Router)
Этот репозиторий — эталон реализации «Crystal Architecture» для Next.js 13+ с App Router. Архитектура делает проект модульным, предсказуемым и масштабируемым: каждая функциональность изолирована в собственном модуле внутри src/modules/, общие части — в src/shared/.
- Быстрый старт
- Структура проекта
- Как создать новый модуль
- Импорты и код-стайл
- Тестирование (Vitest, RTL, Playwright)
- Работа с API (orval, axios, react-query)
- Локализация
- UI: shadcn/ui
- Баррели (barrelsby)
- Storybook
- Пример использования модуля
- Требования к качеству
- Полезные ссылки
- Модульность: всё, что относится к фиче, живёт в её модуле.
- Изоляция слоёв: UI-компоненты (тупые), виджеты (умные), схемы/типы, хуки, стейт, утилиты — раздельно.
- Переиспользуемость: общие блоки — в
src/shared/. - Тестируемость: тесты рядом с кодом, e2e — в
tests/e2e. - Соблюдение правил качества: ESLint + Prettier, импорт-ордера, JSDoc для публичных API.
- Установить зависимости и запустить дев-сервер
pnpm install
pnpm dev
# Откройте http://localhost:3000Полезные скрипты:
# Линтер
pnpm lint
# Тесты (Vitest)
pnpm test # интерактивно
pnpm test:run # один прогон
pnpm test:coverage # покрытие
# E2E (Playwright)
pnpm test:e2e
pnpm test:e2e:ui
# Storybook
pnpm storybook
pnpm build-storybook
# Генерация API-клиента (orval)
pnpm orvalsrc/
├── app/ # Next.js App Router
├── modules/ # Модули (фичи)
│ └── {moduleName}/
│ ├── ui/
│ │ ├── components/ # Тупые компоненты (только пропсы)
│ │ └── widgets/ # Умные компоненты (связь с логикой)
│ ├── schemas/ # Zod-схемы, DTO, типы
│ ├── utils/ # Утилиты модуля
│ ├── hooks/ # Кастомные хуки модуля
│ ├── constants/ # Константы, словари, enum’ы
│ ├── model/ # Состояние (zustand/jotai/redux)
│ └── index.ts # Публичный API модуля
├── shared/ # Общие части
│ ├── ui/ # Шердовые UI-компоненты
│ ├── utils/ # Утилиты
│ ├── hooks/ # Общие хуки
│ ├── constants/ # Глобальные константы
│ ├── config/ # Глобальные конфиги (env, api)
│ └── lib/ # Адаптеры библиотек (axios, react-query и т.п.)
└── styles/ # Глобальные стили
tests/
└── e2e/ # End-to-End тесты (Playwright)
Посмотреть полноценный пример можно в модуле src/modules/users/.
- Создайте структуру каталога:
src/modules/profile/
├── ui/
│ ├── components/
│ └── widgets/
├── schemas/
├── utils/
├── hooks/
├── constants/
├── model/
└── index.ts
- Экспортируйте публичный API в
index.tsмодуля:
// src/modules/profile/index.ts
export * from "./ui/components/ProfileCard/ProfileCard";
export * from "./ui/widgets/ProfileWidget/ProfileWidget";
export * from "./hooks/useProfile";
export * from "./schemas/profile.schema";- Следуйте правилам слоёв:
- components/: только представление, принимают пропсы, могут иметь локальный стейт/эффекты, но без бизнес-логики.
- widgets/: связывают UI с логикой: обращения к API, использование хранилищ/хуков.
- hooks/: переиспользуемая логика, асинхронные операции, работа с API и кешем.
- utils/: чистые функции без побочных эффектов.
- schemas/: Zod-схемы валидации, DTO, типы, экспортируемые наружу.
- model/: состояние модуля (zustand/jotai/redux), не экспортируйте детали реализации напрямую.
- Тесты кладите рядом с кодом в
__tests__/:
src/modules/profile/ui/components/ProfileCard/__tests__/ProfileCard.test.tsx
src/modules/profile/hooks/__tests__/useProfile.test.ts
src/modules/profile/utils/__tests__/formatName.test.ts
Названия describe/it/test — только на русском.
Порядок импортов (ESLint):
- Внешние библиотеки (React, Next.js и т.п.)
- Внутренние алиасы (
@/shared,@/modules) - Относительные импорты (
./,../) - Типы — отдельными импортами
Пример:
import React from "react";
import { NextRequest } from "next/server";
import { z } from "zod";
import { Button } from "@/shared/ui";
import { useUser } from "@/modules/users/hooks";
import { UserCard } from "./UserCard";
import type { UserCardProps } from "./types";/**
* Короткое описание функции
*
* @param paramName - Описание параметра
* @returns Описание результата
* @throws {ErrorType} Когда возникает ошибка
* @example
* const result = fn(param);
*/- Unit/Component/Integration-тесты — рядом с кодом в
__tests__/, имя файла*.test.ts(x). - E2E — в
tests/e2e(Playwright). - Инструменты: Vitest, React Testing Library, Playwright.
- Все названия в тестах — на русском.
Скрипты:
pnpm test # интерактивные тесты (Vitest)
pnpm test:run # один прогон
pnpm test:coverage # покрытие
pnpm test:e2e # e2e-тесты- Сначала пишем тесты (ожидаемое поведение).
- Реализуем код до «зелёного» состояния.
- Запускаем всё (включая линтер), чиним замечания.
- Повторяем при добавлении новой функциональности.
- Генерация типов и клиента:
pnpm orval— конфигурация вorval.config.js. - HTTP-клиент и инстансы — в
src/shared/lib/client/. - Рекомендуем выносить запросы в хуки модуля (например,
useUser).
Пример использования (из модуля users):
import { UserCard, useUser } from "@/modules/users";
const { updateUser, isUpdating } = useUser(userId);Проект использует next-intl. Настройки маршрутизации/локалей лежат в src/shared/configs/i18/. Добавляйте новые ключи в соответствующие файлы messages/*.json.
Запуск: pnpm storybook. Компоненты в ui/components должны иметь сториз. Это помогает документировать API и упростить визуальное тестирование.
// Импорт из публичного API модуля
import { UserCard, useUser, User } from "@/modules/users";
// Компонент
<UserCard user={user} onEdit={handleEdit} onDelete={handleDelete} />;
// Хук
const { updateUser, isUpdating } = useUser(userId);- ESLint + Prettier обязательны, не допускаются неиспользуемые импорты/переменные.
- Типизация без
any, используйте конкретные типы/встроенные типы из Zod. - Асинхронные операции с обработкой ошибок.
- Доступность (A11y) для UI-компонентов: семантические теги, aria-атрибуты.
- Next.js —
https://nextjs.org/docs - Vitest —
https://vitest.dev/ - React Testing Library —
https://testing-library.com/docs/react-testing-library/intro/ - Playwright —
https://playwright.dev/ - Zod —
https://zod.dev/ - Orval —
https://orval.dev/
Проект использует библиотеку компонентов shadcn/ui на базе Tailwind CSS v4.
-
Инициализация (уже выполнено):
- Конфигурация хранится в
components.json. - Tailwind подключён в
src/styles/globals.css. - Алиасы настроены на
src/shared.
- Конфигурация хранится в
-
Пути и алиасы (components.json):
components:@/shared/componentsui:@/shared/components/uiutils:@/shared/lib/utils
-
Где лежат компоненты:
src/shared/components/ui/* -
Добавление компонентов:
- Командой:
pnpx shadcn@latest add {component} - Примеры:
pnpx shadcn@latest add input label card pnpx shadcn@latest add dialog dropdown-menu select
- Командой:
-
Уже добавлено:
button,accordionи набор базовых компонентов (form, input/label, card, dialog, dropdown-menu, select, табы и др.). Если потребуется — добавляйте точечно любой недостающий компонент той же командой. -
Импорт в коде:
import { Button } from "@/shared/components/ui/button"; import { Accordion, AccordionItem } from "@/shared/components/ui/accordion";
-
Замечание по toast: компонент
toastпомечен как устаревший у shadcn/ui. Рекомендуется использоватьsonner. -
ESLint для shared: правило сортировки импортов
simple-import-sort/importsотключено дляsrc/shared/**/*.{ts,tsx}. -
Документация:
https://ui.shadcn.com/
Для автоматической генерации файла с реэкспортами компонентов (index.tsx) используем barrelsby.
- Где генерируем:
src/shared/components/ui/index.tsx - Команда:
pnpx barrelsby \ --directory src/shared/components/ui \ --delete \ --barrelName index.tsx \ --structure flat \ --exclude index
Пояснения:
- --delete: удаляет старые баррели перед генерацией
- --barrelName index.tsx: имя файла-барреля
- --structure flat: плоская структура реэкспортов из текущей директории
- --exclude index: не реэкспортировать сам файл
index.*
После генерации можно импортировать компоненты так:
import { Button, Card } from "@/shared/components/ui";-
Сгенерировать точь‑в‑точь как текущий
@index.ts(только именованные экспорты, без default):pnpx barrelsby \ --directory src/shared/components/ui \ --delete \ --barrelName index.ts \ --structure flat \ --exclude index \ --no-default-exports
-
Сгенерировать
index.tsxс default‑экспортами ПЛЮС именованными:pnpx barrelsby \ --directory src/shared/components/ui \ --delete \ --barrelName index.tsx \ --structure flat \ --exclude index \ --exportDefault
-
Иерархическая структура по подпапкам (filesystem):
pnpx barrelsby \ --directory src/shared/components \ --delete \ --barrelName index.ts \ --structure filesystem \ --exclude index
-
Для другого каталога (например, компоненты модуля users):
pnpx barrelsby \ --directory src/modules/users/ui/components \ --delete \ --barrelName index.ts \ --structure flat \ --exclude index
-
Исключить сториз и тесты:
pnpx barrelsby \ --directory src/shared/components/ui \ --delete \ --barrelName index.ts \ --structure flat \ --exclude "**/*.stories.*" "**/__tests__/**" index
-
Удобный npm‑скрипт: Добавьте в
package.json:{ "scripts": { "barrels:ui": "barrelsby --directory src/shared/components/ui --delete --barrelName index.ts --structure flat --exclude index --no-default-exports" } }И запускайте:
pnpm barrels:ui