DEV Community

Mobeen Ahmed
Mobeen Ahmed

Posted on

Share links with app state in the URL hash: copy, paste, resume

I wanted a small thing on ramrom.io that most tool sites never bother with: if you have done half the work on a page, you should be able to send someone a link and have them land on exactly the same state. Not the homepage. Not a blank tool. The same link pasted in, the same format picked, the same calculator filled in. That is what the URL hash is for, and it took me an afternoon to get right.

The idea is simple. Everything the page needs to rebuild its state lives after the # in the URL. The hash is never sent to the server, so nothing about what you pasted ever touches my logs. It is just there in the address bar, waiting for the browser to read it. When someone opens a shared link, a small script runs on load, splits the hash into key-value pairs, and puts each one back into its field.

The first version was naive. I encoded the whole state as one JSON blob, base64 and all, and it worked until someone pasted a link into a chat app that truncated it. Long hashes get cut, mangled, or wrapped. So I went back to the boring approach: plain key=value pairs joined with &, the same shape as a query string, just sitting after the #. A TikTok link becomes url=..., a quality choice becomes format=hd, a calculator's hours become hours=8. Human-readable, copyable, and short enough that nothing truncates it.

Encoding matters more than it looks. A pasted video link contains ?, &, and = of its own, so every value goes through encodeURIComponent before it joins the hash, and decodeURIComponent on the way back. I learned that one the hard way: the first build decoded the hash with a plain split('&') and a link containing an &si= tracking parameter silently broke the state in two. The fix is three lines, but it is the difference between a feature and a bug report.

There is a second half to this that people forget: the hash has to update as you work, not just on load. Every time a field changes, I rebuild the hash with history.replaceState instead of assigning to location.hash. The difference matters. Setting location.hash adds a history entry and scrolls the page; replaceState swaps the URL in place, silently. Nobody wants their back button to step through every keystroke they typed into a calculator.

I also had to decide what not to share. Anything sensitive or huge stays out of the hash. On the downloader pages, the hash only ever carries the pasted link and the chosen format — never the resolved download URL, which is temporary and dies after a few minutes anyway. Sharing a dead link would be worse than sharing nothing. On the calculator pages the hash carries the entered numbers only, and nothing else. If the state is too big to fit sensibly in a URL, that is the feature telling you it does not belong there.

The part I am happiest with is the fallback. If the hash is empty, corrupt, or refers to a field that no longer exists, the page just loads blank and works normally. No error, no warning, no broken page. A shared link should degrade to a normal link, never to a crash. I test this by hand before every deploy: paste a garbage hash, reload, and check the tool still works.

There is no library involved. No framework, no router, no state manager. It is about forty lines of plain JavaScript: one function to read the hash into an object, one to write an object back into the hash, and a listener that calls the writer whenever a field changes. That is the whole feature.

It is a small thing, and most visitors will never notice it. But the ones who do — someone sending a colleague a pre-filled calculator, or a friend sharing the exact video they were looking at — get a link that resumes instead of restarting. That is the entire point of a tool: less friction between the person and the result. The hash is the cheapest way I know to buy that.

Top comments (0)