Directual + NextJS

AI-Ready Documentation: Универсальный шаблон для создания полноценного Next.js приложения с интеграцией Directual (авторизация, WebSocket, API endpoints)

📋 Обзор

Этот шаблон описывает архитектуру современного веб-приложения на Next.js 16+ с App Router, интегрированного с бэкендом Directual. Включает безопасную авторизацию через HTTP-only cookies, WebSocket для real-time обновлений, глобальное управление состоянием и адаптивную тему.

Стек технологий:

  • Next.js 16+ (App Router)

  • TypeScript

  • Tailwind CSS

  • shadcn/ui (компоненты)

  • Directual (бэкенд: auth, API, WebSocket)

  • socket.io-client (WebSocket)


🏗️ Архитектурные принципы

1. Безопасность сессии

  • HTTP-only cookie для хранения sessionID (защита от XSS атак)

  • SessionID НЕ хранится в localStorage или клиентском state

  • API Routes в Next.js служат прокси для авторизации

  • Периодическая проверка сессии через /api/auth/check

2. Разделение клиентов Directual

  • Клиентский: через Next.js rewrites (apiHost: '/') для обхода CORS

  • Серверный: прямое подключение к api.directual.com для API Routes

3. Глобальное управление состоянием

Иерархия провайдеров (от внешнего к внутреннему):

4. WebSocket интеграция

  • Singleton pattern для socket.io-client

  • Подключение после получения sessionID из HTTP-only cookie

  • Глобальный слушатель событий для real-time обновлений

  • Важно: только прием событий, отправка не поддерживается Directual


📁 Структура проекта


🔐 Безопасная авторизация

Концепция

SessionID хранится в HTTP-only cookie, недоступной для JavaScript на клиенте. Все операции с сессией проходят через API Routes.

API Routes

1. /api/auth/login (POST)

  • Принимает: { email, password }

  • Вызывает: serverApi.auth.login()

  • Возвращает: { success, user }

  • Устанавливает cookie: 1cable_session (httpOnly, secure, sameSite: strict, maxAge: 7 дней)

2. /api/auth/register (POST)

  • Принимает: { email, password, username }

  • Вызывает: serverApi.auth.register()

  • Возвращает: { success, user }

  • Устанавливает cookie: аналогично login

3. /api/auth/logout (POST)

  • Читает cookie 1cable_session

  • Вызывает: serverApi.auth.logout(sessionID)

  • Удаляет cookie (maxAge: 0)

  • Возвращает: { success }

4. /api/auth/check (GET)

  • Читает cookie 1cable_session

  • Вызывает: serverApi.auth.check(sessionID)

  • Возвращает: { success, user } или 401

  • При ошибке удаляет cookie

5. /api/auth/session (GET)

  • Читает cookie 1cable_session

  • Возвращает: { success, sessionID }

  • Используется WebSocket для авторизации

AuthProvider (context/auth-provider.tsx)

State:

  • user: User | null - текущий пользователь

  • loading: boolean - загрузка

  • error: string | null - ошибка

Методы:

  • login(email, password) - вход через /api/auth/login

  • register(email, password, username) - регистрация через /api/auth/register

  • logout() - выход через /api/auth/logout

  • isAuthorized() - проверка авторизации

  • hasRole(role) - проверка роли (RBAC)

Lifecycle:

  • При монтировании вызывает /api/auth/check для проверки сессии


🔌 Directual клиенты

lib/directual/client.ts (клиентский)

lib/directual/server-client.ts (серверный)

next.config.mjs (rewrites)

Почему два клиента?

  • Клиентский использует rewrites для обхода CORS

  • Серверный используется в API Routes, где CORS не проблема


📡 Универсальный Fetcher

lib/directual/fetcher.ts

Класс Fetcher - обертка над directual-api с дополнительными фичами:

Ключевые возможности:

  1. Кеширование sessionID - избегает множественных вызовов /api/auth/session

  2. Обработка ошибок - унифицированная обработка ошибок API

  3. Загрузка файлов - с отслеживанием прогресса

  4. Универсальные методы - get(), post(), специфичные методы для структур

Методы:

Обработка ошибок:

Важно: 403 ошибки могут означать не только истечение сессии, но и ограничения по ролям (RBAC). Не рекомендуется автоматически разлогинивать пользователя при 403 - лучше показывать сообщение о недостаточных правах.


🌐 WebSocket интеграция

lib/directual/socket.ts

Singleton pattern для socket.io-client:

Авторизация WebSocket:

Важно: Directual WebSocket работает только на прием событий. Вы можете подписываться на события и получать их, но отправка событий в Directual НЕ поддерживается. Все изменения данных делайте через HTTP API (Fetcher).

hooks/use-socket.ts

Хуки для работы с WebSocket в React:

Примечание: Хук useSocketEmit НЕ нужен, так как Directual WebSocket не принимает события от клиента. Все изменения данных делайте через HTTP API.

components/socket-listener.tsx

Глобальный слушатель для всего приложения:

  • Подключается к WebSocket при монтировании

  • Логирует все события в консоль

  • Обрабатывает специальные события (например, alert)

  • Рендерит null (невидимый компонент)

Пример обработки события alert:


📦 Глобальное управление данными

context/data-provider.tsx

DataProvider - глобальный кеш данных приложения с автоматическим обновлением.

Концепция: Храним в контексте те данные, которые нужны во многих местах приложения (профиль пользователя, настройки, списки сущностей и т.д.). Это избавляет от повторных запросов и обеспечивает консистентность данных.

Структура State:

  • userProfile: UserProfile | null - профиль текущего пользователя

  • settings: Record<string, unknown> | null - настройки приложения

  • [yourEntityList]: YourEntity[] - списки ваших сущностей (адаптируйте под проект)

  • loading: boolean - первоначальная загрузка

  • refreshing: boolean - фоновое обновление данных

  • error: string | null - ошибка загрузки

Методы:

  • refreshProfile(silent?) - обновить профиль пользователя

  • refreshSettings(silent?) - обновить настройки

  • refreshYourEntity(silent?) - обновить вашу сущность (адаптируйте)

  • refreshAll(silent?) - обновить все данные

  • updateProfile(profileData) - обновить профиль

Автоматическое обновление:

  1. При монтировании (если isAuthorized())

  2. При WebSocket событиях: refresh, entity_created, entity_updated, entity_deleted

  3. При возврате на вкладку браузера (visibilitychange)

Silent refresh:

  • silent=true - обновление без loading индикатора

  • Используется для фоновых обновлений (WebSocket, visibilitychange)

Пример использования:

Адаптация под проект: Замените примеры методов на ваши сущности. Принцип остается тот же - централизованное хранилище данных с автоматической синхронизацией через WebSocket.


📤 Загрузка файлов

Настройка структуры в Directual

Обязательный шаг: Создайте структуру данных file_links в Directual:

  1. Создайте структуру с именем file_links

  2. Добавьте поле link с типом FileUpload

  3. Настройте права доступа:

    • Write: разрешите запись для авторизованных пользователей

    • Read: настройте по необходимости

  4. Создайте эндпоинт uploadFiles (POST) для загрузки файлов

Структура поля:

Метод uploadFile в Fetcher

Fetcher включает специальный метод для загрузки файлов на Directual с отслеживанием прогресса.

Сигнатура:

Механизм работы:

  1. FormData - файл оборачивается в FormData для multipart/form-data

  2. Прокси через rewrites - используется /good/api/v5/data/file_links/uploadFiles

  3. SessionID из cookie - автоматически добавляется в query параметры

  4. XMLHttpRequest - для отслеживания прогресса (если передан onProgress)

  5. Fallback на fetch - если onProgress не нужен

Структура запроса:

Пример использования:

Компонент с прогресс-баром:

Важные детали:

  1. credentials: 'include' - обязательно для отправки cookie с sessionID

  2. XMLHttpRequest.upload.progress - используется для отслеживания прогресса

  3. Обработка 403 - если сессия невалидна, вернется ошибка

  4. Возвращаемый объект - { urlLink: string } - прямая ссылка на файл в Directual

Структура ответа от Directual:

Рекомендации:

  • ⚠️ Обязательно создайте структуру file_links с полем link (FileUpload) в Directual

  • Проверяйте размер файла на клиенте перед загрузкой

  • Показывайте прогресс для файлов > 1MB

  • Добавьте валидацию типов файлов (MIME types)

  • Обрабатывайте ошибки сети (abort, timeout)

  • Настройте права доступа на запись для структуры file_links


🎯 Паттерн работы с Directual

Рекомендуемый подход: Single Action Endpoint

Концепция: Все действия пользователя с фронтенда отправляются в один универсальный эндпоинт (например, user_actions). Directual сценарий анализирует тип действия и возвращает результат либо синхронно, либо через WebSocket.

Преимущества:

  • ✅ Единая точка входа для всех действий

  • ✅ Легко логировать и анализировать действия пользователей

  • ✅ Гибкость в обработке (синхронный или асинхронный ответ)

  • ✅ Простота добавления новых действий без изменения API

Структура запроса

Пример использования

Обработка в Directual

Сценарий в эндпоинте postUserAction:

  1. Читаем action из запроса

  2. Switch/case по типу действия

  3. Выполняем бизнес-логику

  4. Возвращаем результат:

    • Синхронно: return result (быстрые операции)

    • Асинхронно: отправляем WebSocket событие (долгие операции)

Пример структуры:

WebSocket события для ответов

Для асинхронных операций отправляйте WebSocket события:

  • entity_created → DataProvider обновляет список

  • entity_updated → DataProvider обновляет список

  • entity_deleted → DataProvider обновляет список

  • alert → GlobalAlerts показывает уведомление

  • progress → Индикатор прогресса (для долгих операций)

Когда использовать синхронный vs асинхронный ответ

Синхронный (return result):

  • Быстрые операции (< 1 сек)

  • Когда нужен немедленный ответ для UI

  • Операции чтения (GET)

  • Обновление профиля, настроек

Асинхронный (WebSocket):

  • Долгие операции (> 1 сек)

  • Когда результат нужен не сразу

  • Операции, влияющие на других пользователей

  • Создание/удаление сущностей

  • Фоновые задачи


🎨 Система темизации

context/theme-provider.tsx

ThemeProvider - управление темой приложения.

State:

  • theme: 'light' | 'dark' | 'system' - выбранная тема

  • actualTheme: 'light' | 'dark' - реальная применяемая тема

  • mounted: boolean - флаг монтирования

Методы:

  • setTheme(theme) - установить тему

  • toggleTheme() - переключить light ↔ dark

  • isDark, isLight, isSystem - утилиты проверки

Механизм работы:

  1. Тема хранится в localStorage.theme

  2. Применяется через класс light или dark на <html>

  3. При system слушает prefers-color-scheme media query

  4. При изменении системной темы автоматически переключается

CSS переменные (app/globals.css):

tailwind.config.js


🔒 Security Headers

next.config.mjs

Обязательные security headers:

Важно:

  • unsafe-eval может потребоваться для CodeMirror или других редакторов

  • connect-src должен включать Directual API и WebSocket

  • frame-ancestors 'none' запрещает iframe embedding


📝 Типизация TypeScript

types/index.ts

Базовые типы:

types/directual.d.ts

Расширение типов для directual-api:


🚀 Root Layout

app/layout.tsx

Структура провайдеров:

Важно:

  • suppressHydrationWarning - для темы (класс меняется до hydration)

  • Порядок провайдеров: Theme → Auth → Data

  • SocketListener и GlobalAlerts монтируются глобально


🎯 Глобальные алерты

components/global-alerts.tsx

Механизм:

  1. WebSocket отправляет событие alert с данными

  2. SocketListener перехватывает и вызывает window.__showGlobalAlert()

  3. GlobalAlerts отображает toast-уведомление

Структура алерта:

Фичи:

  • Автоматическое скрытие через 5 секунд

  • Дедупликация (один и тот же алерт не показывается дважды)

  • Ручное закрытие (кнопка X)

  • Поддержка HTML в description

  • Динамические иконки из lucide-react


⚙️ Environment Variables

.env.local

Важно:

  • Префикс NEXT_PUBLIC_ делает переменную доступной на клиенте

  • Без этой переменной приложение не запустится (проверка в lib/directual/client.ts)


📦 Dependencies

package.json (основные зависимости)

scripts


🔄 Жизненный цикл приложения

1. Инициализация (app load)

  1. Root Layout монтируется

  2. ThemeProvider загружает тему из localStorage и применяет

  3. AuthProvider проверяет сессию через /api/auth/check

  4. DataProvider загружает данные (если авторизован)

  5. SocketListener подключается к WebSocket (если авторизован)

2. Авторизация (login flow)

  1. Пользователь вводит credentials

  2. authContext.login() отправляет POST /api/auth/login

  3. API route вызывает serverApi.auth.login()

  4. При успехе устанавливается HTTP-only cookie

  5. AuthProvider обновляет user state

  6. DataProvider автоматически загружает данные

  7. SocketListener подключается к WebSocket

3. Работа с данными

  1. Компонент вызывает refreshYourEntity() или refreshAll()

  2. Fetcher получает sessionID из /api/auth/session (с кешем)

  3. Выполняется запрос к Directual через api.structure().getData()

  4. DataProvider обновляет state

  5. React перерендеривает компоненты

4. WebSocket обновления

  1. Сервер отправляет событие (например, entity_created)

  2. SocketListener перехватывает событие

  3. DataProvider подписан на это событие через useSocketEvent()

  4. Вызывается refreshYourEntity(true) (silent)

  5. UI обновляется автоматически

5. Обработка ошибок доступа

  1. Fetcher получает 403 ошибку

  2. Возвращается { success: false, error: 'Недостаточно прав' }

  3. Компонент показывает сообщение об ошибке

  4. Пользователь остается авторизованным (403 ≠ протухшая сессия)

Примечание: 403 может означать RBAC ограничения, а не истечение сессии. Для проверки сессии используйте периодический вызов /api/auth/check.

6. Выход (logout flow)

  1. Пользователь нажимает "Выйти"

  2. authContext.logout() отправляет POST /api/auth/logout

  3. API route удаляет cookie

  4. AuthProvider очищает user state

  5. Fetcher очищает кеш sessionID

  6. SocketListener отключается от WebSocket

  7. Редирект на страницу входа


🎓 Best Practices

1. Безопасность

  • ✅ Всегда используй HTTP-only cookie для sessionID

  • ✅ Никогда не храни sessionID в localStorage

  • ✅ Используй серверный клиент в API Routes

  • ✅ Добавь security headers в next.config.mjs

  • ✅ Проверяй сессию при каждом критичном запросе

2. Performance

  • ✅ Используй silent=true для фоновых обновлений

  • ✅ Кешируй sessionID в Fetcher

  • ✅ WebSocket singleton предотвращает множественные подключения

  • ✅ React Context оптимизирован (useCallback с [])

3. UX

  • ✅ Показывай loading только при первоначальной загрузке

  • ✅ Используй refreshing для индикатора обновления

  • ✅ Автоматически обновляй данные при возврате на вкладку

  • ✅ Показывай понятные ошибки пользователю

4. Архитектура

  • ✅ Разделяй клиентский и серверный код

  • ✅ Используй TypeScript для type safety

  • ✅ Следуй иерархии провайдеров

  • ✅ Держи бизнес-логику в контекстах, не в компонентах

5. Directual

  • ✅ Всегда передавай sessionID в запросах

  • ✅ Обрабатывай 403 ошибки глобально

  • ✅ Используй структуры данных правильно

  • ✅ WebSocket требует app_id + session_id в socket.auth


🐛 Troubleshooting

Проблема: WebSocket не подключается

Причины:

  • sessionID не найден (не авторизован)

  • Неверный APP_ID

  • Неверный формат socket.auth

Решение:

  • Проверь что cookie установлена (document.cookie)

  • Проверь что /api/auth/session возвращает sessionID

  • Убедись что NEXT_PUBLIC_DIRECTUAL_APP_ID установлен

Проблема: 403 ошибка на запросе

Причины:

  • Недостаточно прав (RBAC ограничения)

  • Неверная роль пользователя для этого эндпоинта

  • sessionID не передается в запросах

  • Cookie не отправляется (credentials: 'include')

Решение:

  • Проверь права пользователя в Directual (структура WebUser, поле role)

  • Проверь что fetcher.getSessionID() возвращает sessionID

  • Убедись что credentials: 'include' в fetch

  • Проверь настройки эндпоинта в Directual (Access control)

Проблема: Данные не обновляются

Причины:

  • DataProvider не подписан на WebSocket события

  • WebSocket не подключен

  • Неверное имя события

Решение:

  • Проверь что SocketListener монтирован в layout

  • Проверь консоль на наличие логов [WebSocket] eventName

  • Убедись что структура данных в событии правильная

Проблема: Тема не применяется

Причины:

  • CSS переменные не определены

  • Класс dark не добавляется на <html>

  • Tailwind не настроен на darkMode: ["class"]

Решение:

  • Проверь app/globals.css на наличие :root и .dark

  • Проверь что ThemeProvider монтирован

  • Проверь tailwind.config.js


📚 Дополнительные ресурсы

Directual

Next.js

Security

React Patterns


🚪 Страница входа (Login Page)

Что нужно реализовать

Шаблон НЕ включает готовую страницу входа "из коробки". Вам нужно создать:

1. Страницу /app/auth/login/page.tsx:

2. Страницу /app/auth/register/page.tsx (аналогично)

3. Middleware для защиты роутов (опционально):


🧩 shadcn/ui Компоненты

Что входит "из коробки"

Шаблон предполагает использование shadcn/ui, но НЕ устанавливает компоненты автоматически.

Вам нужно установить вручную:

GlobalAlerts vs Toast

В шаблоне есть GlobalAlerts (компонент для WebSocket алертов), который работает аналогично тостам, но:

  • Показывает алерты из WebSocket

  • Позиционируется fixed top-4 right-4

  • Автоматически скрывается через 5 секунд

Если вам нужны обычные тосты для клиентских уведомлений:

Вариант 1: shadcn/ui Toast

Вариант 2: Sonner (рекомендуется)

Используйте в приложении:

Рекомендуемый набор компонентов

Минимальный (для авторизации):

  • button, input, card, alert

Стандартный (для приложения):

  • dialog, popover, dropdown-menu, tabs, tooltip, avatar, skeleton, toast/sonner

Расширенный (для сложных UI):

  • command, combobox, select, radio-group, checkbox, switch, slider, calendar, date-picker, table, pagination


✅ Чеклист для нового проекта

При создании нового приложения по этому шаблону:

Шаг 1: Инициализация

Шаг 2: Конфигурация

Шаг 3: Структура проекта

Шаг 4: Directual интеграция

Шаг 5: Провайдеры и контексты

Шаг 6: UI компоненты

Шаг 7: Directual Backend

Шаг 8: Тестирование

Last updated

Was this helpful?