Frontend‑приложение проекта Obfuscatorium, разработанное на базе Next.js (App Router) и React с использованием современного TypeScript‑стека.
Проект ориентирован на масштабируемость, читаемую архитектуру и предсказуемое управление состоянием.
В проекте используется следующий стек:
- Framework: Next.js 16 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS v4
- CSS Framework: Bootstrap (частично, для готовых компонентов и утилит)
- State Management: Redux Toolkit
- Internationalization: next-intl (EN / RU)
- Node.js (LTS рекомендуется)
- Пакетный менеджер: bun / npm / yarn / pnpm
bun install
# или
npm install
# или
yarn install
# или
pnpm installbun run dev
# или
npm run dev
# или
yarn dev
# или
pnpm devПосле запуска приложение будет доступно по адресу: http://localhost:3000
Проект организован по принципам Feature‑Sliced Design (FSD):
src/
├── app/ # Инициализация приложения, store, rootReducer
├── entities/ # Бизнес‑сущности (User и др.)
├── features/ # Фичи (auth, ui, и т.д.)
├── ui/ # Переиспользуемые UI‑компоненты
├── i18n/ # Конфигурация интернационализации
└── store/ # Redux store и связанные типы
Ключевая идея: одна фича — один независимый модуль состояния.
Доступные команды из package.json:
dev— запуск в режиме разработкиbuild— сборка production‑версииstart— запуск production‑сборкиlint— проверка кода ESLint
В проекте используется Redux Toolkit (RTK) — рекомендованный способ работы с Redux.
Основные сущности:
- Slice (reducer) — описание части состояния
- initialState — начальное состояние slice
- name — уникальный namespace slice
- Thunk — асинхронная логика и side effects
- rootReducer — объединение всех reducer
Slice описывает одну логическую область состояния (например, авторизация).
import { createSlice } from '@reduxjs/toolkit'
const initialState = {
isAuth: false,
token: '',
url: '',
loginStatus: 'idle', // idle | ok | error
}
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
loginSuccess(state, action) {
state.isAuth = true
state.token = action.payload.token
state.url = action.payload.url
state.loginStatus = 'ok'
},
logout(state) {
state.isAuth = false
state.token = ''
state.url = ''
state.loginStatus = 'error'
},
},
})
export const { loginSuccess, logout } = authSlice.actions
export default authSlice.reducer- initialState — начальное состояние slice
- name — имя slice, используется как namespace (
auth/loginSuccess) - reducers — только синхронное изменение state
❗ В reducer запрещено:
- работать с
localStorage - обращаться к DOM
- делать API‑запросы
Thunk используется для:
- API‑запросов
- работы с
localStorage - side effects
- сложной бизнес‑логики
import { createAsyncThunk } from '@reduxjs/toolkit'
import { loginSuccess, logout } from './authReducer'
export const loginThunk = createAsyncThunk(
'auth/login',
async (data, { dispatch, rejectWithValue }) => {
try {
const token = data.accessToken
const url = data.url
// side effects
localStorage.setItem('access_token', token)
document.body.classList.add('no-scroll')
// обновление state
dispatch(loginSuccess({ token, url }))
} catch (e) {
dispatch(logout())
return rejectWithValue('login error')
}
}
)📌 Правило:
Вся асинхронная логика и побочные эффекты — только в thunk
rootReducer объединяет все reducer приложения в один глобальный reducer.
import { combineReducers } from '@reduxjs/toolkit'
import authReducer from '../features/auth/authReducer'
import uiReducer from '../features/ui/uiReducer'
export const rootReducer = combineReducers({
auth: authReducer,
ui: uiReducer,
})После этого состояние приложения выглядит так:
state = {
auth: { ... },
ui: { ... },
}Для работы с Redux в React используются хуки из react-redux.
useDispatch позволяет отправлять actions (в том числе thunk) в Redux Store.
const dispatch = useDispatch()
dispatch(loginThunk(data))
dispatch(logout())Что важно понимать:
dispatch— это единственный способ изменить state- можно диспатчить:
- обычные actions (
logout()) - thunk (
loginThunk())
- обычные actions (
В проекте рекомендуется использовать типизированный хук:
const dispatch = useAppDispatch()Это даёт автокомплит и защиту типов.
useSelector используется для чтения данных из Redux Store.
const isAuth = useSelector((state) => state.auth.isAuth)
const token = useSelector((state) => state.auth.token)Как это работает:
state— это глобальное состояние, сформированноеrootReducer- компонент автоматически перерисуется при изменении выбранных данных
Рекомендуемый вариант — типизированный хук:
const isAuth = useAppSelector((state) => state.auth.isAuth)useDispatch— отправляем события (actions / thunk)useSelector— читаем состояние
- Slice — описывает часть состояния
- initialState — стартовые данные
- name — namespace slice
- reducers — синхронное обновление state
- Thunk — асинхронка и side effects
- rootReducer — сборка всех reducer