Files
sfera-new/docs/development/NEXTJS_BEST_PRACTICES.md
Veronika Smirnova 3f0cc933fc 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>
2025-08-30 22:37:15 +03:00

10 KiB
Raw Blame History

⚛️ 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 СТРАНИЦ:

'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

ЗАЩИТА СТРАНИЦ:

Базовый паттерн:

'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 ЗАЩИТА:

// 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:

'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>
  )
}

ПЕРЕИСПОЛЬЗУЕМЫЕ ХУКИ:

Паттерн извлечения данных:

// 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

ПРОГРАММНАЯ НАВИГАЦИЯ:

import { useRouter } from 'next/navigation'

function NavigationComponent() {
  const router = useRouter()

  // ✅ Правильно: используем новые пути
  const handleNavigate = () => {
    router.push('/seller/warehouse/fulfillment')
  }

  // ❌ Неправильно: старые пути
  // router.push('/wb-warehouse')
}

ОПРЕДЕЛЕНИЕ АКТИВНЫХ СОСТОЯНИЙ:

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

Основной компонент:

'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

Решение:

'use client' // Добавить в начало файла

import { useRoleGuard } from '@/hooks/useRoleGuard'

ПРОБЛЕМА 2: Redirect loops

Ошибка: Бесконечные редиректы между путями

Причина: Одновременно существуют старые и новые пути

Решение: Удалить старые директории:

rm -rf src/app/fulfillment-supplies/
rm -rf src/app/fulfillment-warehouse/

ПРОБЛЕМА 3: Потеря активных состояний

Ошибка: Табы не показывают активное состояние

Причина: Проверка pathname на старые пути

Решение: Обновить логику проверки:

// ✅ До:
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