Глобальные middleware

Глобальные middleware в библиотеке Page.js представляют собой функции-посредники, которые выполняются при каждом переходе по маршруту. Они позволяют централизованно обрабатывать повторяющиеся задачи: проверку авторизации, логирование, модификацию контекста запроса, загрузку данных и многое другое.

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


Механизм работы

В основе лежит цепочка функций, каждая из которых получает два аргумента:

function middleware(ctx, next) {
  // логика
  next();
}
  • ctx (context) — объект контекста, содержащий информацию о текущем маршруте
  • next — функция, передающая управление следующему middleware

Глобальные middleware регистрируются через вызов page() без указания пути:

page(middleware1);
page(middleware2);

Порядок регистрации определяет порядок выполнения.


Объект контекста (ctx)

Контекст передается через всю цепочку middleware и может изменяться на любом этапе.

Основные свойства:

  • ctx.path — текущий путь
  • ctx.params — параметры маршрута
  • ctx.querystring — строка запроса
  • ctx.hash — хеш
  • ctx.state — пользовательские данные
  • ctx.handled — флаг обработки

Пример модификации:

page((ctx, next) => {
  ctx.startTime = Date.now();
  next();
});

Последовательность выполнения

Глобальные middleware выполняются до middleware маршрута.

Пример:

page(globalMiddleware);
page('/user', routeMiddleware);

Порядок вызова:

  1. globalMiddleware
  2. routeMiddleware

Если зарегистрировано несколько глобальных middleware:

page(m1);
page(m2);
page('/test', handler);

Цепочка будет следующей:

m1 → m2 → handler

Управление потоком выполнения

Передача управления

Вызов next() обязателен для продолжения цепочки:

page((ctx, next) => {
  console.log('Перед');
  next();
  console.log('После');
});

Прерывание цепочки

Если next() не вызван, выполнение останавливается:

page((ctx, next) => {
  if (!isAuthorized()) {
    page.redirect('/login');
    return;
  }
  next();
});

Типичные сценарии использования

1. Проверка авторизации

page((ctx, next) => {
  if (!auth.isLoggedIn()) {
    return page.redirect('/login');
  }
  next();
});

2. Логирование переходов

page((ctx, next) => {
  console.log(`Переход: ${ctx.path}`);
  next();
});

3. Измерение времени выполнения

page((ctx, next) => {
  const start = performance.now();

  next();

  const end = performance.now();
  console.log(`Время выполнения: ${end - start}ms`);
});

4. Глобальная обработка данных

page((ctx, next) => {
  ctx.user = getCurrentUser();
  next();
});

Асинхронные middleware

Page.js поддерживает асинхронные операции, но требует явного управления потоком.

Использование Promise

page((ctx, next) => {
  fetch('/api/data')
    .then(res => res.json())
    .then(data => {
      ctx.data = data;
      next();
    });
});

Использование async/await

page(async (ctx, next) => {
  const res = await fetch('/api/data');
  ctx.data = await res.json();
  next();
});

Важно: next() должен вызываться вручную даже в async-функциях.


Комбинирование с маршрутизируемыми middleware

Глобальные middleware можно комбинировать с локальными:

page(authMiddleware);

page('/dashboard',
  loadDashboardData,
  renderDashboard
);

Порядок выполнения:

  1. authMiddleware
  2. loadDashboardData
  3. renderDashboard

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

Page.js не имеет встроенного механизма обработки ошибок middleware, поэтому используется ручной подход.

page((ctx, next) => {
  try {
    next();
  } catch (err) {
    console.error(err);
    page.redirect('/error');
  }
});

Для асинхронных операций:

page((ctx, next) => {
  fetch('/api')
    .then(() => next())
    .catch(() => page.redirect('/error'));
});

Глобальные редиректы

Middleware может инициировать перенаправление:

page((ctx, next) => {
  if (ctx.path === '/old') {
    return page.redirect('/new');
  }
  next();
});

Изменение поведения маршрутов

Глобальные middleware позволяют внедрять дополнительную логику без изменения самих маршрутов:

page((ctx, next) => {
  ctx.isMobile = /mobile/i.test(navigator.userAgent);
  next();
});

Теперь любой маршрут может использовать ctx.isMobile.


Переиспользуемость и композиция

Middleware удобно выносить в отдельные функции:

function logger(ctx, next) {
  console.log(ctx.path);
  next();
}

function auth(ctx, next) {
  if (!user) return page.redirect('/login');
  next();
}

page(logger);
page(auth);

Порядок подключения

Критически важно учитывать порядок:

page(auth);
page(logger);

В этом случае логирование произойдет после проверки авторизации.


Влияние на производительность

Каждое middleware добавляет накладные расходы. При большом количестве:

  • увеличивается время перехода
  • усложняется отладка
  • растет связность

Рекомендуется:

  • минимизировать количество глобальных middleware
  • избегать тяжелых операций
  • использовать кэширование

Ограничения

  • Нет встроенного механизма отмены перехода (кроме отсутствия next())
  • Нет встроенного error middleware
  • Нет строгой типизации (если не используется TypeScript)

Расширение контекста

Глобальные middleware — основной способ расширения ctx:

page((ctx, next) => {
  ctx.api = createApiClient();
  next();
});

Это позволяет избежать глобальных переменных.


Сравнение с Express middleware

Сходства:

  • цепочка вызовов через next()
  • возможность модификации контекста
  • последовательное выполнение

Отличия:

  • Page.js работает в браузере
  • отсутствует объект req/res
  • нет встроенной обработки ошибок
  • меньше встроенных инструментов

Практическая архитектура

Типичная структура:

// глобальные middleware
page(initApp);
page(auth);
page(logger);
page(loadUser);

// маршруты
page('/', homeHandler);
page('/profile', profileHandler);

Где:

  • initApp — базовая инициализация
  • auth — проверка доступа
  • logger — аналитика
  • loadUser — загрузка данных пользователя

Отладка

Для отладки цепочки middleware удобно добавлять временные логи:

page((ctx, next) => {
  console.log('Middleware 1 start');
  next();
  console.log('Middleware 1 end');
});

Это помогает понять порядок выполнения.


Частые ошибки

1. Забыт вызов next()

page((ctx, next) => {
  console.log('Лог');
  // next() отсутствует
});

Результат: маршруты не выполняются.


2. Двойной вызов next()

page((ctx, next) => {
  next();
  next(); // ошибка
});

Приводит к непредсказуемому поведению.


3. Асинхронность без ожидания

page((ctx, next) => {
  fetch('/api');
  next(); // данные еще не загружены
});

Организация кода

Рекомендуется разделять middleware по назначению:

  • middlewares/auth.js
  • middlewares/logger.js
  • middlewares/data.js

И подключать централизованно:

import auth from './middlewares/auth';
import logger from './middlewares/logger';

page(auth);
page(logger);

Использование с History API

Page.js основан на History API, поэтому глобальные middleware срабатывают при:

  • переходах через page()
  • кликах по ссылкам
  • изменении URL

Но не срабатывают при:

  • полной перезагрузке страницы (если не инициирован page())

Влияние на UX

Глобальные middleware могут:

  • задерживать переходы (при загрузке данных)
  • обеспечивать защиту маршрутов
  • создавать единый пользовательский опыт

Важно соблюдать баланс между логикой и скоростью.


Пример полной цепочки

page((ctx, next) => {
  console.log('Старт');
  next();
});

page((ctx, next) => {
  ctx.user = getUser();
  next();
});

page('/dashboard', (ctx) => {
  console.log('Рендер dashboard', ctx.user);
});

Цепочка:

Старт → загрузка пользователя → рендер

Глобальные middleware формируют фундамент маршрутизации в Page.js, обеспечивая единый поток обработки запросов и централизованное управление логикой приложения.