Работа с History API

History API — это встроенный механизм браузера, позволяющий управлять историей навигации без перезагрузки страницы. Библиотека Page.js использует его как фундамент для реализации клиентской маршрутизации в одностраничных приложениях (SPA).

Ключевые возможности History API:

  • изменение URL без перезагрузки страницы
  • добавление записей в историю
  • реакция на навигацию пользователя (кнопки «назад» / «вперёд»)
  • хранение состояния для каждой записи

Основные методы:

  • history.pushState(state, title, url)
  • history.replaceState(state, title, url)
  • событие popstate

Интеграция Page.js с History API

Page.js абстрагирует работу с History API, предоставляя удобный интерфейс маршрутизации. При каждом переходе библиотека:

  1. Обрабатывает маршрут
  2. Вызывает соответствующий обработчик
  3. Изменяет URL через pushState или replaceState
  4. Управляет состоянием

Пример базовой маршрутизации:

page('/', (ctx) => {
  console.log('Главная страница');
});

page('/about', (ctx) => {
  console.log('О нас');
});

page();

При переходе между маршрутами Page.js автоматически использует history.pushState.

Метод pushState

pushState добавляет новую запись в стек истории.

Сигнатура:

history.pushState(state, title, url);
  • state — объект состояния
  • title — игнорируется большинством браузеров
  • url — новый URL

В контексте Page.js:

page('/user/:id', (ctx) => {
  console.log(ctx.params.id);
});

При переходе на /user/42:

  • URL изменяется
  • создаётся новая запись в истории
  • сохраняется контекст маршрута

Метод replaceState

replaceState заменяет текущую запись, не добавляя новую.

history.replaceState(state, title, url);

Использование в Page.js:

page.redirect('/old', '/new');

В этом случае:

  • URL обновляется
  • старая запись заменяется
  • пользователь не сможет вернуться к /old через кнопку «назад»

Объект состояния (state)

Каждая запись истории может содержать произвольный объект состояния.

В Page.js он доступен через ctx.state:

page('/profile', (ctx) => {
  console.log(ctx.state);
});

Пример установки состояния:

page.show('/profile', { userId: 123 });

Особенности:

  • состояние не сериализуется в URL
  • сохраняется в памяти браузера
  • восстанавливается при навигации назад/вперёд

Событие popstate

Событие popstate возникает при переходе по истории:

  • кнопка «назад»
  • кнопка «вперёд»
  • вызов history.back() или history.forward()

Пример:

window.addEventListener('popstate', (event) => {
  console.log(event.state);
});

Page.js автоматически подписывается на это событие и:

  • определяет текущий маршрут
  • повторно вызывает обработчики
  • восстанавливает состояние

Управление навигацией

Page.js предоставляет методы, работающие поверх History API:

page.show(path, state)

Добавляет новую запись:

page.show('/dashboard', { fromLogin: true });

Аналог pushState.

page.replace(path, state)

Заменяет текущую запись:

page.replace('/dashboard');

Аналог replaceState.

page.redirect(from, to)

Перенаправление:

page.redirect('/login', '/dashboard');

Использует replaceState, чтобы избежать лишней записи в истории.

Работа с URL без перезагрузки

Главная цель History API — изменение URL без reload.

Page.js перехватывает клики по ссылкам:

<a href="/about">О нас</a>

При клике:

  1. предотвращается стандартное поведение (event.preventDefault)
  2. вызывается page('/about')
  3. выполняется pushState
  4. вызывается обработчик маршрута

Настройка базового пути (base path)

History API чувствителен к базовому URL приложения.

В Page.js:

page.base('/app');

Теперь маршруты:

page('/home', ...);

будут соответствовать:

/app/home

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

Хэш-режим vs History API

Page.js может работать через History API или через hash (#).

History API (рекомендуется)

/about

Плюсы:

  • чистые URL
  • лучшее SEO
  • современный стандарт

Минусы:

  • требуется настройка сервера

Hash-режим

/#/about

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

page({ hashbang: true });

Плюсы:

  • не требует настройки сервера

Минусы:

  • менее эстетичные URL
  • ограниченные возможности SEO

Обработка прямых переходов

При прямом вводе URL:

https://example.com/about

сервер должен вернуть HTML приложения, иначе возникнет ошибка 404.

Это связано с тем, что:

  • History API работает только на клиенте
  • сервер не знает о маршрутах Page.js

Типичное решение:

  • настроить сервер на возврат index.html для всех маршрутов

Состояние и восстановление интерфейса

History API позволяет сохранять состояние интерфейса:

page('/list', (ctx) => {
  if (ctx.state.scrollPosition) {
    window.scrollTo(0, ctx.state.scrollPosition);
  }
});

Сохранение:

window.addEventListener('beforeunload', () => {
  history.replaceState({
    scrollPosition: window.scrollY
  }, '');
});

Page.js передаёт это состояние в ctx.state.

Ограничения History API

Размер состояния

Браузеры ограничивают размер объекта state:

  • обычно несколько мегабайт
  • зависит от реализации

Безопасность

  • нельзя изменять домен
  • URL должен быть того же происхождения (same-origin)

Поддержка браузеров

History API поддерживается:

  • всеми современными браузерами
  • IE10+

Page.js автоматически проверяет поддержку.

Перехват ссылок

Page.js перехватывает только внутренние ссылки:

<a href="/about">О нас</a>

Не перехватываются:

<a href="https://external.com">Внешний сайт</a>
<a href="/file.pdf">Файл</a>
<a target="_blank" href="/about">Открыть в новой вкладке</a>

Это реализуется через фильтрацию событий клика.

Асинхронная навигация

History API не блокирует выполнение кода, поэтому Page.js поддерживает асинхронные маршруты:

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

URL изменяется сразу, а данные подгружаются позже.

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

Пример поведения:

page('/step1');
page('/step2');
page('/step3');

История:

step1 → step2 → step3

Кнопка «назад»:

  • возвращает к step2
  • вызывает popstate
  • Page.js повторно обрабатывает маршрут

Программное управление историей

Можно использовать нативные методы вместе с Page.js:

history.back();
history.forward();
history.go(-2);

Page.js корректно обработает эти действия через popstate.

Оптимизация навигации

Избежание лишних записей

page.replace('/same-page');

Полезно при:

  • обновлении параметров
  • редиректах
  • логике авторизации

Кэширование состояния

const cache = {};

page('/user/:id', (ctx) => {
  if (cache[ctx.params.id]) {
    render(cache[ctx.params.id]);
  } else {
    fetchUser(ctx.params.id).then(data => {
      cache[ctx.params.id] = data;
      render(data);
    });
  }
});

History API при этом хранит только навигацию, а не данные.

Связь URL и состояния приложения

Правильное использование History API предполагает:

  • URL отражает текущее состояние
  • состояние можно восстановить по URL
  • state используется как вспомогательный механизм

Пример:

/products?page=2

Вместо хранения номера страницы только в state.

Отладка

Полезные инструменты:

  • вкладка Application → History (в DevTools)
  • логирование ctx
  • прослушивание popstate
window.addEventListener('popstate', console.log);

Практическая схема работы Page.js + History API

  1. Пользователь кликает ссылку
  2. Page.js перехватывает событие
  3. Вызывается pushState
  4. URL обновляется
  5. Выполняется обработчик маршрута
  6. При навигации назад — срабатывает popstate
  7. Page.js повторно вызывает обработчик

Такой подход позволяет:

  • создавать SPA без фреймворков
  • полностью контролировать навигацию
  • использовать стандартные браузерные механизмы