feat: завершить полную миграцию V1→V2 с модульной архитектурой и документацией
АРХИТЕКТУРНЫЕ ИЗМЕНЕНИЯ: - Полная миграция на URL структуру /{role}/{domain}/{section}/{view} - Удаление всех старых директорий (/fulfillment-supplies/, /fulfillment-warehouse/, etc.) - Модульная архитектура seller warehouse с URL-based routing - Система rollback через комментарии для безопасных изменений НОВЫЕ КОМПОНЕНТЫ И СТРАНИЦЫ: - Создание всех недостающих страниц для FULFILLMENT, SELLER ролей - Модульный layout для seller warehouse с 3 табами - Извлечение переиспользуемого хука useWBWarehouseData ИСПРАВЛЕНИЯ БЕЗОПАСНОСТИ: - Добавление 'use client' директив во все WHOLESALE и LOGISTICS страницы - Исправление отсутствующих security guards (useRoleGuard + AuthGuard) - Обновление navigation конфигураций для всех ролей ДОКУМЕНТАЦИЯ: - Создание MIGRATION_GUIDE_V1_TO_V2.md: 8-этапное руководство по миграции - Создание NEXTJS_BEST_PRACTICES.md: паттерны для Next.js 13+ в SFERA - Обновление URL_ROUTING_RULES.md с seller warehouse и rollback системой - Обновление SIDEBAR_ARCHITECTURE_IMPLEMENTATION.md с новыми метриками - Обновление INDEX.md с новыми документами Development раздела ИСПРАВЛЕНИЯ ESLINT: - Удаление неиспользуемых импортов и переменных - Исправление import/order ошибок в модульных компонентах - Исправление react/no-unescaped-entities - Перенос длинных строк для соответствия max-len 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
381
docs/development/NEXTJS_BEST_PRACTICES.md
Normal file
381
docs/development/NEXTJS_BEST_PRACTICES.md
Normal file
@ -0,0 +1,381 @@
|
||||
# ⚛️ NEXT.JS 13+ BEST PRACTICES ДЛЯ SFERA
|
||||
|
||||
> **Статус**: ✅ **ДЕЙСТВУЮЩИЙ**
|
||||
> **Применимо к**: Next.js 15 + TypeScript
|
||||
> **Дата создания**: 30.08.2025
|
||||
|
||||
---
|
||||
|
||||
## 🎯 КЛЮЧЕВЫЕ КОНЦЕПЦИИ
|
||||
|
||||
### 1. 'use client' ДИРЕКТИВА
|
||||
|
||||
#### ОБЯЗАТЕЛЬНО ИСПОЛЬЗОВАТЬ КОГДА:
|
||||
|
||||
- Используются React hooks (`useState`, `useEffect`, `useRouter`)
|
||||
- Используются custom hooks (`useRoleGuard`, `useAuth`)
|
||||
- Используются event handlers (`onClick`, `onSubmit`)
|
||||
- Используются browser APIs (`localStorage`, `window`)
|
||||
|
||||
#### ПАТТЕРН ДЛЯ SFERA СТРАНИЦ:
|
||||
|
||||
```typescript
|
||||
'use client'
|
||||
|
||||
import { AuthGuard } from '@/components/auth-guard'
|
||||
import { ComponentDashboard } from '@/components/path/component-dashboard'
|
||||
import { useRoleGuard } from '@/hooks/useRoleGuard'
|
||||
|
||||
export default function RolePage() {
|
||||
useRoleGuard('ROLE_NAME')
|
||||
|
||||
return (
|
||||
<AuthGuard>
|
||||
<ComponentDashboard />
|
||||
</AuthGuard>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. APP ROUTER СТРУКТУРА
|
||||
|
||||
#### ФАЙЛОВАЯ СИСТЕМА:
|
||||
|
||||
```
|
||||
src/app/
|
||||
├── {role}/ # Динамический сегмент роли
|
||||
│ ├── page.tsx # Главная роли (редирект)
|
||||
│ ├── layout.tsx # Layout для роли (опционально)
|
||||
│ └── {domain}/
|
||||
│ ├── page.tsx # Главная домена
|
||||
│ ├── layout.tsx # Layout с табами
|
||||
│ └── {section}/
|
||||
│ ├── page.tsx # Основная страница секции
|
||||
│ └── {view}/
|
||||
│ └── page.tsx # Конкретное представление
|
||||
```
|
||||
|
||||
#### ПРАВИЛА ИМЕНОВАНИЯ:
|
||||
|
||||
- **Роли**: `seller`, `fulfillment`, `wholesale`, `logistics`
|
||||
- **Домены**: `supplies`, `warehouse`, `orders`, `statistics`
|
||||
- **Секции**: `goods`, `consumables`, `marketplace`
|
||||
- **Представления**: `cards`, `suppliers`, `new`, `receiving`
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ SECURITY PATTERNS
|
||||
|
||||
### ЗАЩИТА СТРАНИЦ:
|
||||
|
||||
#### Базовый паттерн:
|
||||
|
||||
```typescript
|
||||
'use client'
|
||||
|
||||
import { AuthGuard } from '@/components/auth-guard'
|
||||
import { useRoleGuard } from '@/hooks/useRoleGuard'
|
||||
|
||||
export default function SecurePage() {
|
||||
useRoleGuard('REQUIRED_ROLE') // Проверка роли
|
||||
|
||||
return (
|
||||
<AuthGuard> {/* Проверка авторизации */}
|
||||
<PageContent />
|
||||
</AuthGuard>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### Доступные роли:
|
||||
|
||||
- `'SELLER'` - селлеры
|
||||
- `'FULFILLMENT'` - фулфилмент
|
||||
- `'WHOLESALE'` - поставщики
|
||||
- `'LOGIST'` - логистика
|
||||
|
||||
### MIDDLEWARE ЗАЩИТА:
|
||||
|
||||
```typescript
|
||||
// middleware.ts - автоматическая проверка URL соответствия роли
|
||||
export function middleware(request: NextRequest) {
|
||||
const url = request.nextUrl.pathname
|
||||
const userRole = getUserRole(request) // из JWT токена
|
||||
|
||||
// Проверяем соответствие роли и URL
|
||||
if (url.startsWith('/seller/') && userRole !== 'SELLER') {
|
||||
return NextResponse.redirect('/unauthorized')
|
||||
}
|
||||
// аналогично для других ролей
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 LAYOUT PATTERNS
|
||||
|
||||
### LAYOUT С ТАБАМИ:
|
||||
|
||||
#### Структура файлов:
|
||||
|
||||
```
|
||||
warehouse/
|
||||
├── layout.tsx # Tabs UI + активный таб по URL
|
||||
├── fulfillment/page.tsx # /warehouse/fulfillment
|
||||
├── wildberries/page.tsx # /warehouse/wildberries
|
||||
└── storage/page.tsx # /warehouse/storage
|
||||
```
|
||||
|
||||
#### Реализация layout.tsx:
|
||||
|
||||
```typescript
|
||||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function WarehouseLayout({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname()
|
||||
|
||||
// Автоматическое определение активного таба по URL
|
||||
const getActiveTab = () => {
|
||||
if (pathname.includes('/fulfillment')) return 'fulfillment'
|
||||
if (pathname.includes('/wildberries')) return 'wildberries'
|
||||
if (pathname.includes('/storage')) return 'storage'
|
||||
return 'fulfillment' // default
|
||||
}
|
||||
|
||||
const activeTab = getActiveTab()
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Tabs Header */}
|
||||
<div className="flex space-x-1 bg-gray-900 p-1 rounded-lg">
|
||||
<Link
|
||||
href="/seller/warehouse/fulfillment"
|
||||
className={`flex-1 py-2 px-4 text-center rounded-md transition-all whitespace-nowrap ${
|
||||
activeTab === 'fulfillment'
|
||||
? 'bg-blue-600 text-white shadow-lg'
|
||||
: 'text-white/60 hover:bg-white/10'
|
||||
}`}
|
||||
>
|
||||
Склад фулфилмент
|
||||
</Link>
|
||||
{/* другие табы */}
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### ПЕРЕИСПОЛЬЗУЕМЫЕ ХУКИ:
|
||||
|
||||
#### Паттерн извлечения данных:
|
||||
|
||||
```typescript
|
||||
// hooks/useWBWarehouseData.ts
|
||||
export function useWBWarehouseData() {
|
||||
const { data, loading, error } = useQuery(GET_WB_WAREHOUSE_DATA, {
|
||||
fetchPolicy: 'cache-first',
|
||||
errorPolicy: 'ignore',
|
||||
})
|
||||
|
||||
return {
|
||||
data: data?.getWBWarehouseData || [],
|
||||
loading,
|
||||
error,
|
||||
refetch: () => {
|
||||
/* логика обновления */
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Использование в компонентах:
|
||||
const { data, loading } = useWBWarehouseData()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 ROUTING PATTERNS
|
||||
|
||||
### ПРОГРАММНАЯ НАВИГАЦИЯ:
|
||||
|
||||
```typescript
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
function NavigationComponent() {
|
||||
const router = useRouter()
|
||||
|
||||
// ✅ Правильно: используем новые пути
|
||||
const handleNavigate = () => {
|
||||
router.push('/seller/warehouse/fulfillment')
|
||||
}
|
||||
|
||||
// ❌ Неправильно: старые пути
|
||||
// router.push('/wb-warehouse')
|
||||
}
|
||||
```
|
||||
|
||||
### ОПРЕДЕЛЕНИЕ АКТИВНЫХ СОСТОЯНИЙ:
|
||||
|
||||
```typescript
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
function ActiveStateComponent() {
|
||||
const pathname = usePathname()
|
||||
|
||||
// ✅ Правильно: проверяем новые пути
|
||||
const isActive = pathname.startsWith('/seller/warehouse')
|
||||
|
||||
// ✅ Конкретная секция:
|
||||
const isFulfillmentActive = pathname.includes('/warehouse/fulfillment')
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 COMPONENT PATTERNS
|
||||
|
||||
### МОДУЛЬНАЯ АРХИТЕКТУРА:
|
||||
|
||||
#### Структура папки компонента:
|
||||
|
||||
```
|
||||
ComponentName/
|
||||
├── index.tsx # Основной компонент
|
||||
├── ComponentName.types.ts # TypeScript типы
|
||||
├── ComponentName.hooks.ts # Custom hooks
|
||||
├── ComponentName.utils.ts # Утилиты
|
||||
└── components/ # Подкомпоненты
|
||||
├── Header.tsx
|
||||
├── Content.tsx
|
||||
└── Footer.tsx
|
||||
```
|
||||
|
||||
#### Основной компонент:
|
||||
|
||||
```typescript
|
||||
'use client'
|
||||
|
||||
import { ComponentNameProvider } from './ComponentName.context'
|
||||
import { useComponentName } from './ComponentName.hooks'
|
||||
import { Header } from './components/Header'
|
||||
import { Content } from './components/Content'
|
||||
|
||||
export function ComponentName() {
|
||||
return (
|
||||
<ComponentNameProvider>
|
||||
<div className="space-y-6">
|
||||
<Header />
|
||||
<Content />
|
||||
</div>
|
||||
</ComponentNameProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 ОШИБКИ И РЕШЕНИЯ
|
||||
|
||||
### ПРОБЛЕМА 1: "useRoleGuard from server"
|
||||
|
||||
**Ошибка:**
|
||||
|
||||
```
|
||||
Error: Attempted to call useRoleGuard() from the server but useRoleGuard is on the client
|
||||
```
|
||||
|
||||
**Причина**: Отсутствует 'use client' в page.tsx
|
||||
|
||||
**Решение:**
|
||||
|
||||
```typescript
|
||||
'use client' // Добавить в начало файла
|
||||
|
||||
import { useRoleGuard } from '@/hooks/useRoleGuard'
|
||||
```
|
||||
|
||||
### ПРОБЛЕМА 2: Redirect loops
|
||||
|
||||
**Ошибка**: Бесконечные редиректы между путями
|
||||
|
||||
**Причина**: Одновременно существуют старые и новые пути
|
||||
|
||||
**Решение**: Удалить старые директории:
|
||||
|
||||
```bash
|
||||
rm -rf src/app/fulfillment-supplies/
|
||||
rm -rf src/app/fulfillment-warehouse/
|
||||
```
|
||||
|
||||
### ПРОБЛЕМА 3: Потеря активных состояний
|
||||
|
||||
**Ошибка**: Табы не показывают активное состояние
|
||||
|
||||
**Причина**: Проверка pathname на старые пути
|
||||
|
||||
**Решение**: Обновить логику проверки:
|
||||
|
||||
```typescript
|
||||
// ✅ До:
|
||||
const isActive = pathname.startsWith('/fulfillment-supplies')
|
||||
|
||||
// ✅ После:
|
||||
const isActive = pathname.startsWith('/fulfillment/supplies')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 CHECKLIST ДЛЯ НОВЫХ КОМПОНЕНТОВ
|
||||
|
||||
### ПЕРЕД СОЗДАНИЕМ:
|
||||
|
||||
- [ ] Прочитал MODULAR_ARCHITECTURE_PATTERN.md
|
||||
- [ ] Определил нужность модульной архитектуры
|
||||
- [ ] Выбрал правильный URL путь по формуле `/{role}/{domain}/{section}/{view}`
|
||||
- [ ] Добавил 'use client' если используются hooks
|
||||
|
||||
### ПРИ СОЗДАНИИ:
|
||||
|
||||
- [ ] Добавил useRoleGuard с правильной ролью
|
||||
- [ ] Обернул в AuthGuard для проверки авторизации
|
||||
- [ ] Использовал существующие компоненты и хуки
|
||||
- [ ] Следовал naming conventions
|
||||
|
||||
### ПОСЛЕ СОЗДАНИЯ:
|
||||
|
||||
- [ ] Проверил `npm run typecheck`
|
||||
- [ ] Проверил `npm run lint`
|
||||
- [ ] Протестировал в браузере
|
||||
- [ ] Убедился что navigation работает
|
||||
|
||||
---
|
||||
|
||||
## 🎯 ЗАКЛЮЧЕНИЕ
|
||||
|
||||
**NEXT.JS 13+ В SFERA: СТАБИЛЬНАЯ PRODUCTION-READY СИСТЕМА**
|
||||
|
||||
🎯 **ПРИНЦИПЫ:**
|
||||
|
||||
- 'use client' для всех interactive компонентов
|
||||
- URL-based routing вместо внутренних состояний
|
||||
- Модульная архитектура для сложных компонентов
|
||||
- Систематическая структура путей по ролям
|
||||
|
||||
🚀 **РЕЗУЛЬТАТ:**
|
||||
|
||||
- 100% совместимость с Next.js 15
|
||||
- Отсутствие server/client конфликтов
|
||||
- SEO-оптимизированные URL
|
||||
- Простая навигация и maintenance
|
||||
|
||||
**Данные практики обеспечивают стабильность и масштабируемость SFERA на Next.js 13+.**
|
||||
|
||||
---
|
||||
|
||||
_Создано: 30.08.2025_
|
||||
_На основе реального опыта разработки SFERA_
|
Reference in New Issue
Block a user