didn't down-vote you but perhaps you mean tech which holds nearly all client state on the server like JSF or webforms and I think that may be not so clear to some :)
for front-end frameworks, not storing the state on the URL usually means storing it on the memory or sessionstorage and server is usually not involved
This pattern - saving the query to the URL with the history API - is fantastic UX but never gets implemented because there’s never time. Luckily an LLM can build this quickly as it’s straightforward and mostly boilerplate.
Still the boilerplate makes me wonder if it belongs in a library, eg. a React hook that’s a drop in replacement for `useState`. Backend logic would still need to be implemented. Does something like this exist?
I didn't see if you were doing this, but there is an additional use case that I had when using hot swapping like HTMX: updating other links on the page to reflect the URL state, when those links are outside of the swapped content.
While the server can use the URLs to update the links in the new HTML, if other links outside that content should also reflect the changes params, you need to manually update them.
You don't need to do anything different to the other URLs on the page, by default all parameters are passed along in every request, so you just need to retrieve any expected url parameters in the server code
Note that you can store longer state (at least 64K; more not tested) in the fragment (`location.hash`); obviously only the client gets to see this, but it's better than nothing (and JS can send it to the server if really needed).
For parameters the server does need to see, remember that params need not be &-separated kv pairs, it can be arbitrary text. Keys can usually be eliminated and just hard-coded in the web page; this may make short params longer but likely makes long ones shorter.
You absolutely should not restore state based on LocalStorage; that breaks the whole advantage of doing this properly! If I wanted previous state, I would've navigated my history thereto. I hope this isn't as bad as sites that break with multiple open tabs at least ...
The example URL here, though, is still not (helpfully) bookmarkable because the contents of page 2 will change as new items are added. To get truly bookmarkable list URLs, the best approach I've seen is ‘page starting from item X’, where X is an effectively-unique ID for the item (e.g. a primary key, or a timestamp to avoid exposing IDs).
Yeah, solving this edge case properly can add a lot of complexity (your solution has the same problem, no? deletes would mess it up as would updates, technically). I've seen people using long-lived "idempotency tokens" point to an event log for this but it's a bit nuts. Definitely worth considering not solving it, which might be a more intuitive UX anyway (e.g. for leaderboards).
I like the simplicity. I've been building some web apps with Alpine.js recently, another lightweight React alternative. It's pretty powerful and capable for building reactive SPAs, only ~16kb.
Our dealership listings page is largely run with this pattern, as well as most of our plugins. Nothing new and very dependable. Forgo HTMX for one less dependency.
This is a great pattern to follow, and I highly recommend understanding it even to those working on projects that are full client-side SPAs.
Its too easy to jump right in to react, NextJS, etc. Learning why URLs work the way they work is still extremely useful. Eventually you will want to support deep linking, and when you do the URL suddenly becomes really important.
Remix has also been really interesting on this front. They leaned heavily into web standards, including URLs as a way of managing state.
The JS world leaves me more and more perplexed.There's a similar rant about forms, but why is this so hard? Huge amount of dev time spent being able to execute asynchronous functions to the backend seamlessly yet pretty much every major framework is just rawdog the url string and deal with URLSearchParams object yourself.
Tanstack router[1] provides first class support for not only parsing params but giving you a typed URL helper, this should be the goal for the big meta frameworks but even tools like sveltekit that advertise themselves on simplicity and web standards have next to zero support.
I've seen even non JS frameworks, with like fifteen lines of documentation for half baked support of search params.
The industry would probably be better off if even a tenth of the effort that goes into doing literally anything to avoid learning the platform was spent making this (and post-redirect-get for forms) the path of least resistance for the 90% of the time search params are perfectly adequate.
I don't use HTMX but i do love how it and its community are pushing the rediscover of how much simpler things can be.
> Tanstack router[1] provides first class support for not only parsing params but giving you a typed URL helper, this should be the goal for the big meta frameworks
Let's not pretend that the Tanstack solution would be good. For example, What if my form changes and a new field is added but someone still runs the old html/js and sends their form from the old code? Does Tanstack have any support to 1.) detect that situation 2.) analyze / monitor / log it (for easy debugging), 3.) automatically resolve it (if possible) and 4.) allow custom handling where automatic resolution isn't possible?
It doesn't look like it from the documentation.
Sorry, frustration is causing me to rant here, but it's a classical thing of the frontend-world and it causes so much frustration. In the backend-world, many (maybe even most) libraries/frameworks/protocols have builtin support for that. See graphql with it's default values and deprecation at least, see avro or protobuff with their support for versions, schema-history and even automatic migration.
When will I not have to deal with that by hand in my frontend-code anymore?
On a related note, I've found combining htmx with Parsley[0] to be very powerful for adding client-side validation to declarative server-rendered HTML form definitions. All that is needed is a simple htmx extension[1] and applicable data attribute use.
> treating URL parameters as your single source of truth... a URL like /?status=active&sortField=price&sortDir=desc&page=2 tells you everything about the current view
Hard disagree that there can be a single source of truth. There are (at least) 3 levels of state for parameter control, and I don't like when libraries think they can gloss over the differences or remove this nuance from the developer:
- The "in-progress" state of the UI widgets that someone is editing (from radio buttons to characters typed in a search box)
- The "committed" state that indicates the snapshot of those parameters that is actively desired to be loaded from the server; this may be debounced, or triggered by a Search button
- The "loaded" state that indicates what was most recently loaded from the server, and which (most likely) drives the data visualized in the non-parameter-controlling parts of the UI
What if someone types in a search bar but then hits "next page" - do we forget what they typed? What happens if you've just committed changes to your parameters, but data subsequently loaded from a prior commit? Do changes fire in sequence? Should they cancel prior requests or ignore their results? What happens if someone clicks a back button while requests are inflight, or while someone's typed uncommitted values into a pre-committed search bar? How do you visualize the loaded parameters as distinct from the in-progress parameters? What if some queries take orders of magnitude longer than others, and you want to provide guidance about this?
All of those questions and more will vary between applications. One size does not fit all.
If this comment resonates with you, choose and advocate for tooling that gives you the expressivity you feel in your gut that you'll need. Especially in a world of LLMs, terse syntax and implicit state management may not be worth losing that expressivity.
> All of those questions and more will vary between applications. One size does not fit all.
all of those come from the fundamental "requirement" set out earlier to have no in-page state, but still require the webpage to behave as tho it did.
If you remove this requirement, then it will be like how it was back in the 2000's era web pages! And the url does indeed contain the single source of truth - there are no inflight requests that are not also a full page reloads.
I had a similar strategy when building early web apps with jQuery and ExtJS (but using the URL hash before the History API was available). Just read from location.hash during page load and write to it when the form state changes.
For more complex state (like table layout), I used to save it as a JSON object, then compress and base64 encode it, and stick it in the URL hash. Gave my users a bookmarklet that would create a shortened URL (like https://example.com/url/1afe9) from my server if they needed to share it.
48 comments
[ 5.9 ms ] story [ 67.9 ms ] threadEDIT: Hmm. Is this comment controversial? Obviously some people disagree strongly. Mind sharing why?
for front-end frameworks, not storing the state on the URL usually means storing it on the memory or sessionstorage and server is usually not involved
Still the boilerplate makes me wonder if it belongs in a library, eg. a React hook that’s a drop in replacement for `useState`. Backend logic would still need to be implemented. Does something like this exist?
That’s exactly what `nuqs` does (disclaimer: I’m the author).
> Backend logic would still need to be implemented
Assuming your backend is written in TypeScript, you can use nuqs loaders to reuse the same validation logic on both sides.
https://nuqs.47ng.com
While the server can use the URLs to update the links in the new HTML, if other links outside that content should also reflect the changes params, you need to manually update them.
In my progressive enhancement library I call this 'sync-params' https://github.com/roryl/zsx?tab=readme-ov-file#synchronize-...
https://htmx.org/attributes/hx-params/
For parameters the server does need to see, remember that params need not be &-separated kv pairs, it can be arbitrary text. Keys can usually be eliminated and just hard-coded in the web page; this may make short params longer but likely makes long ones shorter.
You absolutely should not restore state based on LocalStorage; that breaks the whole advantage of doing this properly! If I wanted previous state, I would've navigated my history thereto. I hope this isn't as bad as sites that break with multiple open tabs at least ...
Why is the content changing between refreshes not "(helpfully) bookmarkable"?
The HN front page (ie. "page 1") does that but it's a very useful bookmark.
https://alpinejs.dev/
https://github.com/alpinejs/alpine
https://alpine-ajax.js.org/
https://www.lexo.ch/blog/2025/01/highlight-text-on-page-and-...
In any case, yeah, what was suggested in the submission is nothing esoteric, but I guess everything can be new to someone.
Its too easy to jump right in to react, NextJS, etc. Learning why URLs work the way they work is still extremely useful. Eventually you will want to support deep linking, and when you do the URL suddenly becomes really important.
Remix has also been really interesting on this front. They leaned heavily into web standards, including URLs as a way of managing state.
Tanstack router[1] provides first class support for not only parsing params but giving you a typed URL helper, this should be the goal for the big meta frameworks but even tools like sveltekit that advertise themselves on simplicity and web standards have next to zero support.
I've seen even non JS frameworks, with like fifteen lines of documentation for half baked support of search params.
The industry would probably be better off if even a tenth of the effort that goes into doing literally anything to avoid learning the platform was spent making this (and post-redirect-get for forms) the path of least resistance for the 90% of the time search params are perfectly adequate.
I don't use HTMX but i do love how it and its community are pushing the rediscover of how much simpler things can be.
[1] https://tanstack.com/router/latest/docs/framework/react/guid...
Let's not pretend that the Tanstack solution would be good. For example, What if my form changes and a new field is added but someone still runs the old html/js and sends their form from the old code? Does Tanstack have any support to 1.) detect that situation 2.) analyze / monitor / log it (for easy debugging), 3.) automatically resolve it (if possible) and 4.) allow custom handling where automatic resolution isn't possible?
It doesn't look like it from the documentation.
Sorry, frustration is causing me to rant here, but it's a classical thing of the frontend-world and it causes so much frustration. In the backend-world, many (maybe even most) libraries/frameworks/protocols have builtin support for that. See graphql with it's default values and deprecation at least, see avro or protobuff with their support for versions, schema-history and even automatic migration.
When will I not have to deal with that by hand in my frontend-code anymore?
0 - https://parsleyjs.org/doc/index.html
1 - https://htmx.org/docs/#extensions
Hard disagree that there can be a single source of truth. There are (at least) 3 levels of state for parameter control, and I don't like when libraries think they can gloss over the differences or remove this nuance from the developer:
- The "in-progress" state of the UI widgets that someone is editing (from radio buttons to characters typed in a search box)
- The "committed" state that indicates the snapshot of those parameters that is actively desired to be loaded from the server; this may be debounced, or triggered by a Search button
- The "loaded" state that indicates what was most recently loaded from the server, and which (most likely) drives the data visualized in the non-parameter-controlling parts of the UI
What if someone types in a search bar but then hits "next page" - do we forget what they typed? What happens if you've just committed changes to your parameters, but data subsequently loaded from a prior commit? Do changes fire in sequence? Should they cancel prior requests or ignore their results? What happens if someone clicks a back button while requests are inflight, or while someone's typed uncommitted values into a pre-committed search bar? How do you visualize the loaded parameters as distinct from the in-progress parameters? What if some queries take orders of magnitude longer than others, and you want to provide guidance about this?
All of those questions and more will vary between applications. One size does not fit all.
If this comment resonates with you, choose and advocate for tooling that gives you the expressivity you feel in your gut that you'll need. Especially in a world of LLMs, terse syntax and implicit state management may not be worth losing that expressivity.
all of those come from the fundamental "requirement" set out earlier to have no in-page state, but still require the webpage to behave as tho it did.
If you remove this requirement, then it will be like how it was back in the 2000's era web pages! And the url does indeed contain the single source of truth - there are no inflight requests that are not also a full page reloads.
https://data-star.dev/guide/reactive_signals
For more complex state (like table layout), I used to save it as a JSON object, then compress and base64 encode it, and stick it in the URL hash. Gave my users a bookmarklet that would create a shortened URL (like https://example.com/url/1afe9) from my server if they needed to share it.
It cant be used for everything. E.g. not dark mode!
https://github.com/Nanonid/rison
Exactly, this approach doesn't scale well without trickery involved. You have to have some sort of weird encoding in place to compact it down.