I have a very simple component to demonstrate this issue.


    <button onclick={push1}>Push 1</button>
    <button onclick={push2}>Push 2</button>
    <button onclick={push3}>Push 3</button>


import { LightningElement } from 'lwc';

export default class Users extends LightningElement {
    connectedCallback() {

    handlePopstate = (event) => {
        console.log('handling popstate');

    push1() {

    push2() {

    push3() {

    push (value) {
        console.log('pushing new history');
        const params = new URLSearchParams(window.location.search);
        const newUrl = `${window.location.origin}${window.location.pathname}?${params}`;
        window.history.pushState({ path: newUrl, push: value },'',newUrl);

The pushState function itself is working exactly as expected. However, the back and forward buttons do not navigate through the updated history stack as expected. Here is an example I've simulated:

  1. Click "Push 1" => updates c__push param to 1
  2. Click "Push 2" => updates c__push param to 2
  3. Click "Push 3" => updates c__push param to 3
  4. Click back on browser => updates c__push param to 2, but the forward button is disabled after VERY briefly showing as enabled.
  5. Click back on browser again => c__push param stays at 2, and forward button becomes enabled, but if click will keep param at 2. <- this whole step seems to behave as though a second instance of this history somehow made it into the stack
  6. Click back on browser again => c__push param goes to 1, and forward button is disabled again.

I can't find anything to suggest that window.history.pushState shouldn't work in LWC. Does anyone have any idea what might be at play here?

If pushState() really is broken in LWC then I'm not sure how I'm going to build a reasonable navigational structure. It seems pushState() SHOULD work in LWC though, right...? It seems at least one other person in the world is or at least was doing this, though I'm not sure what the responder in this thread is referring to with "SecureWindow" as this doesn't seem to be something I can declare: Lightning LockerService & History.pushState

1 Answer 1


Given the parameter names, it appears you're in Salesforce. Assuming that, you should be using NavigationMixin:

import { CurrentPageReference, NavigationMixin } from 'lightning/navigation';
import { LightningElement, wire } from 'lwc';


export default class MyComponent extends NavigationMixin(LightningElement) {
  @wire(CurrentPageReference) handleStateChange(pageReference) {
    this.myValue = pageReference.state.c__myValue;
    this.pageRef = pageReference;
  modifyState(state) {
      { ...this.pageRef, state }

Note that this all works without needing to use pushState, popState, replaceState, or handling popstate directly.

This feature works in Lightning Experience, Digital Experiences, and the mobile app.

What you're likely experiencing is NavigationMixin intefering with your own state changes.

When you're outside of Salesforce-controlled environments, it works as you expect:

import { LightningElement } from 'lwc';

export default class App extends LightningElement {
  state = 1;
  connectedCallback() {
    window.addEventListener('popstate', this.handleState);
  disconnectedCallback() {
    window.removeEventListener('popstate', this.handleState);
  handleState = (event)  => {
    this.state = event.state.state
  setState(event) {
    let state = event.target.dataset.index;
    this.state = state;
    window.history.pushState({ state }, '', `?state=${state}`);


  • this looks like exactly what I need - thanks sooo much! Does NavigationMixin also contain an equivalent of window.history.replaceState()? Particularly, when my page is visited without params I will fill them with some default values, and would like to replace the current history entry with the new URL with params so that users can't click back to the URL without them. Mar 28 at 5:37
  • @BrigLarimer Yes, there's an optional second parameter to [NavigationMixin.Navigate] which is called replaceUrl. When truthy, it behaves as replaceState, and when falsy behaves as pushState. The default (as shown in this code) is to behave like pushState. Check out the docs for lightning/navigate if you need more info.
    – sfdcfox
    Mar 28 at 13:15
  • That's perfect, thanks again! You saved me from some serious dev despair there - truly :) Mar 28 at 17:15

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .