Prevent full page load when clicking the forward button with popstate event

I’m creating a SPA that has multiple pages that have some hierarchy. The user can click on any item in a list to see details (going forward) and on the details page can click a “back” button on the page to return to the list view.

When a list item is clicked and the details are shown, I call pushState to add a history item. When clicking on my “back” button, I call history.back() to return. By handling the popstate event, I do the same when the browser’s back navigation button is used.

Now the trouble starts. Here, the user can click the browser’s “forward” button. I’d expect another popstate event to be triggered, but I see a full page load instead. On the newly loaded page, then the expected popstate event is triggered, but that’s too late.

The page transitions use animations and they only work if no reload happens in between. Also, loading the page newly destroys any view state.

How can I prevent the browser from loading the page when clicking the browser’s forward button?

Using Firefox 132 on Windows. ChatGPT was repeating the same information as MDN, but at some point stated that a full page load would be normal on forward navigation. Wouldn’t that contradict the whole purpose of a “soft navigation” with the history API? If that’s true, can it be prevented?

Also, I’m having trouble detecting whether the popstate was raised from a back or forward navigation. A few hacks seem to exist but they all fail after looking at them for more than a few seconds. For example, the user can skip history entries, which none of the solutions even try to handle. I’m thinking about dropping the whole history API completely and not allowing the user the use the back button at all within my SPA because it’s so broken and useless.

An acceptable hack would be to clear all future history on any popstate event to ensure that the user can only ever use it to go back but never forward again. But sadly the history API hides behind “security issues” and won’t even offer that simple operation. (I could erase all future history by calling pushState again, but that would introduce a new unwanted entry and mess up the history when going back next time.)

I’m not using any JavaScript framework or library, but would look at a good implementation to see how it might be solved.