Universal Router — это гибкий маршрутизатор для JavaScript, который
работает как на сервере, так и в браузере. Его ключевая особенность —
декларативное определение маршрутов через объект или массив объектов,
где каждый маршрут описывается с помощью путя
(path), обработчика
(action) и опциональных вложенных маршрутов
(children).
Структура маршрута:
{
path: '/users/:id',
action: ({ params }) => `Пользователь с ID ${params.id}`,
children: [
{
path: 'edit',
action: ({ params }) => `Редактирование пользователя ${params.id}`
}
]
}
path — строка с поддержкой динамических сегментов
(:id) и регулярных выражений.action — функция, вызываемая при совпадении маршрута,
принимает объект с параметрами, базовым URL и контекстом.children — вложенные маршруты, которые позволяют
строить иерархию маршрутизации.Создание маршрутизатора требует передачи массива маршрутов в
конструктор UniversalRouter.
import UniversalRouter from 'universal-router';
const routes = [
{
path: '/',
action: () => 'Главная страница'
},
{
path: '/about',
action: () => 'О нас'
}
];
const router = new UniversalRouter(routes);
Для разрешения маршрута используется метод resolve:
router.resolve('/about').then(result => {
console.log(result); // "О нас"
});
Метод resolve возвращает промис, что позволяет работать
с асинхронными действиями, такими как запросы к серверу.
Динамические сегменты в path обозначаются с помощью
двоеточия :paramName. Они автоматически извлекаются и
передаются в action через объект params.
{
path: '/posts/:postId',
action: ({ params }) => `Пост с ID ${params.postId}`
}
Поддерживается также вложенная маршрутизация:
{
path: '/posts/:postId',
children: [
{
path: 'comments/:commentId',
action: ({ params }) => `Комментарий ${params.commentId} к посту ${params.postId}`
}
]
}
Universal Router позволяет использовать асинхронные функции в
action, что удобно для загрузки данных перед отображением
страницы.
{
path: '/profile/:id',
async action({ params }) {
const user = await fetchUser(params.id);
return `Профиль пользователя: ${user.name}`;
}
}
Асинхронная маршрутизация интегрируется с промисами и
async/await, что упрощает построение SPA с динамическим
контентом.
context — объект, который можно передавать в
resolve и который будет доступен в каждом
action. Это удобно для передачи сервисов, состояния
приложения или данных конфигурации.
const router = new UniversalRouter(routes);
const context = { userService, authToken: '123' };
router.resolve('/profile/42', { context }).then(result => {
console.log(result);
});
В action можно обращаться к context
напрямую:
action({ params, context }) {
return context.userService.getUser(params.id);
}
Для перехвата маршрутов, которые не совпали с существующими,
используют маршрут с path: '(.*)' или
path: '*'.
{
path: '(.*)',
action: () => '404 — страница не найдена'
}
Также можно обрабатывать ошибки внутри action через
try/catch или глобально через промисы:
router.resolve('/unknown')
.catch(err => {
console.error('Ошибка маршрутизации:', err);
});
Вложенные маршруты позволяют создавать модульные маршруты, которые подключаются только при необходимости. Это важно для оптимизации SPA.
{
path: '/dashboard',
action: () => 'Панель управления',
children: [
{
path: 'stats',
action: () => 'Статистика',
},
{
path: 'settings',
action: () => import('./settings.js').then(module => module.default)
}
]
}
Поддержка lazy-loading через динамический импорт уменьшает начальный размер бандла.
Universal Router допускает добавление промежуточной логики через
обертки action. Это позволяет проверять авторизацию,
логировать действия или выполнять редиректы.
function authMiddleware(action) {
return async (context) => {
if (!context.user) {
throw new Error('Неавторизованный доступ');
}
return action(context);
};
}
{
path: '/admin',
action: authMiddleware(() => 'Админ-панель')
}
Маршруты могут использовать регулярные выражения для более сложного сопоставления:
{
path: /^\/product\/(\d+)$/,
action: ({ params, match }) => `Продукт с ID ${match[1]}`
}
При таком подходе match содержит массив совпадений
регулярного выражения, что удобно для обработки нестандартных URL.
Для интеграционных тестов важно проверять:
action;params и context;Пример интеграционного теста с использованием Jest:
test('маршрут /posts/:id возвращает корректный результат', async () => {
const routes = [
{ path: '/posts/:id', action: ({ params }) => `Пост ${params.id}` }
];
const router = new UniversalRouter(routes);
const result = await router.resolve('/posts/10');
expect(result).toBe('Пост 10');
});
test('неизвестный маршрут возвращает 404', async () => {
const routes = [
{ path: '/', action: () => 'Главная' },
{ path: '(.*)', action: () => '404' }
];
const router = new UniversalRouter(routes);
const result = await router.resolve('/unknown');
expect(result).toBe('404');
});
Тестирование асинхронных маршрутов и middleware требует мокирования
сервисов и проверки передачи context.
Universal Router предоставляет мощный и гибкий инструмент для построения маршрутизации в любом JavaScript-приложении, обеспечивая поддержку динамических сегментов, асинхронных данных, middleware и lazy-loading, что делает его идеальным выбором для SPA и серверных приложений.