javascriptajaxgoogle-chromesingle-page-applicationback-button

Browser back button exits Single Page App despite pushState() on Chrome only


I’m working on a legacy single-page application that uses AJAX to load different internal views. After recent browser updates the Browser Back Button stopped behaving as expected.

The current problem:

When the user clicks the Back button in Firefox, everything works as intended — the app restores the previous in-app view without leaving the page.

In Chrome, the first Back click correctly restores the previous in-app view but the second Back click always kicks the user out of the app and back to the login screen.

I’m already using history.pushState and listening to popstate to restore the internal content and re-push guard states when needed. It seems like Chrome doesn’t honor the guard state or simply navigates past it. I’ve tried: -Unique hash fragments for every state -Replacing the base state -Re-pushing guard states in popstate

None of it seem to make a difference in Chrome.

function sendAction(action, content) {
  // Capture current dynamic content
  const section = document.querySelector('.content section.rows');
  const html = section ? section.innerHTML : '';

  // Push in-app navigation state
  const newUrl = window.location.href.split('#')[0] + '#' + encodeURIComponent(content);
  history.pushState({ html, content }, null, newUrl);
  
  // (Ajax content loading happens here)
}

// Back button handling
window.addEventListener('popstate', function(event) {
  const section = document.querySelector('.content section.rows');

  if (event.state && event.state.html && section) {
    // Restore previous HTML content
    section.innerHTML = event.state.html;
    event.preventDefault();
    return;
  }

  // Prevent leaving the app
  history.pushState({ guard: true }, null, window.location.href);
  event.preventDefault();
});

// Initialize base history state
if (!history.state) {
  const section = document.querySelector('.content section.rows');
  history.replaceState(
    { initialized: true, html: section ? section.innerHTML : '' },
    null,
    window.location.href
  );
}

Solution

  • Thanks to @mplungjan I was able to fix the issue by adding a timeout

    setTimeout(() => {
        history.pushState(
          { guard: true, html: pageContent.innerHTML },
          null,
          window.location.href
        );
        console.debug("Guard state reinserted");
      }, 0);