MDN History API - discussion (2022-07-23)
For apps that need deep links + complex UI motion (tabs/modals/inspector): how much do you actually rely on the History API directly vs letting your routing layer abstract it?
Do you use pushState/replaceState to encode UI posture, or treat history as a pure URL thing? How do you keep back/forward deterministic when multiple panels participate? And do you render any history/nav signals so support can explain why navigation behaved a certain way?
Comments (10)
Back to latestWe rarely touch History API directly anymore. But back/forward bugs keep happening, so maybe we should.
We don't call history.pushState all over the app, but we do treat posture as route truth.
Once posture is route-visible, you can make back/forward deterministic because state is the contract, not events.
Related: Routing and Navigation and Navigation Routines (Deep Dive #4).
We did a "single boundary" approach: one place translates posture -> history, and one place translates history -> posture.
ts
function writePosture(p: { tab: string; inspector?: string }) {
const url = new URL(location.href);
url.searchParams.set('tab', p.tab);
p.inspector ? url.searchParams.set('inspector', p.inspector) : url.searchParams.delete('inspector');
history.pushState({ posture: p }, '', url);
setNavSignal({ to: url.pathname + url.search, reason: 'posture-write' });
}
window.addEventListener('popstate', (e) => {
setHistorySignal({ type: 'popstate', hasState: Boolean(e.state) });
});Counterpoint: encoding too much posture in the URL can make it unreadable and fragile.
We store only what users expect to share (tab, selected id) and keep the rest as derived evidence in route docs.
Agree. The rule we ended up with: URL contains inputs, docs contain derived evidence, and signals show the current lane.
If you shove derived stuff into params, you'll regret it during refactors.
Yep. Inputs only. Everything else is evidence.
The important part is you can reproduce it. If two panels fight over history and there's no signal, you'll never debug it.
We had a popstate bug where a modal closed but the inspector stayed open, and it only happened on one navigation path.
Once we stored the overlay stack in route state and rendered it as evidence, the bug became obvious.
One tip: keep history state small and stable. If you store random objects, you end up debugging serialization instead of navigation.
Signals that helped us: historySignal={type,hasState} and navSignal={from,to,reason}.
It doesn't solve everything, but it stops the "it just happened" bug report class.
If back/forward is a feature, treat it like an API. You need a contract and tests, not hope.
We never touched history until support started filing "stuck overlay" tickets. After that, route truth + signals paid for itself immediately.