DEV Community

Daniel Pertu
Daniel Pertu

Posted on

Thirty-two controls on our free tools, and not one of them is in the URL

The round planner on our marketing site turns a set of quiz rounds into a running order with real timings. It has 15 controls: a start time, a break length, which round the break follows, and a type select plus a question count for each round, with more appearing as you add rounds.

Fill it in, get your running order, and send it to the person actually hosting the night. You cannot. The URL never changes.

The measurement

Scripted on the live page:

defaults        start 20:00, 6 rounds, break after 3      1 hr 51 min
edit            start 19:30, add two rounds (8 total)     2 hr 17 min
                url: /tools/round-planner      (unchanged)
reload          start 20:00, 6 rounds                     1 hr 51 min
edit again      start 21:15
navigate away to /pricing, then press Back
                start 21:15                               restored
open /tools/round-planner?start=21:15&rounds=8 in a new tab
                start 20:00, 6 rounds                     defaults
Enter fullscreen mode Exit fullscreen mode

Four outcomes, three of which lose the work:

  • Reload loses it. The inputs are React-controlled, so the browser's own form restoration has nothing to put back.
  • A copied link loses it. Nothing on the page reads location.search.
  • A new tab loses it, for the same reason.
  • Back keeps it, because our prerendered pages qualify for the back/forward cache and the whole document comes back alive. That is a browser feature we benefit from, not a decision we made.

It is all four tools

tool controls state
round planner 15 and counting start time, break length, break position, per-round type and question count
quiz night profit calculator 11 ten numbers and a currency
scoresheet generator 4 quiz name, rounds, questions per round, teams
QR code generator 2 the address, the label under the code

Thirty-two controls, four useState hooks holding the lot, zero reads of the query string, and nothing written to localStorage either. I checked the storage side separately a few days ago and the whole public site writes to none of the seven storage surfaces a browser offers.

The QR generator is the one that stings. Its entire output is a web address, printed as a square. The tool that exists to make a link shareable cannot itself be shared in a filled-in state.

The cost of fixing it is not what I assumed

My first assumption was that putting state in the query string would hurt us at the CDN: these are prerendered pages served from cache, and query strings usually mean cache keys.

They do not, here. Two requests, one with a query string:

curl -sI https://pub-trivia.app/tools/round-planner
# etag: "dwxhstp9uo1kxv"   age: 86703   x-vercel-cache: HIT   content-length: 73797

curl -sI "https://pub-trivia.app/tools/round-planner?rounds=6&len=8"
# etag: "dwxhstp9uo1kxv"   age: 86703   x-vercel-cache: HIT   content-length: 73797
Enter fullscreen mode Exit fullscreen mode

Same ETag, same age, same byte count, same cached object. The cache key is the path. A shared planner link would be served from the identical prerendered HTML, and the state would be applied on the client after hydration, which keeps the page static and costs one effect.

So the reason the state is not in the URL is not performance and not caching. It is that nobody wrote it, and with a tool that works the first time you load it, nobody notices.

What the design should probably be

  • Read the query string on mount, fall back to the defaults, and keep the URL in step with history.replaceState rather than pushState, so typing a number does not fill the Back stack. Back should still leave the page, which is the behaviour people expect.
  • Keep the parameters short and validated, with the same clamps the inputs already have, because a shared link is an input from a stranger.
  • Leave the QR generator's label out of the URL if you like, but the address belongs there.

There is a second prize in it too. Every one of these pages is a tool and a content page at once, and a URL that carries a worked example is a link somebody else can publish. "Here is the running order for a six round quiz that finishes by eleven" is a better link than "here is a planner".

Check it yourself

Open the round planner, change the start time, add a couple of rounds, and watch the address bar not move. Copy the URL into a new tab and compare. Then press Back from any page you navigate to and watch the whole plan come back, which is the one case that works and the one case nobody relies on.

Top comments (0)