Изоморфная маршрутизация подразумевает использование одного и того же маршрутизатора как на сервере, так и на клиенте, что позволяет создавать приложения с универсальной логикой обработки маршрутов. Библиотека Universal Router в JavaScript предоставляет гибкий и минималистичный способ реализации такой маршрутизации.
Маршруты в Universal Router задаются в виде массива объектов:
import UniversalRouter from 'universal-router';
const routes = [
{
path: '/',
action: () => 'Главная страница'
},
{
path: '/about',
action: () => 'О проекте'
},
{
path: '/users/:id',
action: ({ params }) => `Профиль пользователя ${params.id}`
}
];
Ключевые моменты:
path — шаблон URL. Поддерживаются динамические сегменты
(:id), опциональные сегменты (:id?),
регулярные выражения и вложенные маршруты.action — функция, которая выполняется при совпадении
маршрута. Возвращает результат рендеринга страницы или данные для
клиента.params внутри action автоматически
содержит значения динамических сегментов URL.Universal Router поддерживает иерархическую структуру маршрутов:
const routes = [
{
path: '/users',
children: [
{
path: '/',
action: () => 'Список пользователей'
},
{
path: '/:id',
action: ({ params }) => `Профиль пользователя ${params.id}`
}
]
}
];
Особенности вложенных маршрутов:
children позволяет структурировать маршруты по
логическим блокам.action, выступая
как контейнер для группы подмаршрутов.action.Маршруты могут возвращать промисы, что удобно для загрузки данных на сервере и клиенте:
const routes = [
{
path: '/posts/:id',
async action({ params }) {
const response = await fetch(`/api/posts/${params.id}`);
const post = await response.json();
return post;
}
}
];
action может быть асинхронной функцией
(async), что позволяет использовать await для
загрузки данных.Universal Router предоставляет возможность задать обработчик для неопределенных маршрутов:
const router = new UniversalRouter(routes, {
resolveRoute(context, params) {
if (context.route) {
return context.route.action(context, params);
}
return 'Страница не найдена';
}
});
resolveRoute позволяет контролировать процесс
разрешения маршрута, включая обработку ошибок и fallback-страниц.Для изоморфных приложений важна идентичность маршрутов на клиенте и сервере. Пример интеграции:
// server.js
import express from 'express';
import UniversalRouter from 'universal-router';
import routes from './routes';
const app = express();
app.get('*', async (req, res) => {
const router = new UniversalRouter(routes);
const result = await router.resolve({ pathname: req.path });
res.send(renderHTML(result));
});
app.listen(3000);
// client.js
import UniversalRouter from 'universal-router';
import routes from './routes';
const router = new UniversalRouter(routes);
router.resolve({ pathname: window.location.pathname }).then(result => {
document.getElementById('root').innerHTML = result;
});
Для SPA можно реализовать управление историей браузера:
function navigate(url) {
history.pushState(null, '', url);
router.resolve({ pathname: url }).then(result => {
document.getElementById('root').innerHTML = result;
});
}
window.addEventListener('popstate', () => {
router.resolve({ pathname: window.location.pathname }).then(result => {
document.getElementById('root').innerHTML = result;
});
});
history.pushState позволяет изменять URL без
перезагрузки страницы.popstate обеспечивает корректную работу
кнопок “Назад/Вперед” браузера.Universal Router поддерживает редиректы через исключения:
import UniversalRouter from 'universal-router';
const routes = [
{
path: '/login',
action: () => 'Страница логина'
},
{
path: '/dashboard',
action: ({ redirect }) => {
const isAuthenticated = false;
if (!isAuthenticated) {
return redirect('/login');
}
return 'Панель управления';
}
}
];
const router = new UniversalRouter(routes, {
async resolveRoute(context, params) {
if (context.route) {
try {
return await context.route.action(context, params);
} catch (e) {
if (e.path) return router.resolve({ pathname: e.path });
throw e;
}
}
return '404';
}
});
redirect позволяет немедленно перенаправить
пользователя на другой маршрут.Universal Router предоставляет возможность передавать глобальные данные в контекст:
const context = { user: { id: 1, role: 'admin' } };
router.resolve({ pathname: '/dashboard', ...context }).then(result => {
console.log(result);
});
action маршрутов.Применение Universal Router в изоморфном приложении обеспечивает:
Такой подход значительно упрощает поддержку и масштабирование универсальных приложений, сохраняя гибкость и минимальный объем кода.