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 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 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.replaceStaterather thanpushState, 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.

