MDN History API - discussion (2023-01-30)
MDN's History API doc is the kind of primitive-level reading that makes route contracts feel less magical. Once you understand what the browser is actually doing with pushState, replaceState, and back/forward, it's easier to design a calm URL surface and keep the rest of the app model in explicit state documents.
Do you treat URL changes as commands (intent) or as a representation of state? What do you put in the URL versus a route document when you have lots of UI motion? How do you debug back/forward issues—do you log history transitions as evidence?
Comments (16)
Back to latestURL is the "where" for us. State docs are the "what".
When we put too much "what" in the URL, sharing is fine but the app becomes fragile.
We log history transitions as a contract log. It's saved us a lot:
txt
[tips] history pushState url=/accounts/42 reason=nav:click
[tips] history popstate url=/accounts reason=back
[tips] routeDoc replay panel=list selection=null
The replay line is key. Back/forward isn't just URL; it's the model replaying.
Counterpoint: treating URL as only "where" can make sharing frustrating if the "what" matters (filters, tabs).
We compromised: URL keeps a couple user-meaningful keys; route doc owns the rest.
Same. The guide's posture isn't "URL empty"; it's "URL calm".
If a key is meaningful for sharing, it can belong in URL—just make it part of the contract and log it.
Back/forward bugs got better when we made panel state explicit and replayable.
If panel is hidden component state, back/forward becomes a guessing game.
I like MDN because it reminds you that history state is arbitrary and easy to misuse.
If you store too much in history state, you end up with hidden, unversioned contracts.
We treat replaceState as a "correction" tool (fix URL without adding history entry). And we log it, because otherwise it feels like the browser is haunted:
txt
[tips] history replaceState url=/search?q=react reason=normalize:queryCounterpoint: history logging can be noisy in complex apps.
We only log push/replace/pop transitions + the route doc key summary, not every param tweak.
The concept that helped me: URL changes are intent signals, but the app decides how to interpret them.
That's basically the route contract story.
History state can become a footgun if you treat it as storage.
If you need persistence, use a real store/vault and treat history as navigation only.
Docs tie-in: the routing docs here basically recommend this: keep URL stable, keep state explicit, render evidence.
Related: Routing and Navigation and The App Router Mindset.
We also render a "history action" evidence key (push/replace/pop).
It made it easier to debug why a user can't get back to the previous screen.
Counterpoint: sometimes putting filters in URL is just the right UX.
But we still parse/serialize through one module and log parse results so defaults don't drift.
If you do UI motion (overlays) without URL changes, you should still have a route-doc motion key.
Otherwise the motion becomes hidden state and back/forward can't reason about it.
A trick we used: log a compact summary of route doc on every history transition.
It acts like a receipt without calling it a receipt.
MDN also clarified what doesn't happen automatically (scroll restoration, state replay).
Once you accept that, you're more willing to build explicit contracts for it.
If a user reports navigation bugs, history logs + evidence keys are the fastest path to diagnosis.
Otherwise you end up guessing based on vibes.