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/loginregister(email, password, username)- регистрация через/api/auth/registerlogout()- выход через/api/auth/logoutisAuthorized()- проверка авторизации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 с дополнительными фичами:
Ключевые возможности:
Кеширование sessionID - избегает множественных вызовов
/api/auth/sessionОбработка ошибок - унифицированная обработка ошибок API
Загрузка файлов - с отслеживанием прогресса
Универсальные методы -
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)- обновить профиль
Автоматическое обновление:
При монтировании (если
isAuthorized())При WebSocket событиях:
refresh,entity_created,entity_updated,entity_deletedПри возврате на вкладку браузера (
visibilitychange)
Silent refresh:
silent=true- обновление безloadingиндикатораИспользуется для фоновых обновлений (WebSocket, visibilitychange)
Пример использования:
Адаптация под проект: Замените примеры методов на ваши сущности. Принцип остается тот же - централизованное хранилище данных с автоматической синхронизацией через WebSocket.
📤 Загрузка файлов
Настройка структуры в Directual
Обязательный шаг: Создайте структуру данных file_links в Directual:
Создайте структуру с именем
file_linksДобавьте поле
linkс типом FileUploadНастройте права доступа:
Write: разрешите запись для авторизованных пользователей
Read: настройте по необходимости
Создайте эндпоинт
uploadFiles(POST) для загрузки файлов
Структура поля:
Метод uploadFile в Fetcher
Fetcher включает специальный метод для загрузки файлов на Directual с отслеживанием прогресса.
Сигнатура:
Механизм работы:
FormData - файл оборачивается в FormData для multipart/form-data
Прокси через rewrites - используется
/good/api/v5/data/file_links/uploadFilesSessionID из cookie - автоматически добавляется в query параметры
XMLHttpRequest - для отслеживания прогресса (если передан
onProgress)Fallback на fetch - если
onProgressне нужен
Структура запроса:
Пример использования:
Компонент с прогресс-баром:
Важные детали:
credentials: 'include' - обязательно для отправки cookie с sessionID
XMLHttpRequest.upload.progress - используется для отслеживания прогресса
Обработка 403 - если сессия невалидна, вернется ошибка
Возвращаемый объект -
{ 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:
Читаем
actionиз запросаSwitch/case по типу действия
Выполняем бизнес-логику
Возвращаем результат:
Синхронно: 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 ↔ darkisDark,isLight,isSystem- утилиты проверки
Механизм работы:
Тема хранится в
localStorage.themeПрименяется через класс
lightилиdarkна<html>При
systemслушаетprefers-color-schememedia queryПри изменении системной темы автоматически переключается
CSS переменные (app/globals.css):
tailwind.config.js
🔒 Security Headers
next.config.mjs
Обязательные security headers:
Важно:
unsafe-evalможет потребоваться для CodeMirror или других редакторовconnect-srcдолжен включать Directual API и WebSocketframe-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
Механизм:
WebSocket отправляет событие
alertс даннымиSocketListener перехватывает и вызывает
window.__showGlobalAlert()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)
Root Layout монтируется
ThemeProvider загружает тему из localStorage и применяет
AuthProvider проверяет сессию через
/api/auth/checkDataProvider загружает данные (если авторизован)
SocketListener подключается к WebSocket (если авторизован)
2. Авторизация (login flow)
Пользователь вводит credentials
authContext.login()отправляет POST/api/auth/loginAPI route вызывает
serverApi.auth.login()При успехе устанавливается HTTP-only cookie
AuthProvider обновляет
userstateDataProvider автоматически загружает данные
SocketListener подключается к WebSocket
3. Работа с данными
Компонент вызывает
refreshYourEntity()илиrefreshAll()Fetcher получает
sessionIDиз/api/auth/session(с кешем)Выполняется запрос к Directual через
api.structure().getData()DataProvider обновляет state
React перерендеривает компоненты
4. WebSocket обновления
Сервер отправляет событие (например,
entity_created)SocketListener перехватывает событие
DataProvider подписан на это событие через
useSocketEvent()Вызывается
refreshYourEntity(true)(silent)UI обновляется автоматически
5. Обработка ошибок доступа
Fetcher получает 403 ошибку
Возвращается
{ success: false, error: 'Недостаточно прав' }Компонент показывает сообщение об ошибке
Пользователь остается авторизованным (403 ≠ протухшая сессия)
Примечание: 403 может означать RBAC ограничения, а не истечение сессии. Для проверки сессии используйте периодический вызов /api/auth/check.
6. Выход (logout flow)
Пользователь нажимает "Выйти"
authContext.logout()отправляет POST/api/auth/logoutAPI route удаляет cookie
AuthProvider очищает
userstateFetcher очищает кеш sessionID
SocketListener отключается от WebSocket
Редирект на страницу входа
🎓 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?