W3docs

JavaScript History API

JavaScript History API позволяет управлять историей браузера без перезагрузки страницы — незаменимый инструмент для одностраничных приложений.

Введение в JavaScript History API

В современной веб-разработке создание бесшовного пользовательского опыта нередко требует управления историей браузера. JavaScript History API позволяет читать и изменять историю сеанса браузера — список страниц (и состояний URL), которые пользователь посетил в текущей вкладке. Используя этот API, разработчики могут обновлять адресную строку и стек «назад/вперёд» без полной перезагрузки страницы, что необходимо одностраничным приложениям (SPA) для быстрой и нативной работы.

На этой странице рассмотрены три ключевых метода (pushState, replaceState и вспомогательные методы навигации), порядок обработки события popstate, типичные ошибки, которые допускают разработчики, а также рекомендации по использованию API в продакшене.

Зачем нужен History API

До появления этого API единственным способом изменить URL была навигация, которая приводила к перезагрузке всего документа. SPA отображают новые «страницы» с помощью JavaScript, поэтому им необходимо, чтобы URL оставался синхронизированным без перезагрузки — иначе кнопка «Назад», закладки и ссылки для совместного использования перестают работать.

History API решает эту задачу, предоставляя возможность:

  • Добавлять новую запись в стек «назад/вперёд» (pushState).
  • Изменять текущую запись на месте (replaceState).
  • Реагировать на перемещение пользователя по стеку с помощью кнопок «Назад/Вперёд» (popstate).

Данные текущей записи можно прочитать через свойство history.state (только для чтения), а history.length показывает, сколько записей находится в стеке сеанса. Для формирования URL, передаваемых этим методам, удобным вспомогательным инструментом служит объект URL.

Объект history в двух словах

API доступен через глобальный объект window.history (можно писать просто history):

ЧленЧто делает
history.pushState(state, title, url)Добавляет новую запись в стек истории и обновляет URL.
history.replaceState(state, title, url)Заменяет текущую запись — новая запись в стеке не создаётся.
history.stateКопия object состояния текущей записи (только для чтения).
history.lengthКоличество записей в истории сеанса.
history.back()Переходит на одну запись назад (аналог кнопки «Назад» в браузере).
history.forward()Переходит на одну запись вперёд.
history.go(n)Перепрыгивает на n записей (отрицательное значение — назад, положительное — вперёд).

Использование History API в веб-приложениях

Навигация между состояниями

History API позволяет перемещаться между различными состояниями приложения без перезагрузки страницы. pushState() принимает три аргумента: object state (любое сериализуемое значение), title (большинство браузеров его игнорируют, поэтому передавайте пустую строку) и url (разрешается относительно текущей страницы и должен принадлежать тому же источнику). Вот как добавить новое состояние:

<div>
    <button onclick="changeState()">Go to New State</button>
</div>

<script>
    // Function to change state
    function changeState() {
        const newState = { id: 'newState' };
        // Push a new state to the history stack
        window.history.pushState(newState, 'New State', 'new-state-url');
    }
</script>

Этот код добавляет новое состояние в стек истории с помощью pushState(). Обратите внимание на то, чего он не делает: он не загружает new-state-url и не генерирует событие popstate. pushState только обновляет адресную строку и стек — отображение соответствующего контента остаётся на вашей совести.

Обработка события Popstate

Когда пользователь нажимает кнопку «Назад» или «Вперёд» в браузере (либо вы вызываете history.back() / history.forward()), генерируется событие popstate. Свойство state события содержит object состояния, переданный ранее в pushState/replaceState для теперь активируемой записи. Обрабатывайте его для восстановления нужного представления:

window.addEventListener('popstate', function(event) {
        if(event.state) {
            console.log('State changed:', event.state);
            // Handle the state object here
        }
    });

Этот обработчик реагирует на события popstate, фиксирует изменения и позволяет корректировать состояние в зависимости от истории навигации пользователя. Если event.state равно null, пользователь вернулся к записи, созданной при обычной загрузке страницы (без object состояния) — в этом случае отображайте представление по умолчанию.

Замена текущего состояния

Иногда нужно обновить текущую запись без добавления новой в стек — например, синхронизировать фильтр или выбранную вкладку с URL, где добавление шага для кнопки «Назад» было бы неудобным. Именно для этого предназначен replaceState():

<div>
    <button onclick="replaceCurrentState()">Replace State</button>
    <p id="replace-status">Ready</p>
</div>

<script>
    function replaceCurrentState() {
        const newState = { id: 'replacedState' };
        // Replace the current state
        window.history.replaceState(newState, 'Replaced State', 'replaced-state-url');
        document.getElementById('replace-status').textContent = 'State replaced successfully!';
    }
</script>

Этот код обновляет текущую запись на месте. Кнопка «Назад» по-прежнему ведёт на страницу, которая была до этого, поскольку replaceState не добавляет новый шаг.

Полный пример в стиле SPA

Теперь соберём всё воедино. Пример ниже имитирует одностраничное приложение: нажатие кнопки меняет содержимое div и обновляет URL с помощью pushState, а обработчик popstate восстанавливает правильный контент при навигации пользователя кнопками «Назад/Вперёд». Это тот же паттерн, который клиентские маршрутизаторы используют под капотом.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>SPA Style History API Example</title>
</head>
<body>
    <h1>Page Navigation with History API</h1>
    <div id="content">Start Page</div>

    <!-- Buttons for navigation -->
    <button onclick="loadPage('page1')">Load Page 1</button>
    <button onclick="loadPage('page2')">Load Page 2</button>
    <button onclick="manualGoBack()">Go Back</button>
    <button onclick="manualGoForward()">Go Forward</button>

    <!-- Display the current status of the history -->
    <p id="historyStatus">History Status: Start</p>

    <script>
        // Loads a "page" and updates the browser's history state
        function loadPage(page) {
            const state = { page: page }; // State to be pushed to history
            history.pushState(state, `Page ${page}`, `${page}.html`); // Pushing state to the history
            document.getElementById('content').innerHTML = `<h2>This is ${page.replace('page', 'Page ')}</h2>`; // Update the content
            updateHistoryStatus(state); // Update the history status display
        }

        // Handles the browser's back and forward button actions
        window.addEventListener('popstate', function(event) {
            if (event.state) {
                // Update the page content and history status when navigating through history
                document.getElementById('content').innerHTML = `<h2>This is ${event.state.page.replace('page', 'Page ')}</h2>`;
                updateHistoryStatus(event.state);
            } else {
                // Fallback content when the history does not have any state
                document.getElementById('content').innerHTML = `<h2>Start Page</h2>`;
                document.getElementById('historyStatus').textContent = "History Status: Start";
            }
        });

        // Updates the display of the current history status
        function updateHistoryStatus(state) {
            document.getElementById('historyStatus').textContent = `History Status: ${state.page}`;
        }

        // Function to manually trigger going back in history
        function manualGoBack() {
            history.back();
        }

        // Function to manually trigger going forward in history
        function manualGoForward() {
            history.forward();
        }
    </script>
</body>
</html>

Как это работает:

  • Динамическая загрузка страницloadPage() изменяет содержимое div и вызывает pushState для добавления записи в историю, благодаря чему каждая «страница» становится полноценной точкой навигации «назад/вперёд».
  • Восстановление при навигации — обработчик popstate читает event.state.page и повторно отображает соответствующий контент; если event.state равно null, происходит возврат на стартовую страницу.
  • Привычный интерфейс — кнопки вызывают history.back() и history.forward(), создавая ощущение многостраничного сайта без единой полной перезагрузки.

Типичные ошибки

Ряд особенностей регулярно ставит разработчиков в тупик:

  • pushState не генерирует popstate. Событие генерируется только при навигации пользователя (кнопки «Назад/Вперёд», history.go()). После вызова pushState отображайте новое представление самостоятельно в той же функции.
  • Аргумент title игнорируется. Практически все браузеры его не учитывают, поэтому передавайте "". Чтобы изменить заголовок вкладки, установите document.title напрямую.
  • URL должны принадлежать тому же источнику. Передача URL другого источника вызывает SecurityError. Путь разрешается относительно текущего документа.
  • Состояние должно быть сериализуемым. object состояния клонируется алгоритмом структурированного клонирования, поэтому функции, DOM-узлы и экземпляры классов хранить нельзя — кроме того, существует ограничение по размеру (обычно несколько МБ).
  • Перезагрузка страницы запускает приложение по новому URL. Когда пользователь обновляет URL, добавленный через pushState, сервер должен ответить на этот путь (или SPA должно обработать его при загрузке), иначе пользователь получит ошибку 404. Серверный фолбэк — это задача маршрутизации, которую SPA обязано решить.

Рекомендации по использованию History API

  • Храните в состоянии минимум данных. Записывайте в object состояния идентификатор или несколько примитивов, а остальное восстанавливайте из них. Не сохраняйте большие массивы данных в историю — это раздувает сеанс и рискует превысить лимит размера.
  • Всегда самостоятельно обновляйте document.title при смене «страницы», поскольку аргумент title игнорируется.
  • Управляйте позицией прокрутки. Браузеры восстанавливают прокрутку при popstate через history.scrollRestoration; установите значение "manual", если хотите управлять прокруткой самостоятельно. Об API прокрутки читайте в разделе размеры окна и прокрутка.
  • Предусмотрите серверный фолбэк, чтобы обновление или передача URL, добавленного через pushState, продолжали работать.
  • Используйте replaceState для ненавигационных обновлений (фильтры, вкладки), чтобы кнопка «Назад» оставалась осмысленной.
  • Изучайте историю — используйте history.state для получения текущего object состояния и history.length для количества записей в стеке сеанса.

Поскольку History API только изменяет URL, он естественно сочетается с fetch для загрузки данных, нужных каждой «странице».

Заключение

JavaScript History API позволяет управлять историей сеанса браузера — добавлять, заменять записи и реагировать на навигацию — без полной перезагрузки страниц. Освоение pushState, replaceState и события popstate с учётом особенностей, связанных с popstate, сериализацией и URL одного источника, делает одностраничные приложения такими же быстрыми и удобными для навигации, как традиционные многостраничные сайты.

Практика

Практика
Какие из перечисленных возможностей предоставляет JavaScript History API?
Какие из перечисленных возможностей предоставляет JavaScript History API?
Was this page helpful?