Генерация страниц на сервере

Universal Router — это гибкая библиотека маршрутизации для JavaScript, которая работает как на клиенте, так и на сервере. В серверной среде (Node.js) она используется для генерации HTML-страниц на основе маршрутов, что позволяет реализовать серверный рендеринг (SSR) и улучшать SEO.

Создание маршрутов для сервера

Маршруты в Universal Router описываются в виде массива объектов. Каждый объект маршрута может содержать следующие ключи:

  • path — строка с шаблоном URL. Поддерживаются динамические параметры, например /users/:id.
  • action — функция, вызываемая при совпадении маршрута. Она возвращает данные или HTML для ответа сервера.
  • children — массив вложенных маршрутов, позволяющий строить иерархическую структуру URL.

Пример простого маршрута для сервера:

import UniversalRouter from 'universal-router';

const routes = [
  {
    path: '/',
    action: () => `<h1>Главная страница</h1>`
  },
  {
    path: '/users/:id',
    action: ({ params }) => `<h1>Пользователь: ${params.id}</h1>`
  }
];

const router = new UniversalRouter(routes);

Асинхронные действия маршрутов

На сервере часто требуется выполнять асинхронные операции, например, получать данные из базы данных перед рендерингом страницы. Universal Router поддерживает асинхронные функции async в ключе action.

const routes = [
  {
    path: '/posts/:id',
    async action({ params }) {
      const post = await fetchPostFromDB(params.id);
      return `<h1>${post.title}</h1><p>${post.content}</p>`;
    }
  }
];

Использование await в action позволяет обрабатывать данные до генерации HTML и возвращать готовый контент серверу.

Передача контекста в маршруты

Для серверного рендеринга важно передавать в маршруты контекст, который содержит информацию о пользователе, настройках приложения или исходные данные запроса. Контекст передаётся при вызове метода resolve:

const context = { user: { id: 1, name: 'Alice' } };

router.resolve({ pathname: '/users/1', context }).then(html => {
  console.log(html); // HTML с информацией о пользователе
});

Внутри маршрута объект context доступен как параметр, что позволяет строить страницы динамически и безопасно.

Обработка ошибок и редиректов

Universal Router позволяет централизованно обрабатывать ошибки и выполнять перенаправления. Для этого в action можно выбросить исключение или вернуть объект с ключом redirect.

Пример редиректа:

{
  path: '/admin',
  action: () => ({ redirect: '/login' })
}

Пример обработки ошибки:

{
  path: '/posts/:id',
  async action({ params }) {
    const post = await fetchPostFromDB(params.id);
    if (!post) throw new Error('Пост не найден');
    return `<h1>${post.title}</h1>`;
  }
}

На сервере можно перехватывать эти ошибки и формировать корректный HTTP-статус:

router.resolve({ pathname: '/posts/999' })
  .then(html => response.send(html))
  .catch(err => response.status(404).send('<h1>Страница не найдена</h1>'));

Вложенные маршруты и вложенный SSR

Вложенные маршруты позволяют строить сложные URL с общими шаблонами и компонентами. В серверном контексте это особенно полезно для генерации шаблонов с общей структурой:

const routes = [
  {
    path: '/',
    action: () => '<h1>Главная</h1>',
    children: [
      {
        path: 'about',
        action: () => '<h1>О нас</h1>'
      },
      {
        path: 'contact',
        action: () => '<h1>Контакты</h1>'
      }
    ]
  }
];

Метод resolve автоматически учитывает вложенность и возвращает HTML соответствующего дочернего маршрута.

Использование Universal Router с Node.js и Express

Для интеграции SSR часто используют фреймворк express. Universal Router позволяет обрабатывать все входящие HTTP-запросы через один маршрутизатор:

import express from 'express';
import UniversalRouter from 'universal-router';

const app = express();
const router = new UniversalRouter(routes);

app.get('*', async (req, res) => {
  try {
    const html = await router.resolve({ pathname: req.path });
    res.send(html);
  } catch (err) {
    res.status(404).send('<h1>Страница не найдена</h1>');
  }
});

app.listen(3000);

В этом подходе все маршруты управляются централизованно, что упрощает поддержку и расширение серверной логики.

Оптимизация и кеширование

Для увеличения производительности серверного рендеринга можно кешировать результаты маршрутов. Universal Router позволяет использовать контекст и промежуточные слои для кеширования:

const cache = new Map();

const routes = [
  {
    path: '/posts/:id',
    async action({ params }) {
      if (cache.has(params.id)) return cache.get(params.id);
      const post = await fetchPostFromDB(params.id);
      const html = `<h1>${post.title}</h1>`;
      cache.set(params.id, html);
      return html;
    }
  }
];

Кеширование уменьшает нагрузку на базу данных и ускоряет генерацию HTML, что критично для серверного рендеринга с большим количеством пользователей.

Поддержка универсальных приложений

Universal Router позволяет использовать одни и те же маршруты как на сервере, так и на клиенте, обеспечивая единую маршрутизацию в универсальных приложениях (isomorphic apps). Это упрощает синхронизацию данных и состояние приложения между сервером и браузером.


Universal Router предоставляет мощные средства для генерации страниц на сервере: асинхронные маршруты, вложенные структуры, контекст и обработку ошибок. Гибкость библиотеки позволяет строить как простые SSR-сайты, так и сложные универсальные приложения с общими маршрутами для клиента и сервера.