Universal Router на сервере

Для использования Universal Router на сервере необходимо установить пакет через npm:

npm install universal-router

После установки создаётся базовая структура маршрутизатора. Основной объект — это экземпляр UniversalRouter, которому передаётся массив маршрутов. Каждый маршрут представляет собой объект с полями:

  • path — строка с шаблоном URL;
  • action — функция, вызываемая при совпадении маршрута;
  • children — массив дочерних маршрутов (опционально).

Пример базовой конфигурации:

import UniversalRouter from 'universal-router';

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

const router = new UniversalRouter(routes);

Асинхронные маршруты

Universal Router поддерживает асинхронные действия, что удобно при работе с серверными данными. Функция action может возвращать Promise. Это позволяет получать данные из базы или API перед формированием ответа.

const routes = [
  {
    path: '/posts/:id',
    async action({ params }) {
      const post = await getPostById(params.id);
      if (!post) throw new Error('Пост не найден');
      return post;
    }
  }
];

Асинхронные маршруты обеспечивают гибкость и делают серверный рендеринг динамичным.

Передача контекста

При инициализации маршрутизатора можно передать объект context, который будет доступен во всех action маршрутах:

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

const router = new UniversalRouter(routes, { context });

router.resolve('/user/1').then(result => {
  console.log(result); // Доступ к db и user через context
});

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

Обработка ошибок

Ошибки внутри маршрутов можно перехватывать с помощью стандартного try/catch или через метод resolve с .catch.

router.resolve('/posts/999')
  .then(result => console.log(result))
  .catch(err => console.error('Ошибка маршрута:', err.message));

Также можно определить маршрут с универсальной обработкой ошибок:

const routes = [
  {
    path: '*',
    action: ({ path }) => `Маршрут ${path} не найден`
  }
];

Маршрут с path: '*' выступает в роли fallback и обрабатывает все неизвестные URL.

Вложенные маршруты

Universal Router поддерживает иерархию маршрутов через свойство children. Это особенно удобно для сложной серверной архитектуры.

const routes = [
  {
    path: '/admin',
    action: () => 'Админ-панель',
    children: [
      {
        path: '/users',
        action: () => 'Список пользователей'
      },
      {
        path: '/settings',
        action: () => 'Настройки'
      }
    ]
  }
];

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

Динамические сегменты

Маршруты с параметрами позволяют захватывать части URL. Параметры задаются через двоеточие :param.

{
  path: '/category/:name',
  action: ({ params }) => `Категория: ${params.name}`
}

Поддерживается несколько параметров и регулярные выражения для валидации:

{
  path: '/order/:id(\\d+)',
  action: ({ params }) => `Заказ №${params.id}`
}

Такой подход позволяет строго контролировать формат параметров и избегать ошибок на сервере.

Редиректы и lazy-loading

Для серверного рендеринга полезны редиректы и ленивые маршруты:

{
  path: '/old-page',
  action: () => ({ redirect: '/new-page' })
}

Также маршруты могут загружаться динамически:

{
  path: '/dashboard',
  async action() {
    const module = await import('./dashboard.js');
    return module.render();
  }
}

Это уменьшает нагрузку на сервер и ускоряет обработку часто посещаемых маршрутов.

Middleware-подобная логика

Хотя Universal Router не имеет встроенной системы middleware, её можно реализовать через обёртки action:

async function auth({ context }) {
  if (!context.user) throw new Error('Доступ запрещён');
  return true;
}

const routes = [
  {
    path: '/protected',
    async action(context) {
      await auth(context);
      return 'Секретная информация';
    }
  }
];

Такой подход обеспечивает проверку прав доступа и другие предварительные действия перед основной логикой маршрута.

Интеграция с серверными фреймворками

Universal Router легко интегрируется с Node.js фреймворками:

  • Express:
import express from 'express';
import UniversalRouter from 'universal-router';

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

app.use(async (req, res) => {
  try {
    const result = await router.resolve(req.path);
    res.send(result);
  } catch (err) {
    res.status(404).send(err.message);
  }
});

app.listen(3000);
  • Koa:
import Koa from 'koa';
import UniversalRouter from 'universal-router';

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

app.use(async ctx => {
  try {
    ctx.body = await router.resolve(ctx.path);
  } catch (err) {
    ctx.status = 404;
    ctx.body = err.message;
  }
});

app.listen(3000);

Это делает Universal Router универсальным инструментом для серверного рендеринга и обработки маршрутов без зависимости от конкретного фреймворка.

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

Для больших приложений рекомендуется кешировать результаты action, особенно если они требуют запросов к базе данных или внешним API. Это снижает нагрузку на сервер и ускоряет отклик. Используются стандартные подходы Node.js: in-memory кеш, Redis или мемоизация функций маршрутов.

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