Universal Router — это легковесная библиотека маршрутизации для JavaScript, которая может работать как на клиенте, так и на сервере. Для серверного рендеринга она предоставляет гибкий способ сопоставления URL с обработчиками и генерации HTML до того, как страница будет отправлена пользователю.
Маршруты в Universal Router определяются массивом объектов, каждый из
которых содержит ключ path и функцию action.
Для серверного рендеринга action обычно возвращает строку
HTML или объект с данными, которые затем используются для генерации
страницы.
import UniversalRouter from 'universal-router';
const routes = [
{
path: '/',
action: async () => {
return `<h1>Главная страница</h1>`;
}
},
{
path: '/about',
action: async () => {
return `<h1>О нас</h1>`;
}
},
{
path: '/users/:id',
action: async ({ params }) => {
const userData = await fetchUser(params.id);
return `<h1>Пользователь: ${userData.name}</h1>`;
}
}
];
const router = new UniversalRouter(routes);
Ключевые моменты:
params содержит все динамические сегменты URL, например
:id.action может быть асинхронной, что позволяет
выполнять запросы к базе данных или API до рендеринга страницы.Для серверного рендеринга чаще всего Universal Router используют вместе с Node.js и фреймворком Express:
import express from 'express';
import UniversalRouter from 'universal-router';
const app = express();
app.get('*', async (req, res) => {
try {
const html = await router.resolve({ pathname: req.path });
res.send(html);
} catch (error) {
res.status(404).send('Страница не найдена');
}
});
app.listen(3000, () => {
console.log('Сервер запущен на порту 3000');
});
Особенности работы на сервере:
resolve принимает объект с ключом pathname
для маршрутизации запроса.context в resolve, чтобы
передавать дополнительные данные или сервисы в обработчики
маршрутов.action нужно корректно
обрабатывать, чтобы сервер возвращал корректный HTTP-статус.Контекст (context) — это объект, который передается во
все функции action. Он используется для передачи сервисов,
глобальных данных или функций рендеринга:
const context = {
fetchUser,
renderPage: (content) => `<html><body>${content}</body></html>`
};
const html = await router.resolve({ pathname: '/users/42', context });
В маршруте можно обращаться к контексту через деструктуризацию:
{
path: '/users/:id',
action: async ({ params, context }) => {
const userData = await context.fetchUser(params.id);
return context.renderPage(`<h1>${userData.name}</h1>`);
}
}
Преимущества использования контекста:
Серверный рендеринг часто требует загрузки данных до генерации HTML.
Universal Router поддерживает асинхронные action, что
позволяет использовать await внутри маршрутов:
{
path: '/posts/:id',
action: async ({ params }) => {
const post = await fetchPostById(params.id);
if (!post) throw new Error('Not Found');
return `<article><h1>${post.title}</h1><p>${post.content}</p></article>`;
}
}
Рекомендации:
try/catch внутри action
для контроля ошибок.Universal Router позволяет использовать вложенные маршруты, что удобно для структурирования крупных приложений:
const routes = [
{
path: '/users',
children: [
{
path: '/',
action: async () => 'Список пользователей'
},
{
path: '/:id',
action: async ({ params }) => `Пользователь ID: ${params.id}`
}
]
}
];
Для серверного рендеринга важно уметь обрабатывать редиректы:
{
path: '/old-path',
action: () => ({
redirect: '/new-path'
})
}
На сервере можно обрабатывать редиректы так:
const result = await router.resolve({ pathname: req.path });
if (result.redirect) {
res.redirect(301, result.redirect);
} else {
res.send(result);
}
Если маршруты используют несколько уровней вложенности и асинхронных действий, можно настроить fallback для обработки ошибок или отсутствующих страниц:
const routes = [
{
path: '/dashboard',
action: async () => '<h1>Панель управления</h1>',
children: [
{
path: '/stats',
action: async () => '<h2>Статистика</h2>'
}
]
},
{
path: '(.*)',
action: () => '<h1>404: Страница не найдена</h1>'
}
];
(.*) используется как catch-all для всех
неизвестных маршрутов.Для серверного рендеринга React-компонентов Universal Router отлично подходит как механизм маршрутизации:
import { renderToString } from 'react-dom/server';
import App from './App';
const routes = [
{
path: '/',
action: () => renderToString(<App page="home" />)
},
{
path: '/about',
action: () => renderToString(<App page="about" />)
}
];
Эти возможности делают Universal Router мощным инструментом для построения серверных приложений на JavaScript с гибкой маршрутизацией и предзагруженным контентом.